Module: Models::Reservable

Extended by:
ActiveSupport::Concern
Included in:
LineItem
Defined in:
app/concerns/models/reservable.rb

Overview

Serial number reservation behaviour for line-item-like models.

Mixed into line items whose items can reserve individual serial numbers
against the quantity being sold or shipped. Handles auto-reservation on
create/update, committing reservations, and linking/unlinking serial
numbers at shipment time.

Has many collapse

Has and belongs to many collapse

Instance Method Summary collapse

Instance Method Details

#all_reserved_serial_numbersArray<ReservedSerialNumber>

Reserved serial numbers on this record plus all of its children.

Returns:



133
134
135
136
137
# File 'app/concerns/models/reservable.rb', line 133

def all_reserved_serial_numbers
  res = reserved_serial_numbers.to_a
  children.each { |c| res = res.concat(c.reserved_serial_numbers) }
  res
end

#all_reserved_serial_numbers_available?Boolean

Whether every reservation fits within its serial number's availability.

Returns:

  • (Boolean)

    true when each reserved serial number has enough
    available quantity to cover its reservation



97
98
99
# File 'app/concerns/models/reservable.rb', line 97

def all_reserved_serial_numbers_available?
  reserved_serial_numbers.all? { |rsn| rsn.serial_number.qty_available >= rsn.qty }
end

#auto_reserve_serial_numbersBoolean

Automatically reserves serial numbers for any unreserved quantity.

Picks the tightest-fitting available serial number (least available
quantity, then least unreserved, then least shipped) and creates a
reservation for the outstanding quantity.

Returns:

  • (Boolean)

    always true so it can be used as a callback



171
172
173
174
175
176
177
178
# File 'app/concerns/models/reservable.rb', line 171

def auto_reserve_serial_numbers
  qty_to_reserve = quantity - total_reserved_serial_numbers
  if qty_to_reserve > 0
    matching_serial_number = available_serial_numbers.select { |sn| sn.qty_available >= qty_to_reserve }.min_by { |sn| [sn.qty_available, -sn.qty_unreserved, sn.shipped_count || 0] } # make this into some clever SQL
    reserved_serial_numbers.create(serial_number: matching_serial_number, qty: qty_to_reserve) if matching_serial_number.present?
  end
  true
end

#available_serial_numbersArray<SerialNumber>

Serial numbers available for reservation, least-available-quantity first.

Returns:

  • (Array<SerialNumber>)

    available serial numbers sorted by
    quantity available, then serial number



75
76
77
# File 'app/concerns/models/reservable.rb', line 75

def available_serial_numbers
  reservable_store_item.serial_numbers.available.sort_by { |sn| [sn.qty_available, sn.number] }
end

#commit_reserved_serial_numbersArray<ReservedSerialNumber>

Marks all reserved serial numbers as committed.

Returns:



104
105
106
# File 'app/concerns/models/reservable.rb', line 104

def commit_reserved_serial_numbers
  update_reserved_serial_numbers_committed(true)
end

#fully_reserved?Boolean

Whether the reserved quantity covers the full line item quantity.

Returns:

  • (Boolean)

    true when total reserved equals quantity



89
90
91
# File 'app/concerns/models/reservable.rb', line 89

def fully_reserved?
  total_reserved_serial_numbers == quantity
end

#kit_components_requires_reservation?Boolean

Whether the item is a kit with components that require reservation.

Returns:

  • (Boolean)

    true when the item is a kit and at least one kit
    component requires reservation



57
58
59
# File 'app/concerns/models/reservable.rb', line 57

def kit_components_requires_reservation?
  reservable_item.is_kit? && reservable_item.kit_components.any?(&:require_reservation?)
end

Links each reserved serial number to this record and marks it shipped.

Returns:



142
143
144
145
146
147
# File 'app/concerns/models/reservable.rb', line 142

def link_serial_numbers
  reserved_serial_numbers.each do |rsn|
    serial_numbers << rsn.serial_number
    rsn.serial_number.trigger_shipped
  end
end

#rejoin_serial_numbersArray<ReservedSerialNumber>

Rejoins each reserved serial number back to its original.

Returns:



125
126
127
128
# File 'app/concerns/models/reservable.rb', line 125

