Adjust backorder for stock controlled items

We aggregate quantities over the whole order cycle to account for
cancelations and order adjustments by admins.
This commit is contained in:
Maikel Linke
2024-10-03 15:58:53 +10:00
parent 09de223c93
commit f8bd0a1cc7
3 changed files with 204 additions and 131 deletions

View File

@@ -22,7 +22,7 @@ class CompleteBackorderJob < ApplicationJob
urls = FdcUrlBuilder.new(order.lines[0].offer.offeredItem.semanticId)
variants = order_cycle.variants_distributed_by(distributor)
adjust_quantities(user, order, urls, variants)
adjust_quantities(order_cycle, user, order, urls, variants)
FdcBackorderer.new(user, urls).complete_order(order)
rescue StandardError
@@ -36,7 +36,7 @@ class CompleteBackorderJob < ApplicationJob
# 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(user, order, urls, variants)
def adjust_quantities(order_cycle, user, order, urls, variants)
broker = FdcOfferBroker.new(user, urls)
order.lines.each do |line|
@@ -45,18 +45,35 @@ class CompleteBackorderJob < ApplicationJob
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
# But maybe we didn't actually order that much:
deductable_quantity = [line.quantity, wholesale_items_contained_in_stock].min
line.quantity -= deductable_quantity
retail_stock_changes = deductable_quantity * transformation.factor
linked_variant.on_hand -= retail_stock_changes
# Find all line items for this order cycle
# Update quantity accordingly
if linked_variant.on_demand
release_superfluous_stock(line, linked_variant, transformation)
else
aggregate_final_quantities(order_cycle, line, linked_variant, transformation)
end
end
# Clean up empty lines:
order.lines.reject! { |line| line.quantity.zero? }
end
def release_superfluous_stock(line, linked_variant, transformation)
# Note that a division of integers dismisses the remainder, like `floor`:
wholesale_items_contained_in_stock = linked_variant.on_hand / transformation.factor
# But maybe we didn't actually order that much:
deductable_quantity = [line.quantity, wholesale_items_contained_in_stock].min
line.quantity -= deductable_quantity
retail_stock_changes = deductable_quantity * transformation.factor
linked_variant.on_hand -= retail_stock_changes
end
def aggregate_final_quantities(order_cycle, line, variant, transformation)
orders = order_cycle.orders.invoiceable
quantity = Spree::LineItem.where(order: orders, variant:).sum(:quantity)
wholesale_quantity = (quantity.to_f / transformation.factor).ceil
line.quantity = wholesale_quantity
end
end

File diff suppressed because one or more lines are too long

View File

@@ -3,52 +3,62 @@
require 'spec_helper'
RSpec.describe CompleteBackorderJob do
let(:user) { build(:testdfc_user) }
let(:catalog) {
VCR.use_cassette(:fdc_catalog) { FdcOfferBroker.load_catalog(user, urls) }
}
let(:user) { create(:testdfc_user) }
let(:urls) { FdcUrlBuilder.new(product_link) }
let(:product_link) {
"https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts/44519466467635"
}
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(:chia_seed_retail_link) {
"https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts/44519468400947"
}
let(:orderer) { FdcBackorderer.new(user, urls) }
let(:order) {
backorder = orderer.find_or_build_order(ofn_order)
broker = FdcOfferBroker.new(user, urls)
offer = broker.best_offer(retail_product.semanticId).offer
line = orderer.find_or_build_order_line(backorder, offer)
line.quantity = 3
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_offer = broker.offer_of(chia)
chia_line = orderer.find_or_build_order_line(backorder, chia_offer)
chia_line.quantity = 5
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] }
let(:beans) { ofn_order.line_items[0].variant }
let(:chia) { chia_item.variant }
let(:chia_item) { ofn_order.line_items[1] }
describe "#perform" do
before do
variant.semantic_links << SemanticLink.new(
semantic_id: retail_product.semanticId
beans.semantic_links << SemanticLink.new(
semantic_id: product_link
)
chia.semantic_links << SemanticLink.new(
semantic_id: chia_seed_retail_link
)
ofn_order.order_cycle = create(
:simple_order_cycle,
distributors: [distributor],
variants: [variant],
variants: ofn_order.variants,
)
ofn_order.save!
end
it "completes an order", vcr: true do
# We are assuming 12 cans in a slab.
# We got more stock than we need.
variant.on_hand = 13
beans.on_demand = true
beans.on_hand = 13
chia.on_demand = false
chia.on_hand = 17
chia_item.update!(quantity: 7)
current_order = order
@@ -62,8 +72,11 @@ RSpec.describe CompleteBackorderJob do
current_order.lines[0].quantity.to_i
}.from(3).to(2)
.and change {
variant.on_hand
beans.on_hand
}.from(13).to(1)
.and change {
current_order.lines[1].quantity.to_i
}.from(5).to(7)
end
it "removes line items", vcr: true do
@@ -71,7 +84,8 @@ RSpec.describe CompleteBackorderJob do
# We backordered 3 slabs, which is 36 cans.
# And now we would have more than 4 slabs (4*12 + 1 = 49)
# We got more stock than we need.
variant.on_hand = 49
beans.on_demand = true
beans.on_hand = 49
current_order = order
@@ -85,7 +99,7 @@ RSpec.describe CompleteBackorderJob do
current_order.lines.count
}.from(1).to(0)
.and change {
variant.on_hand
beans.on_hand
}.from(49).to(13) # minus 3 backordered slabs (3 * 12 = 36)
end