Class: Spree::Stock::Quantifier
- Inherits:
-
Object
- Object
- Spree::Stock::Quantifier
- Defined in:
- app/models/spree/stock/quantifier.rb
Instance Attribute Summary collapse
-
#excluded_order ⇒ Object
readonly
Returns the value of attribute excluded_order.
-
#stock_location ⇒ Object
readonly
Returns the value of attribute stock_location.
-
#variant ⇒ Object
readonly
Returns the value of attribute variant.
Class Method Summary collapse
-
.allocated_count_column? ⇒ Boolean
Memoized schema check so #available_stock doesn't introspect the column list on every call.
Instance Method Summary collapse
-
#available_stock ⇒ Integer
Physical pool minus already-allocated units, summed across the variant's active stock items.
-
#backorderable? ⇒ Boolean
Check if any of variant stock items is backorderable.
-
#can_supply?(required = 1) ⇒ Boolean
Whether the requested quantity can be supplied.
-
#initialize(variant, stock_location = nil, excluded_order: nil) ⇒ Quantifier
constructor
A new instance of Quantifier.
-
#reserved_quantity ⇒ Integer
Units currently held by active checkout reservations on the location-filtered stock items.
- #stock_items ⇒ Object
-
#total_on_hand ⇒ Integer, BigDecimal
Units a customer can purchase right now: physical pool minus already-allocated units minus active checkout reservations.
Constructor Details
#initialize(variant, stock_location = nil, excluded_order: nil) ⇒ Quantifier
Returns a new instance of Quantifier.
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_order ⇒ Object (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_location ⇒ Object (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 |
#variant ⇒ Object (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.
124 125 126 127 128 |
# File 'app/models/spree/stock/quantifier.rb', line 124 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_stock ⇒ Integer
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.
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
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
Whether the requested quantity can be supplied. Beyond on-hand stock a
variant may oversell two ways: backorderable stock, or a pre-order
(which also lifts the publish gate for a scheduled launch). Either way
the variant's backorder_limit caps how far below zero it may go; a nil
(empty) limit means unlimited.
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'app/models/spree/stock/quantifier.rb', line 96 def can_supply?(required = 1) return false unless variant.available? || variant.preorder? return true unless variant.should_track_inventory? oversellable = backorderable? || variant.preorder? limit = variant.backorder_limit return true if oversellable && limit.nil? # On-hand stock, plus — for a capped oversell — the room to sell below # zero down to +-backorder_limit+ (a single clamp of signed on-hand plus # the limit). supplyable = if oversellable [available_stock - reserved_quantity + limit, 0].max else total_on_hand end supplyable >= required end |
#reserved_quantity ⇒ Integer
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.
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_items ⇒ Object
115 116 117 |
# File 'app/models/spree/stock/quantifier.rb', line 115 def stock_items @stock_items ||= scope_to_location(variant.stock_items) end |
#total_on_hand ⇒ Integer, 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).
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 |