Skip to content

Commit

Permalink
Merge pull request #13049 from mkllnk/dfc-wholesale-stock
Browse files Browse the repository at this point in the history
Calculate stock from DFC wholesale variants
  • Loading branch information
mkllnk authored Jan 8, 2025
2 parents 8e0c039 + 0bd6fe6 commit 1df3a6b
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 73 deletions.
28 changes: 3 additions & 25 deletions app/controllers/admin/dfc_product_imports_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,12 @@ def index
.find(params.require(:enterprise_id))

catalog_url = params.require(:catalog_url)
broker = FdcOfferBroker.new(spree_current_user, catalog_url)
catalog = DfcCatalog.load(spree_current_user, catalog_url)
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 = broker.catalog.map do |subject|
next unless subject.is_a? DataFoodConsortium::Connector::SuppliedProduct

adjust_to_wholesale_price(broker, subject)

imported = catalog.products.map do |subject|
existing_variant = enterprise.supplied_variants.linked_to(subject.semanticId)

if existing_variant
Expand All @@ -44,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
3 changes: 2 additions & 1 deletion app/jobs/backorder_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
14 changes: 4 additions & 10 deletions app/jobs/stock_sync_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -58,15 +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)

graph.select do |subject|
subject.is_a? DataFoodConsortium::Connector::SuppliedProduct
end
end

def linked_variants(enterprises, product_ids)
Spree::Variant.where(supplier: enterprises)
.includes(:semantic_links).references(:semantic_links)
Expand Down
3 changes: 2 additions & 1 deletion app/services/backorder_updater.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 8 additions & 24 deletions app/services/fdc_offer_broker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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).
Expand All @@ -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
Expand All @@ -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
80 changes: 80 additions & 0 deletions engines/dfc_provider/app/services/dfc_catalog.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# 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

def products
@products ||= @graph.select do |subject|
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

def apply_wholesale_values!
broker = FdcOfferBroker.new(self)
products.each do |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(product, transformation)
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

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.stockLimitation = wholesale_offer.stockLimitation.to_i * transformation.factor
end
end
56 changes: 56 additions & 0 deletions engines/dfc_provider/spec/services/dfc_catalog_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# frozen_string_literal: true

require_relative "../spec_helper"

RSpec.describe DfcCatalog do
subject(:catalog) {
VCR.use_cassette(:fdc_catalog) {
DfcCatalog.load(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

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 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
5 changes: 3 additions & 2 deletions spec/jobs/complete_backorder_job_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 6 additions & 7 deletions spec/services/fdc_backorderer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
8 changes: 5 additions & 3 deletions spec/services/fdc_offer_broker_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down

0 comments on commit 1df3a6b

Please sign in to comment.