Class: Spree::Stock::Quantifier

Inherits:
Object
  • Object
show all
Defined in:
app/models/spree/stock/quantifier.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(variant, stock_location = nil, excluded_order: nil) ⇒ Quantifier

Returns a new instance of Quantifier.

Parameters:

  • excluded_order (Spree::Order, nil) (defaults to: nil)

    when given, reservations belonging to this order are not counted against availability. Used when checking an order’s own line items so the customer’s own checkout hold doesn’t make their item look out of stock.



10
11
12
13
14
# File 'app/models/spree/stock/quantifier.rb', line 10

def initialize(variant, stock_location = nil, excluded_order: nil)
  @variant         = variant
  @stock_location  = stock_location
  @excluded_order  = excluded_order
end

Instance Attribute Details

#excluded_orderObject (readonly)

Returns the value of attribute excluded_order.



4
5
6
# File 'app/models/spree/stock/quantifier.rb', line 4

def excluded_order
  @excluded_order
end

#stock_locationObject (readonly)

Returns the value of attribute stock_location.



4
5
6
# File 'app/models/spree/stock/quantifier.rb', line 4

def stock_location
  @stock_location
end

#variantObject (readonly)

Returns the value of attribute variant.



4
5
6
# File 'app/models/spree/stock/quantifier.rb', line 4

def variant
  @variant
end

Class Method Details

.allocated_count_column?Boolean

Memoized schema check so #available_stock doesn’t introspect the column list on every call. Flips from false → true when 6.0 Typed Stock Movements adds the ‘allocated_count` column.

Returns:

  • (Boolean)


104
105
106
107
108
# File 'app/models/spree/stock/quantifier.rb', line 104

def self.allocated_count_column?
  return @allocated_count_column if defined?(@allocated_count_column)

  @allocated_count_column = Spree::StockItem.connection.column_exists?(:spree_stock_items, :allocated_count)
end

Instance Method Details

#available_stockInteger

Physical pool minus already-allocated units, summed across the variant’s active stock items.

In Spree 5.5 Spree::StockItem#allocated_count is a Ruby shim that always returns 0, so this equals SUM(count_on_hand). In 6.0 (Typed Stock Movements) allocated_count becomes a real column and the SQL path subtracts it natively.

Returns:

  • (Integer)

    units available before checkout reservations



41
42
43
44
45
46
47
48
49
# File 'app/models/spree/stock/quantifier.rb', line 41

def available_stock
  if association_loaded?
    stock_items.sum(&:available_count)
  elsif self.class.allocated_count_column?
    stock_items.sum('count_on_hand - allocated_count')
  else
    stock_items.sum(:count_on_hand)
  end
end

#backorderable?Boolean

Check if any of variant stock items is backorderable

Returns:

  • (Boolean)


87
88
89
# File 'app/models/spree/stock/quantifier.rb', line 87

def backorderable?
  @backorderable ||= stock_items.any?(&:backorderable)
end

#can_supply?(required = 1) ⇒ Boolean

Returns:

  • (Boolean)


91
92
93
# File 'app/models/spree/stock/quantifier.rb', line 91

def can_supply?(required = 1)
  variant.available? && (backorderable? || total_on_hand >= required)
end

#reserved_quantityInteger

Units currently held by active checkout reservations on the location-filtered stock items. Returns 0 when stock reservations are globally disabled.

Reads through the same #stock_items collection as #available_stock so a per-location query (filtered by ‘stock_location`) only counts reservations that belong to those same stock items — otherwise a multi-location variant would subtract reservations from other warehouses.

When excluded_order is set, that order’s own reservations are left out of the count so an order’s own checkout hold doesn’t count against the availability of its own line items.

Returns:

  • (Integer)


66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'app/models/spree/stock/quantifier.rb', line 66

def reserved_quantity
  return @reserved_quantity if defined?(@reserved_quantity)
  return @reserved_quantity = 0 unless Spree::Config[:stock_reservations_enabled]
  return @reserved_quantity = 0 if stock_items.blank?

  excluded_order_id = excluded_order&.id

  @reserved_quantity = if reservations_preloaded?
                         stock_items.sum do |si|
                           reservations = si.active_stock_reservations
                           reservations = reservations.reject { |r| r.order_id == excluded_order_id } if excluded_order_id
                           reservations.sum(&:quantity)
                         end
                       else
                         reservations = Spree::StockReservation.active.where(stock_item_id: stock_items.map(&:id))
                         reservations = reservations.where.not(order_id: excluded_order_id) if excluded_order_id
                         reservations.sum(:quantity)
                       end
end

#stock_itemsObject



95
96
97
# File 'app/models/spree/stock/quantifier.rb', line 95

def stock_items
  @stock_items ||= scope_to_location(variant.stock_items)
end

#total_on_handInteger, BigDecimal

Units a customer can purchase right now: physical pool minus already-allocated units minus active checkout reservations. Clamped at zero so callers never see a negative count.

Returns BigDecimal::INFINITY when the variant does not track inventory (effectively unlimited supply).

Returns:

  • (Integer, BigDecimal)

    purchasable quantity, or INFINITY



24
25
26
27
28
29
30
# File 'app/models/spree/stock/quantifier.rb', line 24

def total_on_hand
  @total_on_hand ||= if variant.should_track_inventory?
                       [available_stock - reserved_quantity, 0].max
                     else
                       BigDecimal::INFINITY
                     end
end