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

Amend backorder after cancellations #12945

Merged
merged 3 commits into from
Oct 29, 2024
Merged
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
98 changes: 98 additions & 0 deletions app/jobs/amend_backorder_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# frozen_string_literal: true

# When orders are cancelled, we need to amend
# an existing backorder as well.
# We're not dealing with line item changes just yet.
class AmendBackorderJob < ApplicationJob
sidekiq_options retry: 0

def perform(order)
OrderLocker.lock_order_and_variants(order) do
amend_backorder(order)
end
end

# The following is a mix of the BackorderJob and the CompleteBackorderJob.
# TODO: Move the common code into a re-usable service class.
def amend_backorder(order)
order_cycle = order.order_cycle
distributor = order.distributor
user = distributor.owner
items = backorderable_items(order)

return if items.empty?

# We are assuming that all variants are linked to the same wholesale
# shop and its catalog:
reference_link = items[0].variant.semantic_links[0].semantic_id
urls = FdcUrlBuilder.new(reference_link)
orderer = FdcBackorderer.new(user, urls)

backorder = orderer.find_open_order

variants = order_cycle.variants_distributed_by(distributor)
adjust_quantities(order_cycle, user, backorder, urls, variants)

FdcBackorderer.new(user, urls).send_order(backorder)
end

# Check if we have enough stock to reduce the backorder.
#
# 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_cycle, user, order, urls, variants)
broker = FdcOfferBroker.new(user, urls)

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

# Assumption: If a transformation is present then we only sell the retail
# variant. If that can't be found, it was deleted and we'll ignore that
# for now.
next if linked_variant.nil?

# 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

# We look at all linked variants.
def backorderable_items(order)
order.line_items.select do |item|
# TODO: scope variants to hub.
# We are only supporting producer stock at the moment.
item.variant.semantic_links.present?
end
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
5 changes: 5 additions & 0 deletions app/jobs/complete_backorder_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ def adjust_quantities(order_cycle, user, order, urls, variants)
transformation = broker.wholesale_to_retail(wholesale_product_id)
linked_variant = variants.linked_to(transformation.retail_product_id)

# Assumption: If a transformation is present then we only sell the retail
# variant. If that can't be found, it was deleted and we'll ignore that
# for now.
next if linked_variant.nil?

# Find all line items for this order cycle
# Update quantity accordingly
if linked_variant.on_demand
Expand Down
2 changes: 2 additions & 0 deletions app/models/spree/order/checkout.rb
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ def after_cancel

OrderMailer.cancel_email(id).deliver_later if send_cancellation_email
update(payment_state: updater.update_payment_state)

AmendBackorderJob.perform_later(self)
end

def after_resume
Expand Down
15 changes: 15 additions & 0 deletions spec/factories/oidc_account_factory.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

FactoryBot.define do
factory :oidc_account, class: OidcAccount do
provider { "openid_connect" }
uid { user&.email || generate(:random_email) }

# This is a live test account authenticated via Les Communes.
factory :testdfc_account do
uid { "[email protected]" }
refresh_token { ENV.fetch("OPENID_REFRESH_TOKEN") }
updated_at { 1.day.ago }
end
end
end
12 changes: 2 additions & 10 deletions spec/factories/user_factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,12 @@
end

factory :oidc_user do
oidc_account {
OidcAccount.new(provider: "openid_connect", uid: email)
}
oidc_account { build(:oidc_account, uid: email) }
end

# This is a live test user authenticated via Les Communes.
factory :testdfc_user do
oidc_account {
OidcAccount.new(
uid: "[email protected]",
refresh_token: ENV.fetch("OPENID_REFRESH_TOKEN"),
updated_at: 1.day.ago,
)
}
oidc_account { build(:testdfc_account) }
end
end
end
Loading
Loading