Skip to content

Commit

Permalink
Adjust quantities of backorder before completion
Browse files Browse the repository at this point in the history
  • Loading branch information
mkllnk committed Sep 12, 2024
1 parent 63530b9 commit 2877d66
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 72 deletions.
26 changes: 20 additions & 6 deletions app/jobs/complete_backorder_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
# After an order cycle closed, we need to finalise open draft orders placed
# to replenish stock.
class CompleteBackorderJob < ApplicationJob
def perform(user, order_id)
# TODO: review our stock levels and adjust quantities if we got surplus.
# This can happen when orders are cancelled and products restocked.
def perform(user, distributor, order_cycle, order_id)
service = FdcBackorderer.new(user)
order = service.find_order(order_id)
adjust_quantities(order)

variants = order_cycle.variants_distributed_by(distributor)
adjust_quantities(user, order, variants)

service.complete_order(order)
end

Expand All @@ -17,7 +18,20 @@ def perform(user, order_id)
# Our local stock can increase when users cancel their orders.
# But stock levels could also have been adjusted manually. So we review all
# quantities before finalising the order.
def adjust_quantities(order)
# TODO
def adjust_quantities(user, order, variants)
broker = FdcOfferBroker.new(BackorderJob.load_catalog(user))

order.lines.each do |line|
wholesale_product_id = line.offer.offeredItem.semanticId
transformation = broker.wholesale_to_retail(wholesale_product_id)
linked_variant = variants.linked_to(transformation.retail_product_id)

# Note that a division of integers dismisses the remainder, like `floor`:
wholesale_items_contained_in_stock = linked_variant.on_hand / transformation.factor
line.quantity = line.quantity.to_i - wholesale_items_contained_in_stock

retail_stock_changes = wholesale_items_contained_in_stock * transformation.factor
linked_variant.on_hand -= retail_stock_changes
end
end
end
27 changes: 26 additions & 1 deletion app/services/fdc_offer_broker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

# Finds wholesale offers for retail products.
class FdcOfferBroker
# TODO: Find a better way to provide this data.
Solution = Struct.new(:product, :factor, :offer)
RetailSolution = Struct.new(:retail_product_id, :factor)

def initialize(catalog)
@catalog = catalog
Expand All @@ -14,13 +16,25 @@ def best_offer(product_id)

contained_quantity = consumption_flow.quantity.value.to_i
wholesale_product_id = production_flow.product
wholesale_product = catalog_item(wholesale_product_id )
wholesale_product = catalog_item(wholesale_product_id)

offer = offer_of(wholesale_product)

Solution.new(wholesale_product, contained_quantity, offer)
end

def wholesale_to_retail(wholesale_product_id)
production_flow = flow_producing(wholesale_product_id)
consumption_flow = catalog_item(
production_flow.semanticId.sub("AsPlannedProductionFlow", "AsPlannedConsumptionFlow")
)
retail_product_id = consumption_flow.product

contained_quantity = consumption_flow.quantity.value.to_i

RetailSolution.new(retail_product_id, contained_quantity)
end

def offer_of(product)
product&.catalogItems&.first&.offers&.first&.tap do |offer|
# Unfortunately, the imported catalog doesn't provide the reverse link:
Expand All @@ -32,4 +46,15 @@ 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(&:product)
@production_flows_by_product_id[wholesale_product_id]
end

def production_flows
@production_flows ||= @catalog.select do |i|
i.semanticType == "dfc-b:AsPlannedProductionFlow"
end
end
end

Large diffs are not rendered by default.

48 changes: 41 additions & 7 deletions spec/jobs/complete_backorder_job_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,60 @@
RSpec.describe CompleteBackorderJob do
let(:user) { build(:testdfc_user) }
let(:catalog) { BackorderJob.load_catalog(user) }
let(:product) {
let(:retail_product) {
catalog.find { |item| item.semanticType == "dfc-b:SuppliedProduct" }
}
let(:wholesale_product) {
flow = catalog.find { |item| item.semanticType == "dfc-b:AsPlannedProductionFlow" }
catalog.find { |item| item.semanticId == flow.product }
}
let(:orderer) { FdcBackorderer.new(user) }
let(:order) {
ofn_order = build(:order, distributor_id: 1)
ofn_order.order_cycle = build(:order_cycle)
backorder = orderer.find_or_build_order(ofn_order)
offer = FdcOfferBroker.new(nil).offer_of(product)
broker = FdcOfferBroker.new(catalog)
offer = broker.best_offer(retail_product.semanticId).offer
line = orderer.find_or_build_order_line(backorder, offer)
line.quantity = 3

orderer.send_order(backorder)
}
let(:ofn_order) { create(:completed_order_with_totals) }
let(:distributor) { ofn_order.distributor }
let(:order_cycle) { ofn_order.order_cycle }
let(:variant) { ofn_order.variants[0] }

describe "#perform" do
before do
variant.semantic_links << SemanticLink.new(
semantic_id: retail_product.semanticId
)

# We are assuming 12 cans in a slab.
# We got more stock than we need.
variant.on_hand = 13

ofn_order.order_cycle = create(
:simple_order_cycle,
distributors: [distributor],
variants: [variant],
)
end

it "completes an order", vcr: true do
subject.perform(user, order.semanticId)
updated_order = orderer.find_order(order.semanticId)
expect(updated_order.orderStatus[:path]).to eq "Complete"
current_order = order

expect {
subject.perform(user, distributor, order_cycle, order.semanticId)
current_order = orderer.find_order(order.semanticId)
}.to change {
current_order.orderStatus[:path]
}.from("Held").to("Complete")
.and change {
current_order.lines[0].quantity.to_i
}.from(3).to(2)
.and change {
variant.on_hand
}.from(13).to(1)
end
end
end

0 comments on commit 2877d66

Please sign in to comment.