diff --git a/app/controllers/admin/standing_orders_controller.rb b/app/controllers/admin/standing_orders_controller.rb index 8fb3c66518..cc8247cd9e 100644 --- a/app/controllers/admin/standing_orders_controller.rb +++ b/app/controllers/admin/standing_orders_controller.rb @@ -6,6 +6,7 @@ module Admin before_filter :load_form_data, only: [:new, :edit] before_filter :strip_banned_attrs, only: [:update] before_filter :wrap_nested_attrs, only: [:create, :update] + before_filter :check_for_open_orders, only: [:cancel, :pause] respond_to :json def index @@ -48,7 +49,7 @@ module Admin end def cancel - @standing_order.cancel + @standing_order.cancel(@open_orders_to_keep || []) respond_with(@standing_order) do |format| format.json { render_as_json @standing_order, fee_calculator: fee_calculator } @@ -56,6 +57,10 @@ module Admin end def pause + unless params[:open_orders] == 'keep' + @standing_order.proxy_orders.placed_and_open.each(&:cancel) + end + @standing_order.update_attributes(paused_at: Time.zone.now) render_as_json @standing_order, fee_calculator: fee_calculator end @@ -116,6 +121,13 @@ module Admin end end + def check_for_open_orders + return if params[:open_orders] == 'cancel' + @open_orders_to_keep = @standing_order.proxy_orders.placed_and_open.pluck(:id) + return if @open_orders_to_keep.empty? || params[:open_orders] == 'keep' + return render json: { errors: { open_orders: t('admin.standing_orders.confirm_cancel_open_orders_msg') } }, status: :conflict + end + def strip_banned_attrs params[:standing_order].delete :schedule_id params[:standing_order].delete :customer_id diff --git a/app/models/proxy_order.rb b/app/models/proxy_order.rb index 68f0f46c6e..0d3a650eeb 100644 --- a/app/models/proxy_order.rb +++ b/app/models/proxy_order.rb @@ -8,6 +8,7 @@ class ProxyOrder < ActiveRecord::Base scope :closed, -> { joins(:order_cycle).merge(OrderCycle.closed) } scope :not_closed, -> { joins(:order_cycle).merge(OrderCycle.not_closed) } scope :not_canceled, where('proxy_orders.canceled_at IS NULL') + scope :placed_and_open, joins(:order).not_closed.where(spree_orders: { state: 'complete' }) def state return 'canceled' if canceled? diff --git a/app/models/standing_order.rb b/app/models/standing_order.rb index 471cca11f0..1b56e9d7c8 100644 --- a/app/models/standing_order.rb +++ b/app/models/standing_order.rb @@ -30,10 +30,10 @@ class StandingOrder < ActiveRecord::Base proxy_orders.not_closed end - def cancel + def cancel(keep_ids = []) transaction do self.update_column(:canceled_at, Time.zone.now) - proxy_orders.each(&:cancel) + proxy_orders.reject{ |o| keep_ids.include? o.id }.each(&:cancel) true end end diff --git a/config/locales/en.yml b/config/locales/en.yml index 9edd595659..6ad3be4d87 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -858,6 +858,7 @@ en: pause_failure_msg: 'Sorry, pausing failed!' confirm_unpause_msg: Are you sure you want to unpause this standing order? unpause_failure_msg: 'Sorry, unpausing failed!' + confirm_cancel_open_orders_msg: "Some orders for this standing order are currently open. The customer has already been notified that the order will be placed. Would you like to cancel these order(s) or keep them?" order_update_issues_msg: Some orders could not be automatically updated, this is most likely because they have been manually edited. Please review the issues listed below and make any adjustments to individual orders if required. no_results: no_standing_orders: No standing orders yet... diff --git a/spec/controllers/admin/standing_orders_controller_spec.rb b/spec/controllers/admin/standing_orders_controller_spec.rb index d50f9efa4c..3e07b45538 100644 --- a/spec/controllers/admin/standing_orders_controller_spec.rb +++ b/spec/controllers/admin/standing_orders_controller_spec.rb @@ -415,12 +415,66 @@ describe Admin::StandingOrdersController, type: :controller do context "with authorisation" do before { shop.update_attributes(owner: user) } - it 'renders the cancelled standing_order as json' do - spree_put :cancel, params - json_response = JSON.parse(response.body) - expect(json_response['canceled_at']).to_not be nil - expect(json_response['id']).to eq standing_order.id - expect(standing_order.reload.canceled_at).to be_within(5.seconds).of Time.now + context "when at least one associated order is still 'open'" do + let(:order_cycle) { standing_order.order_cycles.first } + let(:proxy_order) { create(:proxy_order, standing_order: standing_order, order_cycle: order_cycle) } + let!(:order) { proxy_order.initialise_order! } + + before { while !order.completed? do break unless order.next! end } + + context "when no 'open_orders' directive has been provided" do + it "renders an error, asking what to do" do + spree_put :cancel, params + expect(response.status).to be 409 + json_response = JSON.parse(response.body) + expect(json_response['errors']['open_orders']).to eq I18n.t('admin.standing_orders.confirm_cancel_open_orders_msg') + end + end + + context "when 'keep' has been provided as the 'open_orders' directive" do + before { params.merge!({ open_orders: 'keep'}) } + + it 'renders the cancelled standing_order as json, and does not cancel the open order' do + spree_put :cancel, params + json_response = JSON.parse(response.body) + expect(json_response['canceled_at']).to_not be nil + expect(json_response['id']).to eq standing_order.id + expect(standing_order.reload.canceled_at).to be_within(5.seconds).of Time.now + expect(order.reload.state).to eq 'complete' + expect(proxy_order.reload.canceled_at).to be nil + end + end + + context "when 'cancel' has been provided as the 'open_orders' directive" do + let(:mail_mock) { double(:mail) } + + before do + params.merge!({ open_orders: 'cancel'}) + allow(Spree::OrderMailer).to receive(:cancel_email) { mail_mock } + allow(mail_mock).to receive(:deliver) + end + + it 'renders the cancelled standing_order as json, and cancels the open order' do + spree_put :cancel, params + json_response = JSON.parse(response.body) + expect(json_response['canceled_at']).to_not be nil + expect(json_response['id']).to eq standing_order.id + expect(standing_order.reload.canceled_at).to be_within(5.seconds).of Time.now + expect(order.reload.state).to eq 'canceled' + expect(proxy_order.reload.canceled_at).to be_within(5.seconds).of Time.now + expect(mail_mock).to have_received(:deliver) + end + end + end + + context "when no associated orders are still 'open'" do + it 'renders the cancelled standing_order as json' do + spree_put :cancel, params + json_response = JSON.parse(response.body) + expect(json_response['canceled_at']).to_not be nil + expect(json_response['id']).to eq standing_order.id + expect(standing_order.reload.canceled_at).to be_within(5.seconds).of Time.now + end end end end @@ -460,12 +514,66 @@ describe Admin::StandingOrdersController, type: :controller do context "with authorisation" do before { shop.update_attributes(owner: user) } - it 'renders the paused standing_order as json' do - spree_put :pause, params - json_response = JSON.parse(response.body) - expect(json_response['paused_at']).to_not be nil - expect(json_response['id']).to eq standing_order.id - expect(standing_order.reload.paused_at).to be_within(5.seconds).of Time.now + context "when at least one associated order is still 'open'" do + let(:order_cycle) { standing_order.order_cycles.first } + let(:proxy_order) { create(:proxy_order, standing_order: standing_order, order_cycle: order_cycle) } + let!(:order) { proxy_order.initialise_order! } + + before { while !order.completed? do break unless order.next! end } + + context "when no 'open_orders' directive has been provided" do + it "renders an error, asking what to do" do + spree_put :pause, params + expect(response.status).to be 409 + json_response = JSON.parse(response.body) + expect(json_response['errors']['open_orders']).to eq I18n.t('admin.standing_orders.confirm_cancel_open_orders_msg') + end + end + + context "when 'keep' has been provided as the 'open_orders' directive" do + before { params.merge!({ open_orders: 'keep'}) } + + it 'renders the paused standing_order as json, and does not cancel the open order' do + spree_put :pause, params + json_response = JSON.parse(response.body) + expect(json_response['paused_at']).to_not be nil + expect(json_response['id']).to eq standing_order.id + expect(standing_order.reload.paused_at).to be_within(5.seconds).of Time.now + expect(order.reload.state).to eq 'complete' + expect(proxy_order.reload.canceled_at).to be nil + end + end + + context "when 'cancel' has been provided as the 'open_orders' directive" do + let(:mail_mock) { double(:mail) } + + before do + params.merge!({ open_orders: 'cancel'}) + allow(Spree::OrderMailer).to receive(:cancel_email) { mail_mock } + allow(mail_mock).to receive(:deliver) + end + + it 'renders the paused standing_order as json, and cancels the open order' do + spree_put :pause, params + json_response = JSON.parse(response.body) + expect(json_response['paused_at']).to_not be nil + expect(json_response['id']).to eq standing_order.id + expect(standing_order.reload.paused_at).to be_within(5.seconds).of Time.now + expect(order.reload.state).to eq 'canceled' + expect(proxy_order.reload.canceled_at).to be_within(5.seconds).of Time.now + expect(mail_mock).to have_received(:deliver) + end + end + end + + context "when no associated orders are still 'open'" do + it 'renders the paused standing_order as json' do + spree_put :pause, params + json_response = JSON.parse(response.body) + expect(json_response['paused_at']).to_not be nil + expect(json_response['id']).to eq standing_order.id + expect(standing_order.reload.paused_at).to be_within(5.seconds).of Time.now + end end end end