From e068c4831bfd1fce7715a4e3484aa2f596b2ba20 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 9 Dec 2016 12:29:24 +1100 Subject: [PATCH] Adding job to send confirm emails for standing orders on order cycle close --- app/jobs/order_cycle_open_close_job.rb | 15 ++++++ app/jobs/standing_order_confirm_job.rb | 28 +++++++++++ app/models/standing_order_order.rb | 1 + spec/jobs/order_cycle_open_close_job_spec.rb | 43 +++++++++++++--- spec/jobs/standing_order_confirm_job_spec.rb | 52 ++++++++++++++++++++ 5 files changed, 132 insertions(+), 7 deletions(-) create mode 100644 app/jobs/standing_order_confirm_job.rb create mode 100644 spec/jobs/standing_order_confirm_job_spec.rb diff --git a/app/jobs/order_cycle_open_close_job.rb b/app/jobs/order_cycle_open_close_job.rb index 071d52ed44..f5a27352a6 100644 --- a/app/jobs/order_cycle_open_close_job.rb +++ b/app/jobs/order_cycle_open_close_job.rb @@ -5,6 +5,12 @@ class OrderCycleOpenCloseJob order_cycles.each do |order_cycle| Delayed::Job.enqueue(StandingOrderPlacementJob.new(order_cycle)) end + + order_cycles = recently_closed_order_cycles.all + recently_closed_order_cycles.update_all(standing_orders_confirmed_at: Time.now) + order_cycles.each do |order_cycle| + Delayed::Job.enqueue(StandingOrderConfirmJob.new(order_cycle)) + end end private @@ -17,4 +23,13 @@ class OrderCycleOpenCloseJob 10.minutes.ago, Time.now, 10.minutes.ago, Time.now ) end + + def recently_closed_order_cycles + return @recently_closed_order_cycles unless @recently_closed_order_cycles.nil? + @recently_closed_order_cycles = + OrderCycle.closed.where( + 'standing_orders_confirmed_at IS NULL AND (orders_close_at BETWEEN (?) AND (?) OR updated_at BETWEEN (?) AND (?))', + 10.minutes.ago, Time.now, 10.minutes.ago, Time.now + ) + end end diff --git a/app/jobs/standing_order_confirm_job.rb b/app/jobs/standing_order_confirm_job.rb new file mode 100644 index 0000000000..8a326647fd --- /dev/null +++ b/app/jobs/standing_order_confirm_job.rb @@ -0,0 +1,28 @@ +class StandingOrderConfirmJob + attr_accessor :order_cycle + + def initialize(order_cycle) + @order_cycle = order_cycle + end + + def perform + orders.each do |order| + process(order) + end + end + + private + + def orders + Spree::Order.complete.where(order_cycle_id: order_cycle) + .merge(StandingOrderOrder.not_canceled).joins(:standing_order_order).readonly(false) + end + + def process(order) + send_confirm_email(order) + end + + def send_confirm_email(order) + Spree::OrderMailer.standing_order_email(order.id, 'confirmation', {}).deliver + end +end diff --git a/app/models/standing_order_order.rb b/app/models/standing_order_order.rb index 34ed02c0db..2fb2545ed0 100644 --- a/app/models/standing_order_order.rb +++ b/app/models/standing_order_order.rb @@ -6,6 +6,7 @@ class StandingOrderOrder < ActiveRecord::Base scope :closed, -> { joins(order: :order_cycle).merge(OrderCycle.closed) } scope :not_closed, -> { joins(order: :order_cycle).merge(OrderCycle.not_closed) } + scope :not_canceled, where('standing_order_orders.canceled_at IS NULL') def state return 'canceled' if canceled? diff --git a/spec/jobs/order_cycle_open_close_job_spec.rb b/spec/jobs/order_cycle_open_close_job_spec.rb index 8297571c8e..10681c3d85 100644 --- a/spec/jobs/order_cycle_open_close_job_spec.rb +++ b/spec/jobs/order_cycle_open_close_job_spec.rb @@ -17,16 +17,45 @@ describe OrderCycleOpenCloseJob do end end - describe "running the job" do - let!(:order_cycle) { create(:simple_order_cycle, orders_open_at: 5.minutes.ago) } + describe "finding recently closed order cycles" do + let!(:order_cycle1) { create(:simple_order_cycle, orders_close_at: 11.minutes.ago, updated_at: 11.minutes.ago) } + let!(:order_cycle2) { create(:simple_order_cycle, orders_close_at: 11.minutes.ago, updated_at: 9.minutes.ago) } + let!(:order_cycle3) { create(:simple_order_cycle, orders_close_at: 9.minutes.ago, updated_at: 9.minutes.ago) } + let!(:order_cycle4) { create(:simple_order_cycle, orders_close_at: 2.minutes.ago, standing_orders_confirmed_at: 1.minute.ago ) } + let!(:order_cycle5) { create(:simple_order_cycle, orders_close_at: 1.minute.from_now) } - it "marks the order cycle as processed by setting standing_orders_placed_at" do - expect{job.perform}.to change{order_cycle.reload.standing_orders_placed_at} - expect(order_cycle.standing_orders_placed_at).to be_within(5.seconds).of Time.now + it "returns unprocessed order cycles whose orders_close_at or updated_at date is within the past 10 minutes" do + order_cycles = job.send(:recently_closed_order_cycles) + expect(order_cycles).to include order_cycle2, order_cycle3 + expect(order_cycles).to_not include order_cycle1, order_cycle4, order_cycle5 + end + end + + describe "running the job" do + context "when an order cycle has just opened" do + let!(:order_cycle) { create(:simple_order_cycle, orders_open_at: 5.minutes.ago) } + + it "marks the order cycle as processed by setting standing_orders_placed_at" do + expect{job.perform}.to change{order_cycle.reload.standing_orders_placed_at} + expect(order_cycle.standing_orders_placed_at).to be_within(5.seconds).of Time.now + end + + it "enqueues a StandingOrderPlacementJob for each recently opened order_cycle" do + expect{job.perform}.to enqueue_job StandingOrderPlacementJob + end end - it "enqueues a StandingOrderPlacementJob for each recently opened order_cycle" do - expect{job.perform}.to enqueue_job StandingOrderPlacementJob + context "when an order cycle has just closed" do + let!(:order_cycle) { create(:simple_order_cycle, orders_close_at: 5.minutes.ago) } + + it "marks the order cycle as processed by setting standing_orders_placed_at" do + expect{job.perform}.to change{order_cycle.reload.standing_orders_confirmed_at} + expect(order_cycle.standing_orders_confirmed_at).to be_within(5.seconds).of Time.now + end + + it "enqueues a StandingOrderPlacementJob for each recently opened order_cycle" do + expect{job.perform}.to enqueue_job StandingOrderConfirmJob + end end end end diff --git a/spec/jobs/standing_order_confirm_job_spec.rb b/spec/jobs/standing_order_confirm_job_spec.rb new file mode 100644 index 0000000000..f0e889cf70 --- /dev/null +++ b/spec/jobs/standing_order_confirm_job_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +describe StandingOrderConfirmJob do + let(:shop) { create(:distributor_enterprise) } + let(:order_cycle1) { create(:simple_order_cycle, coordinator: shop) } + let(:order_cycle2) { create(:simple_order_cycle, coordinator: shop) } + let(:schedule1) { create(:schedule, order_cycles: [order_cycle1, order_cycle2]) } + let(:standing_order1) { create(:standing_order_with_items, shop: shop, schedule: schedule1) } + let!(:job) { StandingOrderConfirmJob.new(order_cycle1) } + + describe "finding standing_order orders for the specified order cycle" do + let(:order1) { create(:order, order_cycle: order_cycle1) } # Incomplete + Linked + OC Matches + let(:order2) { create(:order, order_cycle: order_cycle1, completed_at: 5.minutes.ago) } # Complete + Not-Linked + OC Matches + let(:order3) { create(:order, order_cycle: order_cycle2, completed_at: 5.minutes.ago) } # Complete + Linked + OC Mismatch + let(:order4) { create(:order, order_cycle: order_cycle1, completed_at: 5.minutes.ago) } # Complete + Linked + OC Matches + Cancelled + let(:order5) { create(:order, order_cycle: order_cycle1, completed_at: 5.minutes.ago) } # Complete + Linked + OC Matches + let(:cancelled_standing_order_order) { standing_order1.standing_order_orders.find_by_order_id(order4.id) } + + before do + standing_order1.orders = [order1, order3, order4, order5] + cancelled_standing_order_order.update_attribute(:canceled_at, 1.minute.ago) + end + + it "only returns incomplete orders in the relevant order cycle that are linked to a standing order" do + orders = job.send(:orders) + expect(orders).to include order5 + expect(orders).to_not include order1, order2, order3, order4 + end + end + + describe "processing a standing order order" do + let(:order) { standing_order1.orders.first } + + before do + form = StandingOrderForm.new(standing_order1) + form.send(:initialise_orders!) + while !standing_order1.orders.first.completed? do break unless order.next! end + allow(job).to receive(:send_confirm_email).and_call_original + Spree::MailMethod.create!( + environment: Rails.env, + preferred_mails_from: 'spree@example.com' + ) + end + + it "sends only a standing order confirm email, no regular confirmation emails" do + ActionMailer::Base.deliveries.clear + expect{job.send(:process, order)}.to_not enqueue_job ConfirmOrderJob + expect(job).to have_received(:send_confirm_email).with(order).once + expect(ActionMailer::Base.deliveries.count).to be 1 + end + end +end