Files
openfoodnetwork/app/models/spree/order_inventory.rb
2025-05-13 15:24:26 +10:00

111 lines
3.3 KiB
Ruby

# frozen_string_literal: true
module Spree
class OrderInventory
attr_accessor :order
def initialize(order)
@order = order
end
# Only verify inventory for completed orders (as orders in frontend checkout
# have inventory assigned via +order.create_proposed_shipment+) or when
# shipment is explicitly passed
#
# In case shipment is passed stock should only be adjusted
# if the order is completed. That is so because stock items
# are always unstocked when the order is completed through +shipment.finalize+
def verify(line_item, shipment = nil)
if order.completed? || shipment.present?
variant_units = inventory_units_for(line_item.variant)
if variant_units.size < line_item.quantity
quantity = line_item.quantity - variant_units.size
shipment ||= determine_target_shipment(line_item.variant)
add_to_shipment(shipment, line_item.variant, quantity)
elsif variant_units.size > line_item.quantity
remove(line_item, variant_units, shipment)
end
else
true
end
end
def inventory_units_for(variant)
units = order.shipments.collect{ |s| s.inventory_units.to_a }.flatten
units.group_by(&:variant_id)[variant.id] || []
end
private
def remove(line_item, variant_units, shipment = nil)
quantity = variant_units.size - line_item.quantity
if shipment.present?
remove_from_shipment(shipment, line_item.variant, quantity, line_item.restock_item)
else
order.shipments.each do |each_shipment|
break if quantity == 0
quantity -= remove_from_shipment(each_shipment,
line_item.variant,
quantity,
line_item.restock_item)
end
end
end
# Returns either one of the shipment:
#
# first unshipped that already includes this variant
def determine_target_shipment(variant)
target_shipment = order.shipments.detect do |shipment|
(shipment.ready? || shipment.pending?) && shipment.contains?(variant)
end
target_shipment || order.shipments.detect do |shipment|
shipment.ready? || shipment.pending?
end
end
def add_to_shipment(shipment, variant, quantity)
on_hand, back_order = variant.fill_status(quantity)
on_hand.times { shipment.set_up_inventory('on_hand', variant, order) }
back_order.times { shipment.set_up_inventory('backordered', variant, order) }
if order.completed?
variant.move(-quantity)
end
quantity
end
def remove_from_shipment(shipment, variant, quantity, restock_item)
return 0 if quantity == 0 || shipment.shipped?
shipment_units = shipment.inventory_units_for(variant).reject do |variant_unit|
variant_unit.state == 'shipped'
end.sort_by(&:state)
removed_quantity = 0
shipment_units.each do |inventory_unit|
break if removed_quantity == quantity
inventory_unit.destroy
removed_quantity += 1
end
shipment.destroy if shipment.inventory_units.reload.count == 0
if order.completed? && restock_item
variant.move(removed_quantity)
end
removed_quantity
end
end
end