def rejoin_serial_numbers
  # reload here after each one or else the total on the original gets messed up
  reserved_serial_numbers.each { |rsn| rsn.reload.rejoin_serial_number }
end

#require_reservation?Boolean

Whether this line item requires serial number reservation.

Returns:

  • (Boolean)

    true when the resource supports auto-reservation and
    the item requires reservation



65
66
67
68
69
# File 'app/concerns/models/reservable.rb', line 65

def require_reservation?
  resource.respond_to?(:can_auto_reserve_serial_numbers?) &&
    resource.can_auto_reserve_serial_numbers? &&
    reservable_item&.require_reservation?
end

#reservable_itemItem

Item whose serial numbers may be reserved.

Returns:

  • (Item)

    the item backing this line item



34
35
36
# File 'app/concerns/models/reservable.rb', line 34

def reservable_item
  item
end

#reservable_store_itemStoreItem

Store item whose serial number pool is used for reservation.

Returns:

  • (StoreItem)

    the store item backing this line item



41
42
43
# File 'app/concerns/models/reservable.rb', line 41

def reservable_store_item
  store_item
end

#reserved_serial_numbersActiveRecord::Relation<ReservedSerialNumber>

Returns serial number
reservations made for this record.

Returns:

  • (ActiveRecord::Relation<ReservedSerialNumber>)

    serial number
    reservations made for this record



17
# File 'app/concerns/models/reservable.rb', line 17

has_many :reserved_serial_numbers, dependent: :destroy

#serial_numbersActiveRecord::Relation<SerialNumber>

Returns serial numbers linked
(shipped) to this record.

Returns:

  • (ActiveRecord::Relation<SerialNumber>)

    serial numbers linked
    (shipped) to this record



21
# File 'app/concerns/models/reservable.rb', line 21

has_and_belongs_to_many :serial_numbers

#show_serial_number_toggle?Boolean

Whether the serial number toggle should be shown in the UI.

Returns:

  • (Boolean)

    true when reservation is required or any serial
    numbers or reservations exist



184
185
186
# File 'app/concerns/models/reservable.rb', line 184

def show_serial_number_toggle?
  require_reservation? or serial_numbers.present? or reserved_serial_numbers.present?
end

#skip_reservation_screen?Boolean

Whether the serial number reservation screen can be skipped.

Returns:

  • (Boolean)

    true when fully reserved and the item opts out of the
    reservation screen



49
50
51
# File 'app/concerns/models/reservable.rb', line 49

def skip_reservation_screen?
  fully_reserved? && reservable_item.skip_serial_number_reservation_screen?
end

#split_serial_numbersArray<ReservedSerialNumber>

Splits each reserved serial number.

Returns:



118
119
120
# File 'app/concerns/models/reservable.rb', line 118

def split_serial_numbers
  reserved_serial_numbers.each { |rsn| rsn.reload.split_serial_number }
end

#total_reserved_serial_numbersInteger

Total quantity covered by non-destroyed serial number reservations.

Returns:

  • (Integer)

    summed reserved quantity



82
83
84
# File 'app/concerns/models/reservable.rb', line 82

def total_reserved_serial_numbers
  reserved_serial_numbers.reject(&:marked_for_destruction?).sum { |rsn| rsn.qty || 0 }
end

#uncommit_reserved_serial_numbersArray<ReservedSerialNumber>

Marks all reserved serial numbers as uncommitted.

Returns:



111
112
113
# File 'app/concerns/models/reservable.rb', line 111

def uncommit_reserved_serial_numbers
  update_reserved_serial_numbers_committed(false)
end

Unlinks all serial numbers and marks them available again.

Returns:

  • (Array<SerialNumber>)

    the cleared serial number collection



152
153
154
155
# File 'app/concerns/models/reservable.rb', line 152

def unlink_serial_numbers
  serial_numbers.each(&:trigger_available)
  serial_numbers.clear
end

#update_serial_numbers_shipped_countArray<SerialNumber>

Increments each linked serial number's shipped count by this quantity.

Returns:

  • (Array<SerialNumber>)

    the serial numbers that were updated



160
161
162
# File 'app/concerns/models/reservable.rb', line 160

def update_serial_numbers_shipped_count
  serial_numbers.each { |sn| sn.update_attribute!(:shipped_count, (sn.shipped_count || 0) + quantity) }
end