From ef08ae49fe74e2c525fde31b9df032708ade35ff Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 18 Dec 2024 15:34:42 +1100 Subject: [PATCH 1/5] DRY DFC catalog logic for re-use --- .../admin/dfc_product_imports_controller.rb | 5 ++-- app/jobs/stock_sync_job.rb | 4 +--- .../dfc_provider/app/services/dfc_catalog.rb | 13 ++++++++++ .../spec/services/dfc_catalog_spec.rb | 24 +++++++++++++++++++ 4 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 engines/dfc_provider/app/services/dfc_catalog.rb create mode 100644 engines/dfc_provider/spec/services/dfc_catalog_spec.rb diff --git a/app/controllers/admin/dfc_product_imports_controller.rb b/app/controllers/admin/dfc_product_imports_controller.rb index ab3b819c4f2..c17a428c75a 100644 --- a/app/controllers/admin/dfc_product_imports_controller.rb +++ b/app/controllers/admin/dfc_product_imports_controller.rb @@ -20,12 +20,11 @@ def index catalog_url = params.require(:catalog_url) broker = FdcOfferBroker.new(spree_current_user, catalog_url) + catalog = DfcCatalog.new(broker.catalog) # * First step: import all products for given enterprise. # * Second step: render table and let user decide which ones to import. - imported = broker.catalog.map do |subject| - next unless subject.is_a? DataFoodConsortium::Connector::SuppliedProduct - + imported = catalog.products.map do |subject| adjust_to_wholesale_price(broker, subject) existing_variant = enterprise.supplied_variants.linked_to(subject.semanticId) diff --git a/app/jobs/stock_sync_job.rb b/app/jobs/stock_sync_job.rb index d4df9c3c613..1572731e0ce 100644 --- a/app/jobs/stock_sync_job.rb +++ b/app/jobs/stock_sync_job.rb @@ -62,9 +62,7 @@ def load_products(user, catalog_id) json_catalog = DfcRequest.new(user).call(catalog_id) graph = DfcIo.import(json_catalog) - graph.select do |subject| - subject.is_a? DataFoodConsortium::Connector::SuppliedProduct - end + DfcCatalog.new(graph).products end def linked_variants(enterprises, product_ids) diff --git a/engines/dfc_provider/app/services/dfc_catalog.rb b/engines/dfc_provider/app/services/dfc_catalog.rb new file mode 100644 index 00000000000..ace46a51d06 --- /dev/null +++ b/engines/dfc_provider/app/services/dfc_catalog.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class DfcCatalog + def initialize(graph) + @graph = graph + end + + def products + @products ||= @graph.select do |subject| + subject.is_a? DataFoodConsortium::Connector::SuppliedProduct + end + end +end diff --git a/engines/dfc_provider/spec/services/dfc_catalog_spec.rb b/engines/dfc_provider/spec/services/dfc_catalog_spec.rb new file mode 100644 index 00000000000..c050e9f5d7d --- /dev/null +++ b/engines/dfc_provider/spec/services/dfc_catalog_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require_relative "../spec_helper" + +RSpec.describe DfcCatalog do + subject(:catalog) { DfcCatalog.new(fdc_catalog_graph) } + let(:fdc_catalog_graph) { + VCR.use_cassette(:fdc_catalog) { broker.catalog } + } + let(:broker) { FdcOfferBroker.new(user, catalog_url) } + let(:user) { build(:testdfc_user) } + let(:catalog_url) { + "https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts" + } + + describe "#products" do + let(:products) { catalog.products } + + it "returns only products" do + expect(products.count).to eq 4 + expect(products.map(&:semanticType).uniq).to eq ["dfc-b:SuppliedProduct"] + end + end +end From ddaeff7c53bae0c3bb116fbdeafbd53b061f0c45 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 18 Dec 2024 16:04:27 +1100 Subject: [PATCH 2/5] Use DfcCatalog in offer broker --- .../admin/dfc_product_imports_controller.rb | 4 +-- app/jobs/backorder_job.rb | 3 +- app/services/backorder_updater.rb | 3 +- app/services/fdc_offer_broker.rb | 32 +++++-------------- .../dfc_provider/app/services/dfc_catalog.rb | 17 ++++++++++ .../spec/services/dfc_catalog_spec.rb | 8 ++--- spec/jobs/complete_backorder_job_spec.rb | 5 +-- spec/services/fdc_backorderer_spec.rb | 13 ++++---- spec/services/fdc_offer_broker_spec.rb | 8 +++-- 9 files changed, 49 insertions(+), 44 deletions(-) diff --git a/app/controllers/admin/dfc_product_imports_controller.rb b/app/controllers/admin/dfc_product_imports_controller.rb index c17a428c75a..0178d5518e8 100644 --- a/app/controllers/admin/dfc_product_imports_controller.rb +++ b/app/controllers/admin/dfc_product_imports_controller.rb @@ -19,8 +19,8 @@ def index .find(params.require(:enterprise_id)) catalog_url = params.require(:catalog_url) - broker = FdcOfferBroker.new(spree_current_user, catalog_url) - catalog = DfcCatalog.new(broker.catalog) + catalog = DfcCatalog.load(spree_current_user, catalog_url) + broker = FdcOfferBroker.new(catalog) # * First step: import all products for given enterprise. # * Second step: render table and let user decide which ones to import. diff --git a/app/jobs/backorder_job.rb b/app/jobs/backorder_job.rb index c2a0b455272..a0a3c168e50 100644 --- a/app/jobs/backorder_job.rb +++ b/app/jobs/backorder_job.rb @@ -117,7 +117,8 @@ def needed_quantity(line_item) end def load_broker(user, urls) - FdcOfferBroker.new(user, urls.catalog_url) + catalog = DfcCatalog.load(user, urls.catalog_url) + FdcOfferBroker.new(catalog) end def place_order(user, order, orderer, backorder) diff --git a/app/services/backorder_updater.rb b/app/services/backorder_updater.rb index e57e59ff6c3..bb163c8be0a 100644 --- a/app/services/backorder_updater.rb +++ b/app/services/backorder_updater.rb @@ -40,7 +40,8 @@ def update(backorder, user, distributor, order_cycle) reference_link = variants[0].semantic_links[0].semantic_id urls = FdcUrlBuilder.new(reference_link) orderer = FdcBackorderer.new(user, urls) - broker = FdcOfferBroker.new(user, urls.catalog_url) + catalog = DfcCatalog.load(user, urls.catalog_url) + broker = FdcOfferBroker.new(catalog) updated_lines = update_order_lines(backorder, order_cycle, variants, broker, orderer) unprocessed_lines = backorder.lines.to_set - updated_lines diff --git a/app/services/fdc_offer_broker.rb b/app/services/fdc_offer_broker.rb index c4108ee26c5..5071028eaca 100644 --- a/app/services/fdc_offer_broker.rb +++ b/app/services/fdc_offer_broker.rb @@ -6,19 +6,10 @@ class FdcOfferBroker Solution = Struct.new(:product, :factor, :offer) RetailSolution = Struct.new(:retail_product_id, :factor) - def self.load_catalog(user, catalog_url) - api = DfcRequest.new(user) - catalog_json = api.call(catalog_url) - DfcIo.import(catalog_json) - end - - def initialize(user, catalog_url) - @user = user - @catalog_url = catalog_url - end + attr_reader :catalog - def catalog - @catalog ||= self.class.load_catalog(@user, @catalog_url) + def initialize(catalog) + @catalog = catalog end def best_offer(product_id) @@ -30,18 +21,18 @@ def best_offer(product_id) end def wholesale_product(product_id) - production_flow = catalog_item("#{product_id}/AsPlannedProductionFlow") + production_flow = catalog.item("#{product_id}/AsPlannedProductionFlow") if production_flow production_flow.product else # We didn't find a wholesale variant, falling back to the given product. - catalog_item(product_id) + catalog.item(product_id) end end def contained_quantity(product_id) - consumption_flow = catalog_item("#{product_id}/AsPlannedConsumptionFlow") + consumption_flow = catalog.item("#{product_id}/AsPlannedConsumptionFlow") # If we don't find a transformation, we return the original product, # which contains exactly one of itself (identity). @@ -53,7 +44,7 @@ def wholesale_to_retail(wholesale_product_id) return RetailSolution.new(wholesale_product_id, 1) if production_flow.nil? - consumption_flow = catalog_item( + consumption_flow = catalog.item( production_flow.semanticId.sub("AsPlannedProductionFlow", "AsPlannedConsumptionFlow") ) retail_product_id = consumption_flow.product.semanticId @@ -70,19 +61,12 @@ def offer_of(product) end end - def catalog_item(id) - @catalog_by_id ||= catalog.index_by(&:semanticId) - @catalog_by_id[id] - end - def flow_producing(wholesale_product_id) @production_flows_by_product_id ||= production_flows.index_by { |flow| flow.product.semanticId } @production_flows_by_product_id[wholesale_product_id] end def production_flows - @production_flows ||= catalog.select do |i| - i.semanticType == "dfc-b:AsPlannedProductionFlow" - end + @production_flows ||= catalog.select_type("dfc-b:AsPlannedProductionFlow") end end diff --git a/engines/dfc_provider/app/services/dfc_catalog.rb b/engines/dfc_provider/app/services/dfc_catalog.rb index ace46a51d06..42f0df63cf5 100644 --- a/engines/dfc_provider/app/services/dfc_catalog.rb +++ b/engines/dfc_provider/app/services/dfc_catalog.rb @@ -1,6 +1,14 @@ # frozen_string_literal: true class DfcCatalog + def self.load(user, catalog_url) + api = DfcRequest.new(user) + catalog_json = api.call(catalog_url) + graph = DfcIo.import(catalog_json) + + new(graph) + end + def initialize(graph) @graph = graph end @@ -10,4 +18,13 @@ def products subject.is_a? DataFoodConsortium::Connector::SuppliedProduct end end + + def item(semantic_id) + @items ||= @graph.index_by(&:semanticId) + @items[semantic_id] + end + + def select_type(semantic_type) + @graph.select { |i| i.semanticType == semantic_type } + end end diff --git a/engines/dfc_provider/spec/services/dfc_catalog_spec.rb b/engines/dfc_provider/spec/services/dfc_catalog_spec.rb index c050e9f5d7d..35b4a1082d1 100644 --- a/engines/dfc_provider/spec/services/dfc_catalog_spec.rb +++ b/engines/dfc_provider/spec/services/dfc_catalog_spec.rb @@ -3,11 +3,11 @@ require_relative "../spec_helper" RSpec.describe DfcCatalog do - subject(:catalog) { DfcCatalog.new(fdc_catalog_graph) } - let(:fdc_catalog_graph) { - VCR.use_cassette(:fdc_catalog) { broker.catalog } + subject(:catalog) { + VCR.use_cassette(:fdc_catalog) { + DfcCatalog.load(user, catalog_url) + } } - let(:broker) { FdcOfferBroker.new(user, catalog_url) } let(:user) { build(:testdfc_user) } let(:catalog_url) { "https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts" diff --git a/spec/jobs/complete_backorder_job_spec.rb b/spec/jobs/complete_backorder_job_spec.rb index ad6479079f9..0bc1a741e36 100644 --- a/spec/jobs/complete_backorder_job_spec.rb +++ b/spec/jobs/complete_backorder_job_spec.rb @@ -14,13 +14,14 @@ let(:orderer) { FdcBackorderer.new(user, urls) } let(:order) { backorder = orderer.find_or_build_order(ofn_order) - broker = FdcOfferBroker.new(user, urls.catalog_url) + catalog = DfcCatalog.load(user, urls.catalog_url) + broker = FdcOfferBroker.new(catalog) bean_offer = broker.best_offer(product_link).offer bean_line = orderer.find_or_build_order_line(backorder, bean_offer) bean_line.quantity = 3 - chia = broker.catalog_item(chia_seed_retail_link) + chia = catalog.item(chia_seed_retail_link) chia_offer = broker.offer_of(chia) chia_line = orderer.find_or_build_order_line(backorder, chia_offer) chia_line.quantity = 5 diff --git a/spec/services/fdc_backorderer_spec.rb b/spec/services/fdc_backorderer_spec.rb index e784203f274..b2aea87d147 100644 --- a/spec/services/fdc_backorderer_spec.rb +++ b/spec/services/fdc_backorderer_spec.rb @@ -31,9 +31,9 @@ expect(backorder.lines).to eq [] # Add items and place the new order: - catalog = FdcOfferBroker.load_catalog(order.distributor.owner, urls.catalog_url) - product = catalog.find { |i| i.semanticType == "dfc-b:SuppliedProduct" } - offer = FdcOfferBroker.new(nil, nil).offer_of(product) + catalog = DfcCatalog.load(order.distributor.owner, urls.catalog_url) + product = catalog.products.first + offer = FdcOfferBroker.new(nil).offer_of(product) line = subject.find_or_build_order_line(backorder, offer) line.quantity = 3 placed_order = subject.send_order(backorder) @@ -74,15 +74,14 @@ describe "#find_or_build_order_line" do it "add quantity to an existing line item", vcr: true do - catalog = FdcOfferBroker.load_catalog(order.distributor.owner, urls.catalog_url) + catalog = DfcCatalog.load(order.distributor.owner, urls.catalog_url) backorder = subject.find_or_build_order(order) expect(backorder.lines.count).to eq 0 # Add new item to the new order: - catalog = FdcOfferBroker.load_catalog(order.distributor.owner, urls.catalog_url) - product = catalog.find { |i| i.semanticType == "dfc-b:SuppliedProduct" } - offer = FdcOfferBroker.new(nil, nil).offer_of(product) + product = catalog.products.first + offer = FdcOfferBroker.new(nil).offer_of(product) line = subject.find_or_build_order_line(backorder, offer) expect(backorder.lines.count).to eq 1 diff --git a/spec/services/fdc_offer_broker_spec.rb b/spec/services/fdc_offer_broker_spec.rb index 403f3a84cfa..6987e0cec5c 100644 --- a/spec/services/fdc_offer_broker_spec.rb +++ b/spec/services/fdc_offer_broker_spec.rb @@ -3,9 +3,11 @@ require 'spec_helper' RSpec.describe FdcOfferBroker do - subject { FdcOfferBroker.new(user, catalog_url) } + subject { FdcOfferBroker.new(catalog) } let(:catalog) { - VCR.use_cassette(:fdc_catalog) { subject.catalog } + VCR.use_cassette(:fdc_catalog) { + DfcCatalog.load(user, catalog_url) + } } let(:catalog_url) { "https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts" @@ -15,7 +17,7 @@ } let(:user) { build(:testdfc_user) } let(:product) { - catalog.find { |item| item.semanticType == "dfc-b:SuppliedProduct" } + catalog.products.first } describe ".best_offer" do From faad7fa95cad37f716f2d37d799e1b5ae183a419 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 18 Dec 2024 16:42:38 +1100 Subject: [PATCH 3/5] Move wholesale calculation for re-use --- .../admin/dfc_product_imports_controller.rb | 23 +--------------- .../dfc_provider/app/services/dfc_catalog.rb | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/app/controllers/admin/dfc_product_imports_controller.rb b/app/controllers/admin/dfc_product_imports_controller.rb index 0178d5518e8..58239786241 100644 --- a/app/controllers/admin/dfc_product_imports_controller.rb +++ b/app/controllers/admin/dfc_product_imports_controller.rb @@ -20,13 +20,11 @@ def index catalog_url = params.require(:catalog_url) catalog = DfcCatalog.load(spree_current_user, catalog_url) - broker = FdcOfferBroker.new(catalog) + catalog.apply_wholesale_values! # * First step: import all products for given enterprise. # * Second step: render table and let user decide which ones to import. imported = catalog.products.map do |subject| - adjust_to_wholesale_price(broker, subject) - existing_variant = enterprise.supplied_variants.linked_to(subject.semanticId) if existing_variant @@ -43,24 +41,5 @@ def index flash[:error] = e.message redirect_to admin_product_import_path end - - private - - def adjust_to_wholesale_price(broker, product) - transformation = broker.best_offer(product.semanticId) - - return if transformation.factor == 1 - - wholesale_variant_price = transformation.offer.price - - return unless wholesale_variant_price - - offer = product.catalogItems&.first&.offers&.first - - return unless offer - - offer.price = wholesale_variant_price.dup - offer.price.value = offer.price.value.to_f / transformation.factor - end end end diff --git a/engines/dfc_provider/app/services/dfc_catalog.rb b/engines/dfc_provider/app/services/dfc_catalog.rb index 42f0df63cf5..7e9fed7009b 100644 --- a/engines/dfc_provider/app/services/dfc_catalog.rb +++ b/engines/dfc_provider/app/services/dfc_catalog.rb @@ -27,4 +27,30 @@ def item(semantic_id) def select_type(semantic_type) @graph.select { |i| i.semanticType == semantic_type } end + + def apply_wholesale_values! + broker = FdcOfferBroker.new(self) + products.each do |product| + adjust_to_wholesale_price(broker, product) + end + end + + private + + def adjust_to_wholesale_price(broker, product) + transformation = broker.best_offer(product.semanticId) + + return if transformation.factor == 1 + + wholesale_variant_price = transformation.offer.price + + return unless wholesale_variant_price + + offer = product.catalogItems&.first&.offers&.first + + return unless offer + + offer.price = wholesale_variant_price.dup + offer.price.value = offer.price.value.to_f / transformation.factor + end end From 21195c575028580cd088079941bd626b223fef87 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 18 Dec 2024 17:04:55 +1100 Subject: [PATCH 4/5] Calculate stock from wholesale products --- app/jobs/stock_sync_job.rb | 12 +++---- .../dfc_provider/app/services/dfc_catalog.rb | 36 +++++++++++++++---- .../spec/services/dfc_catalog_spec.rb | 21 +++++++++++ 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/app/jobs/stock_sync_job.rb b/app/jobs/stock_sync_job.rb index 1572731e0ce..565d6425d4a 100644 --- a/app/jobs/stock_sync_job.rb +++ b/app/jobs/stock_sync_job.rb @@ -40,7 +40,10 @@ def self.catalog_ids(order) end def perform(user, catalog_id) - products = load_products(user, catalog_id) + catalog = DfcCatalog.load(user, catalog_id) + catalog.apply_wholesale_values! + + products = catalog.products products_by_id = products.index_by(&:semanticId) product_ids = products_by_id.keys variants = linked_variants(user.enterprises, product_ids) @@ -58,13 +61,6 @@ def perform(user, catalog_id) end end - def load_products(user, catalog_id) - json_catalog = DfcRequest.new(user).call(catalog_id) - graph = DfcIo.import(json_catalog) - - DfcCatalog.new(graph).products - end - def linked_variants(enterprises, product_ids) Spree::Variant.where(supplier: enterprises) .includes(:semantic_links).references(:semantic_links) diff --git a/engines/dfc_provider/app/services/dfc_catalog.rb b/engines/dfc_provider/app/services/dfc_catalog.rb index 7e9fed7009b..f068bdc3cf8 100644 --- a/engines/dfc_provider/app/services/dfc_catalog.rb +++ b/engines/dfc_provider/app/services/dfc_catalog.rb @@ -31,17 +31,18 @@ def select_type(semantic_type) def apply_wholesale_values! broker = FdcOfferBroker.new(self) products.each do |product| - adjust_to_wholesale_price(broker, product) + transformation = broker.best_offer(product.semanticId) + + next if transformation.factor == 1 + + adjust_to_wholesale_price(product, transformation) + adjust_to_wholesale_stock(product, transformation) end end private - def adjust_to_wholesale_price(broker, product) - transformation = broker.best_offer(product.semanticId) - - return if transformation.factor == 1 - + def adjust_to_wholesale_price(product, transformation) wholesale_variant_price = transformation.offer.price return unless wholesale_variant_price @@ -53,4 +54,27 @@ def adjust_to_wholesale_price(broker, product) offer.price = wholesale_variant_price.dup offer.price.value = offer.price.value.to_f / transformation.factor end + + def adjust_to_wholesale_stock(product, transformation) + adjust_item_stock(product, transformation) + adjust_offer_stock(product, transformation) + end + + def adjust_item_stock(product, transformation) + item = product.catalogItems&.first + wholesale_item = transformation.product.catalogItems&.first + + return unless item && wholesale_item&.stockLimitation.present? + + item.stockLimitation = wholesale_item.stockLimitation.to_i * transformation.factor + end + + def adjust_offer_stock(product, transformation) + offer = product.catalogItems&.first&.offers&.first + wholesale_offer = transformation.offer + + return unless offer && wholesale_offer&.stockLimitation.present? + + offer.stockLimiation = wholesale_offer.stockLimitation.to_i * transformation.factor + end end diff --git a/engines/dfc_provider/spec/services/dfc_catalog_spec.rb b/engines/dfc_provider/spec/services/dfc_catalog_spec.rb index 35b4a1082d1..15e59f1d2ec 100644 --- a/engines/dfc_provider/spec/services/dfc_catalog_spec.rb +++ b/engines/dfc_provider/spec/services/dfc_catalog_spec.rb @@ -21,4 +21,25 @@ expect(products.map(&:semanticType).uniq).to eq ["dfc-b:SuppliedProduct"] end end + + describe "#apply_wholesale_values!" do + let(:offer) { beans.catalogItems.first.offers.first } + let(:catalog_item) { beans.catalogItems.first } + let(:beans) { catalog.item(beans_id) } + let(:beans_id) { + "https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts/44519466467635" + } + + it "changes price of retail variants" do + expect { catalog.apply_wholesale_values! }.to change { + offer.price.value.to_f.round(2) + }.from(2.09).to(1.57) # 18.85 wholesale price divided by 12 + end + + it "changes stock level of retail variants" do + expect { catalog.apply_wholesale_values! }.to change { + catalog_item.stockLimitation + }.from("-1").to(-12) + end + end end From 0bd6fe670995fc83b4073bcb988625581bda52ac Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 2 Jan 2025 16:48:22 +1100 Subject: [PATCH 5/5] Fix setting stock from wholesale offer --- engines/dfc_provider/app/services/dfc_catalog.rb | 2 +- .../dfc_provider/spec/services/dfc_catalog_spec.rb | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/engines/dfc_provider/app/services/dfc_catalog.rb b/engines/dfc_provider/app/services/dfc_catalog.rb index f068bdc3cf8..08649494c7e 100644 --- a/engines/dfc_provider/app/services/dfc_catalog.rb +++ b/engines/dfc_provider/app/services/dfc_catalog.rb @@ -75,6 +75,6 @@ def adjust_offer_stock(product, transformation) return unless offer && wholesale_offer&.stockLimitation.present? - offer.stockLimiation = wholesale_offer.stockLimitation.to_i * transformation.factor + offer.stockLimitation = wholesale_offer.stockLimitation.to_i * transformation.factor end end diff --git a/engines/dfc_provider/spec/services/dfc_catalog_spec.rb b/engines/dfc_provider/spec/services/dfc_catalog_spec.rb index 15e59f1d2ec..282f345fe71 100644 --- a/engines/dfc_provider/spec/services/dfc_catalog_spec.rb +++ b/engines/dfc_provider/spec/services/dfc_catalog_spec.rb @@ -36,10 +36,21 @@ }.from(2.09).to(1.57) # 18.85 wholesale price divided by 12 end - it "changes stock level of retail variants" do + it "changes stock level of retail variant's catalog item" do expect { catalog.apply_wholesale_values! }.to change { catalog_item.stockLimitation }.from("-1").to(-12) end + + it "changes stock level of retail variant's offer" do + wholesale_offer = catalog.item( + "https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts/44519466500403/Offer" + ) + wholesale_offer.stockLimitation = 2 + + expect { catalog.apply_wholesale_values! }.to change { + offer.stockLimitation + }.from(nil).to(24) + end end end