Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calculate stock from DFC wholesale variants #13049

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.stockLimiation = wholesale_offer.stockLimitation.to_i * transformation.factor
end
end
45 changes: 45 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,45 @@
# 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 variants" do
expect { catalog.apply_wholesale_values! }.to change {
catalog_item.stockLimitation
}.from("-1").to(-12)
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
14 changes: 6 additions & 8 deletions spec/services/fdc_backorderer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,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 @@ -80,7 +80,7 @@

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)
existing_line = backorder.lines[0]

Expand All @@ -89,10 +89,8 @@
# the catalog offer here which is different to the offer on the
# existing order line.
ordered_product = existing_line.offer.offeredItem
catalog_product = catalog.find do |i|
i.semanticId == ordered_product.semanticId
end
catalog_offer = FdcOfferBroker.new(nil, nil).offer_of(catalog_product)
catalog_product = catalog.item(ordered_product.semanticId)
catalog_offer = FdcOfferBroker.new(nil).offer_of(catalog_product)

# The API response is missing this connection:
catalog_offer.offeredItem = catalog_product
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
Loading