From a1bb98ef2ccb11a719c5db5761b2210f5a20bb98 Mon Sep 17 00:00:00 2001
From: andrea longhi <andrea@spaghetticode.it>
Date: Mon, 26 Feb 2024 16:19:43 +0100
Subject: [PATCH] Properly calculate inventory item quantities to be moved

The backordered quantity count should differ depending on whether
moving to the same or a different stock location. For this reason,
the way we calculate `available_quantity` changes as follows:

* when the stock location differs:
  the stock on hand at the new shipment stock location;
* when the stock location is the same:
  the sum of the stock on hand at the shipment stock location
  plus the number of on_hand inventory items from the shipment

The explicit `backordered_quantity` variable is introduced to track
the number of backordered items for the target shipment. The value
is calculated as follows:

* when the stock location differs:
  the quantity to be moved minus the positive available quantity at
  the stock location;
* when the stock location is the same:
  the shipment total quantity for the variant minus the positive
  available quantity at the stock location.

Also, we start the process by moving backordered items first to
to make sure no pending backordered item remains. If the backordered
count decreased, we're going to leave a few to be later moved and
transformed to on hand, while if the backordered count increased, we
are going to move also some previously on hand items.
---
 core/app/models/spree/fulfilment_changer.rb | 41 ++++++++++++++++-----
 1 file changed, 31 insertions(+), 10 deletions(-)

diff --git a/core/app/models/spree/fulfilment_changer.rb b/core/app/models/spree/fulfilment_changer.rb
index 61a4234716f..1178c08f8e6 100644
--- a/core/app/models/spree/fulfilment_changer.rb
+++ b/core/app/models/spree/fulfilment_changer.rb
@@ -86,9 +86,9 @@ def run!
     # we can take from the desired location, we could end up with some items being backordered.
     def run_tracking_inventory
       # Retrieve how many on hand items we can take from desired stock location
-      available_quantity = [desired_shipment.stock_location.count_on_hand(variant), default_on_hand_quantity].max
-
+      available_quantity = get_available_quantity
       new_on_hand_quantity = [available_quantity, quantity].min
+      backordered_quantity = get_backordered_quantity(available_quantity, new_on_hand_quantity)
       unstock_quantity = desired_shipment.stock_location.backorderable?(variant) ? quantity : new_on_hand_quantity
 
       ActiveRecord::Base.transaction do
@@ -105,19 +105,21 @@ def run_tracking_inventory
         # These two statements are the heart of this class. We change the number
         # of inventory units requested from one shipment to the other.
         # We order by state, because `'backordered' < 'on_hand'`.
+        # We start to move the new actual backordered quantity, so the remaining
+        # quantity can be set to on_hand state.
         current_shipment.
           inventory_units.
           where(variant: variant).
           order(state: :asc).
-          limit(new_on_hand_quantity).
-          update_all(shipment_id: desired_shipment.id, state: :on_hand)
+          limit(backordered_quantity).
+          update_all(shipment_id: desired_shipment.id, state: :backordered)
 
         current_shipment.
           inventory_units.
           where(variant: variant).
           order(state: :asc).
-          limit(quantity - new_on_hand_quantity).
-          update_all(shipment_id: desired_shipment.id, state: :backordered)
+          limit(quantity - backordered_quantity).
+          update_all(shipment_id: desired_shipment.id, state: :on_hand)
       end
     end
 
@@ -141,11 +143,22 @@ def handle_stock_counts?
       current_shipment.order.completed? && current_stock_location != desired_stock_location
     end
 
-    def default_on_hand_quantity
+    def get_available_quantity
+      if current_stock_location != desired_stock_location
+        desired_location_quantifier.positive_stock
+      else
+        sl_availability = current_location_quantifier.positive_stock
+        shipment_availability = current_shipment.inventory_units.where(variant: variant).on_hand.count
+        sl_availability + shipment_availability
+      end
+    end
+
+    def get_backordered_quantity(available_quantity, new_on_hand_quantity)
       if current_stock_location != desired_stock_location
-        0
+        quantity - new_on_hand_quantity
       else
-        current_shipment.inventory_units.where(variant: variant).on_hand.count
+        shipment_quantity = current_shipment.inventory_units.where(variant: variant).size
+        shipment_quantity - available_quantity
       end
     end
 
@@ -156,11 +169,19 @@ def current_shipment_not_already_shipped
     end
 
     def enough_stock_at_desired_location
-      unless Spree::Stock::Quantifier.new(variant, desired_stock_location).can_supply?(quantity)
+      unless desired_location_quantifier.can_supply?(quantity)
         errors.add(:desired_shipment, :not_enough_stock_at_desired_location)
       end
     end
 
+    def desired_location_quantifier
+      @desired_location_quantifier ||= Spree::Stock::Quantifier.new(variant, desired_stock_location)
+    end
+
+    def current_location_quantifier
+      @current_location_quantifier ||= Spree::Stock::Quantifier.new(variant, current_stock_location)
+    end
+
     def desired_shipment_different_from_current
       if desired_shipment.id == current_shipment.id
         errors.add(:desired_shipment, :can_not_transfer_within_same_shipment)