diff --git a/app/controllers/checkout_controller.rb b/app/controllers/checkout_controller.rb index b41eeb52658..15cc8ef2f7d 100644 --- a/app/controllers/checkout_controller.rb +++ b/app/controllers/checkout_controller.rb @@ -92,6 +92,7 @@ def confirm_order end @order.process_payments! @order.confirm! + BackorderJob.check_stock(@order) order_completion_reset @order end diff --git a/app/jobs/amend_backorder_job.rb b/app/jobs/amend_backorder_job.rb index b4d2bf0506e..883bed53284 100644 --- a/app/jobs/amend_backorder_job.rb +++ b/app/jobs/amend_backorder_job.rb @@ -23,8 +23,16 @@ def perform(order) def amend_backorder(order) backorder = BackorderUpdater.new.amend_backorder(order) - user = order.distributor.owner - urls = nil # Not needed to send order. The backorder id is the URL. - FdcBackorderer.new(user, urls).send_order(backorder) if backorder + if backorder + user = order.distributor.owner + urls = nil # Not needed to send order. The backorder id is the URL. + FdcBackorderer.new(user, urls).send_order(backorder) + elsif !order.order_cycle.closed? + + # We don't have an order to amend but the order cycle is or will open. + # We can assume that this job was triggered by an admin creating a new + # order or adding backorderable items to an order. + BackorderJob.new.place_backorder(order) + end end end diff --git a/app/models/spree/order.rb b/app/models/spree/order.rb index 81bee889d3d..10375b421dd 100644 --- a/app/models/spree/order.rb +++ b/app/models/spree/order.rb @@ -392,8 +392,6 @@ def finalize! deliver_order_confirmation_email - BackorderJob.check_stock(self) - state_changes.create( previous_state: 'cart', next_state: 'complete', diff --git a/app/services/backorder_updater.rb b/app/services/backorder_updater.rb index bb163c8be0a..10718296d06 100644 --- a/app/services/backorder_updater.rb +++ b/app/services/backorder_updater.rb @@ -28,7 +28,7 @@ def amend_backorder(order) backorder = orderer.find_open_order(order) - update(backorder, user, distributor, order_cycle) + update(backorder, user, distributor, order_cycle) if backorder end # Update a given backorder according to a distributor's order cycle. @@ -58,6 +58,9 @@ def update_order_lines(backorder, order_cycle, variants, broker, orderer) variants.map do |variant| link = variant.semantic_links[0].semantic_id solution = broker.best_offer(link) + + next unless solution.offer + line = orderer.find_or_build_order_line(backorder, solution.offer) if variant.on_demand adjust_stock(variant, solution, line) @@ -66,7 +69,7 @@ def update_order_lines(backorder, order_cycle, variants, broker, orderer) end line - end + end.compact end def cancel_stale_lines(unprocessed_lines, managed_variants, broker) diff --git a/spec/jobs/amend_backorder_job_spec.rb b/spec/jobs/amend_backorder_job_spec.rb index bde82b0741b..3216b109da5 100644 --- a/spec/jobs/amend_backorder_job_spec.rb +++ b/spec/jobs/amend_backorder_job_spec.rb @@ -130,5 +130,32 @@ .to change { backorder.lines.count }.from(2).to(1) .and change { beans.reload.on_hand }.by(-12) end + + it "creates a new order" do + stub_request(:get, catalog_url).to_return(body: catalog_json) + + # Record the placed backorder: + backorder = nil + allow_any_instance_of(FdcBackorderer).to receive(:find_order) do |*_args| + backorder + end + allow_any_instance_of(FdcBackorderer).to receive(:send_order) do |*args| + backorder = args[1] + end + + # Call amending before a backorder has been placed. + expect { subject.amend_backorder(order) } + .to change { backorder.present? } + .to(true) + + # We ordered a case of 12 cans: -3 + 12 = 9 + expect(beans.on_hand).to eq 9 + + # Stock controlled items don't change stock in backorder: + expect(chia_seed.on_hand).to eq 7 + + expect(backorder.lines[0].quantity).to eq 1 # beans + expect(backorder.lines[1].quantity).to eq 5 # chia + end end end diff --git a/spec/services/backorder_updater_spec.rb b/spec/services/backorder_updater_spec.rb index 48aa125fdc2..af1f5471c13 100644 --- a/spec/services/backorder_updater_spec.rb +++ b/spec/services/backorder_updater_spec.rb @@ -4,6 +4,7 @@ RSpec.describe BackorderUpdater do let(:order) { create(:completed_order_with_totals) } + let(:order_cycle) { order.order_cycle } let(:distributor) { order.distributor } let(:beans) { beans_item.variant } let(:beans_item) { order.line_items[0] } @@ -107,11 +108,51 @@ .to change { backorder.lines.count }.from(2).to(1) .and change { beans.reload.on_hand }.by(-12) end + + it "skips updating if there's is no backorder" do + allow_any_instance_of(FdcBackorderer).to receive(:find_open_order) + .and_return(nil) + + expect { subject.amend_backorder(order) }.not_to raise_error + end end - describe "#distributed_linked_variants" do - let(:order_cycle) { order.order_cycle } + describe "#update_order_lines" do + it "skips unavailable items" do + stub_request(:get, catalog_url).to_return(body: catalog_json) + + # Record the placed backorder: + backorder = nil + allow_any_instance_of(FdcBackorderer).to receive(:find_order) do |*_args| + backorder + end + allow_any_instance_of(FdcBackorderer).to receive(:send_order) do |*args| + backorder = args[1] + end + + BackorderJob.new.place_backorder(order) + # Now one of the products becomes unavailable in the catalog. + # I simulate that by changing the link so something unknown. + beans.semantic_links[0].update!(semantic_id: "https://example.net/unknown") + + variants = [beans, chia_seed] + reference_link = chia_seed.semantic_links[0].semantic_id + urls = FdcUrlBuilder.new(reference_link) + catalog = DfcCatalog.load(user, urls.catalog_url) + orderer = FdcBackorderer.new(user, urls) + broker = FdcOfferBroker.new(catalog) + updated_lines = subject.update_order_lines( + backorder, order_cycle, variants, broker, orderer + ) + + expect(updated_lines.count).to eq 1 + expect(updated_lines[0].offer.offeredItem.semanticId) + .to eq chia_seed.semantic_links[0].semantic_id + end + end + + describe "#distributed_linked_variants" do it "selects available variants with semantic links" do variants = subject.distributed_linked_variants(order_cycle, distributor) expect(variants).to match_array [beans, chia_seed] diff --git a/spec/system/consumer/checkout/backorder_spec.rb b/spec/system/consumer/checkout/backorder_spec.rb new file mode 100644 index 00000000000..930e885a565 --- /dev/null +++ b/spec/system/consumer/checkout/backorder_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require "system_helper" + +RSpec.describe "Checkout" do + include ShopWorkflow + include CheckoutHelper + + let(:variant) { order.variants.first } + let(:order) { create(:order_ready_for_confirmation) } + + before do + variant.semantic_links << SemanticLink.new(semantic_id: "https://product") + set_order order + login_as create(:user) + end + + it "triggers a backorder" do + visit checkout_step_path(:summary) + + expect { place_order }.to enqueue_job BackorderJob + end +end