Synchronise stock with DFC catalog during checkout

This will delay the checkout request by a few seconds if there's stock
to sync. But we minimise the chance of missing reduced stock from orders
on another platform.

We still have a gap between the checkout and placing a backorder. In
that time we can't guarantee enough stock. But let's tackle that after
the pilot.
This commit is contained in:
Maikel Linke
2024-10-08 16:59:23 +11:00
parent adf0340153
commit 71ca292c92
4 changed files with 75 additions and 8 deletions

View File

@@ -9,6 +9,12 @@ module CheckoutCallbacks
# Otherwise we fail on duplicate indexes or end up with negative stock.
prepend_around_action CurrentOrderLocker, only: [:edit, :update]
# We want to download the latest stock data before anything else happens.
# We don't want it to be in the same database transaction as the order
# locking because this action locks a different set of variants and it
# could cause race conditions.
prepend_around_action :sync_stock, only: :update
prepend_before_action :check_hub_ready_for_checkout
prepend_before_action :check_order_cycle_expiry
prepend_before_action :require_order_cycle
@@ -25,6 +31,14 @@ module CheckoutCallbacks
private
def sync_stock
if current_order&.state == "confirmation"
StockSyncJob.sync_linked_catalogs_now(current_order)
end
yield
end
def load_order
@order = current_order
@order.manual_shipping_selection = true

View File

@@ -9,15 +9,8 @@ class StockSyncJob < ApplicationJob
# enqueue a new job. That should save some time loading the order with
# all the stock data to make this decision.
def self.sync_linked_catalogs(order)
stock_controlled_variants = order.variants.reject(&:on_demand)
links = SemanticLink.where(variant_id: stock_controlled_variants.map(&:id))
semantic_ids = links.pluck(:semantic_id)
catalog_ids = semantic_ids.map do |product_id|
FdcUrlBuilder.new(product_id).catalog_url
end
user = order.distributor.owner
catalog_ids.uniq.each do |catalog_id|
catalog_ids(order).each do |catalog_id|
perform_later(user, catalog_id)
end
rescue StandardError => e
@@ -28,6 +21,28 @@ class StockSyncJob < ApplicationJob
end
end
def self.sync_linked_catalogs_now(order)
user = order.distributor.owner
catalog_ids(order).each do |catalog_id|
perform_now(user, catalog_id)
end
rescue StandardError => e
# Errors here shouldn't affect the shopping. So let's report them
# separately:
Bugsnag.notify(e) do |payload|
payload.add_metadata(:order, order)
end
end
def self.catalog_ids(order)
stock_controlled_variants = order.variants.reject(&:on_demand)
links = SemanticLink.where(variant_id: stock_controlled_variants.map(&:id))
semantic_ids = links.pluck(:semantic_id)
semantic_ids.map do |product_id|
FdcUrlBuilder.new(product_id).catalog_url
end.uniq
end
def perform(user, catalog_id)
products = load_products(user, catalog_id)
products_by_id = products.index_by(&:semanticId)

View File

@@ -451,6 +451,20 @@ RSpec.describe CheckoutController, type: :controller do
expect(response).to redirect_to order_path(order, order_token: order.token)
expect(order.reload.state).to eq "complete"
end
it "syncs stock before locking the order" do
actions = []
expect(StockSyncJob).to receive(:sync_linked_catalogs_now) do
actions << "sync stock"
end
expect(CurrentOrderLocker).to receive(:around) do
actions << "lock order"
end
put(:update, params:)
expect(actions).to eq ["sync stock", "lock order"]
end
end
context "when accepting T&Cs is required" do

View File

@@ -37,6 +37,30 @@ RSpec.describe StockSyncJob do
end
end
describe ".sync_linked_catalogs_now" do
subject { StockSyncJob.sync_linked_catalogs_now(order) }
it "ignores products without semantic link" do
expect(StockSyncJob).not_to receive(:perform_now)
expect { subject }.not_to enqueue_job(StockSyncJob)
end
it "performs stock check now" do
beans.semantic_links << SemanticLink.new(
semantic_id: beans_retail_link
)
expect(StockSyncJob).to receive(:perform_now).with(user, catalog_link)
expect { subject }.not_to raise_error
end
it "reports errors" do
expect(order).to receive(:variants).and_raise("test error")
expect(Bugsnag).to receive(:notify).and_call_original
expect { subject }.not_to raise_error
end
end
describe "#perform" do
subject { StockSyncJob.perform_now(user, catalog_link) }