Skip to content
Draft
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
9 changes: 8 additions & 1 deletion core/app/models/spree/order_shipping.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def create_proposed_shipments
raise Spree::Order::CannotRebuildShipments.new(I18n.t("spree.cannot_rebuild_shipments_shipments_not_pending"))
else
@order.shipments.destroy_all
@order.shipments.push(*Spree::Config.stock.coordinator_class.new(@order).shipments)
@order.shipments.push(*Spree::Config.stock.coordinator_class.new(@order, stock_locations:).shipments)
end
end

Expand Down Expand Up @@ -92,4 +92,11 @@ def ship(inventory_units:, stock_location:, address:, shipping_method:,
Spree::Bus.publish(:carton_shipped, carton:)
carton
end

private

def stock_locations
# TODO: add stock location filtering and sorting here, pulling out what was
# done in the Coordinator class
end
end
111 changes: 111 additions & 0 deletions core/app/models/spree/stock/coordinator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# frozen_string_literal: true

module Spree
module Stock
class Coordinator
attr_reader :order

# @api private
attr_reader :inventory_units, :splitters, :stock_locations,
:filtered_stock_locations

def initialize(
order,
inventory_units: nil,
inventory_unit_builder_class: Spree::Config.stock.inventory_unit_builder_class,
splitters: Spree::Config.environment.stock_splitters,
allocator_class: Spree::Config.stock.allocator_class,
estimator_class: Spree::Config.stock.estimator_class,
location_filter_class: Spree::Config.stock.location_filter_class,
location_sorter_class: Spree::Config.stock.location_sorter_class,
stock_locations: nil
)
@order = order
@inventory_units = inventory_units

@inventory_unit_builder_class = inventory_unit_builder_class
@location_filter_class = location_filter_class
@location_sorter_class = location_sorter_class
@allocator_class = allocator_class

@estimator = estimator_class.new

@splitters = splitters
end

def shipments
@shipments ||= begin
packages = build_packages
packages = split_packages(packages)
shipments = build_shipments(packages)

# Make sure we don't add the proposed shipments to the order
order.shipments = order.shipments - shipments

shipments
end
end

private

def build_shipments(packages)
# Turn the Stock::Packages into a Shipment with rates
packages.map do |package|
shipment = package.shipment = package.to_shipment
shipment.shipping_rates = @estimator.shipping_rates(package)
shipment
end
end

def build_packages
filtered_stock_locations = @location_filter_class.new(Spree::StockLocation.all, order).filter
stock_locations = @location_sorter_class.new(filtered_stock_locations).sort

@inventory_units ||= inventory_unit_builder_class.new(order).units

@inventory_units_by_variant = @inventory_units.group_by(&:variant)
desired = Spree::StockQuantities.new(@inventory_units_by_variant.transform_values(&:count))
availability = Spree::Stock::Availability.new(
variants: desired.variants,
stock_locations:
)
allocator = @allocator_class.new(availability)

on_hand_packages, backordered_packages, leftover = allocator.allocate_inventory(desired)

raise Spree::Order::InsufficientStock.new(items: leftover.quantities) unless leftover.empty?

packages = stock_locations.map do |stock_location|

Check failure on line 78 in core/app/models/spree/stock/coordinator.rb

View workflow job for this annotation

GitHub Actions / Check Ruby

Lint/UselessAssignment: Useless assignment to variable - `packages`.
# Combine on_hand and backorders into a single package per-location
on_hand = on_hand_packages[stock_location.id] || Spree::StockQuantities.new
backordered = backordered_packages[stock_location.id] || Spree::StockQuantities.new

# Skip this location it has no inventory
next if on_hand.empty? && backordered.empty?

# Turn our raw quantities into a Stock::Package
package = Spree::Stock::Package.new(stock_location)
package.add_multiple(get_units(on_hand), :on_hand)
package.add_multiple(get_units(backordered), :backordered)

package
end.compact
end

def split_packages(initial_packages)
initial_packages.flat_map do |initial_package|
stock_location = initial_package.stock_location
Spree::Stock::SplitterChain.new(stock_location, @splitters).split([initial_package])
end
end

def get_units(quantities)
# Change our raw quantities back into inventory units
quantities.flat_map do |variant, quantity|
@inventory_units_by_variant[variant].shift(quantity)
end
end
end
end
end

Check failure on line 111 in core/app/models/spree/stock/coordinator.rb

View workflow job for this annotation

GitHub Actions / Check Ruby

Layout/TrailingEmptyLines: 1 trailing blank lines detected.
Loading
Loading