From efe2b724e664fc712e73dc564ab309551c9f9166 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Tue, 10 Sep 2024 17:09:12 +1000 Subject: [PATCH] Find wholesale offer for retail variant --- app/jobs/backorder_job.rb | 23 ++++++++++++++----- app/services/fdc_offer_broker.rb | 19 +++++++++++++-- ...yml => finds_a_linked_wholesale_offer.yml} | 0 spec/jobs/backorder_job_spec.rb | 3 ++- spec/services/fdc_offer_broker_spec.rb | 14 +++++------ 5 files changed, 43 insertions(+), 16 deletions(-) rename spec/fixtures/vcr_cassettes/FdcOfferBroker/_best_offer/{finds_a_linked_offer.yml => finds_a_linked_wholesale_offer.yml} (100%) diff --git a/app/jobs/backorder_job.rb b/app/jobs/backorder_job.rb index 1b73af05c6..65233c5955 100644 --- a/app/jobs/backorder_job.rb +++ b/app/jobs/backorder_job.rb @@ -36,23 +36,34 @@ class BackorderJob < ApplicationJob orderer = FdcBackorderer.new(user) backorder = orderer.find_or_build_order(order) broker = load_broker(order.distributor.owner) + ordered_quantities = {} linked_variants.each do |variant| needed_quantity = -1 * variant.on_hand - offer = broker.best_offer(variant.semantic_links[0].semantic_id) + solution = broker.best_offer(variant.semantic_links[0].semantic_id) - line = orderer.find_or_build_order_line(backorder, offer) - line.quantity = line.quantity.to_i + needed_quantity + # The number of wholesale packs we need to order to fulfill the + # needed quantity. + # For example, we order 2 packs of 12 cans if we need 15 cans. + wholesale_quantity = (needed_quantity.to_f / solution.factor).ceil + + # The number of individual retail items we get with the wholesale order. + # For example, if we order 2 packs of 12 cans, we will get 24 cans + # and we'll account for that in our stock levels. + retail_quantity = wholesale_quantity * solution.factor + + line = orderer.find_or_build_order_line(backorder, solution.offer) + line.quantity = line.quantity.to_i + wholesale_quantity + + ordered_quantities[variant] = retail_quantity end placed_order = orderer.send_order(backorder) schedule_order_completion(user, order, placed_order) if orderer.new?(backorder) - # Once we have transformations and know the quantities in bulk products - # we will need to increase on_hand by the ordered quantity. linked_variants.each do |variant| - variant.on_hand = 0 + variant.on_hand += ordered_quantities[variant] end end diff --git a/app/services/fdc_offer_broker.rb b/app/services/fdc_offer_broker.rb index 5112cef2f1..817e816dc1 100644 --- a/app/services/fdc_offer_broker.rb +++ b/app/services/fdc_offer_broker.rb @@ -2,13 +2,23 @@ # Finds wholesale offers for retail products. class FdcOfferBroker + Solution = Struct.new(:product, :factor, :offer) + def initialize(catalog) @catalog = catalog end def best_offer(product_id) - product = @catalog.find { |item| item.semanticId == product_id } - offer_of(product) + consumption_flow = catalog_item("#{product_id}/AsPlannedConsumptionFlow") + production_flow = catalog_item("#{product_id}/AsPlannedProductionFlow") + + contained_quantity = consumption_flow.quantity.value.to_i + wholesale_product_id = production_flow.product + wholesale_product = catalog_item(wholesale_product_id ) + + offer = offer_of(wholesale_product) + + Solution.new(wholesale_product, contained_quantity, offer) end def offer_of(product) @@ -17,4 +27,9 @@ class FdcOfferBroker offer.offeredItem = product end end + + def catalog_item(id) + @catalog_by_id ||= @catalog.index_by(&:semanticId) + @catalog_by_id[id] + end end diff --git a/spec/fixtures/vcr_cassettes/FdcOfferBroker/_best_offer/finds_a_linked_offer.yml b/spec/fixtures/vcr_cassettes/FdcOfferBroker/_best_offer/finds_a_linked_wholesale_offer.yml similarity index 100% rename from spec/fixtures/vcr_cassettes/FdcOfferBroker/_best_offer/finds_a_linked_offer.yml rename to spec/fixtures/vcr_cassettes/FdcOfferBroker/_best_offer/finds_a_linked_wholesale_offer.yml diff --git a/spec/jobs/backorder_job_spec.rb b/spec/jobs/backorder_job_spec.rb index 01e758ac9f..3bfc193837 100644 --- a/spec/jobs/backorder_job_spec.rb +++ b/spec/jobs/backorder_job_spec.rb @@ -35,7 +35,8 @@ RSpec.describe BackorderJob do BackorderJob.check_stock(order) }.to enqueue_job CompleteBackorderJob - expect(variant.on_hand).to eq 0 + # We ordered a case of 12 cans: -3 + 12 = 9 + expect(variant.on_hand).to eq 9 # Clean up after ourselves: perform_enqueued_jobs(only: CompleteBackorderJob) diff --git a/spec/services/fdc_offer_broker_spec.rb b/spec/services/fdc_offer_broker_spec.rb index 5939d41180..024cd9b257 100644 --- a/spec/services/fdc_offer_broker_spec.rb +++ b/spec/services/fdc_offer_broker_spec.rb @@ -11,14 +11,14 @@ RSpec.describe FdcOfferBroker do } describe ".best_offer" do - it "finds a linked offer", vcr: true do - offer = subject.best_offer(product.semanticId) + it "finds a linked wholesale offer", vcr: true do + solution = subject.best_offer(product.semanticId) - # This is the URL structure on the FDC API: - expect(offer.semanticId).to eq "#{product.semanticId}/Offer" - - # Well, if you ask the orders endpoint, you actually get different ids - # for the same offers... + # These values depend on the test data but are a good sanity check: + expect(product.name).to eq "Baked British Beans - Retail can, 400g (can)" + expect(solution.product.name).to eq "Baked British Beans - Case, 12 x 400g (can)" + expect(solution.factor).to eq 12 + expect(solution.offer.offeredItem).to eq solution.product end end end