Class: Spree::Shipment

Inherits:
Base
  • Object
show all
Extended by:
DisplayMoney
Includes:
Metadata
Defined in:
app/models/spree/shipment.rb

Overview

An order’s planned shipments including tracking and cost.

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from DisplayMoney

money_methods

Methods inherited from Base

display_includes

Methods included from Core::Permalinks

#generate_permalink, #save_permalink

Instance Attribute Details

#special_instructionsObject

TODO: remove the suppress_mailer temporary variable once we are calling ‘ship’ from outside of the state machine and can actually pass variables through.



26
27
28
# File 'app/models/spree/shipment.rb', line 26

def special_instructions
  @special_instructions
end

#suppress_mailerObject

TODO: remove the suppress_mailer temporary variable once we are calling ‘ship’ from outside of the state machine and can actually pass variables through.



26
27
28
# File 'app/models/spree/shipment.rb', line 26

def suppress_mailer
  @suppress_mailer
end

Instance Method Details

#after_cancelObject



72
73
74
# File 'app/models/spree/shipment.rb', line 72

def after_cancel
  manifest.each { |item| manifest_restock(item) }
end

#after_resumeObject



76
77
78
# File 'app/models/spree/shipment.rb', line 76

def after_resume
  manifest.each { |item| manifest_unstock(item) }
end

#backordered?Boolean

Returns:

  • (Boolean)


80
81
82
# File 'app/models/spree/shipment.rb', line 80

def backordered?
  inventory_units.any?(&:backordered?)
end

#can_transition_from_canceled_to_ready?Boolean

Returns:

  • (Boolean)


61
62
63
# File 'app/models/spree/shipment.rb', line 61

def can_transition_from_canceled_to_ready?
  can_transition_from_pending_to_ready?
end

#can_transition_from_pending_to_ready?Boolean

Returns:

  • (Boolean)


55
56
57
58
59
# File 'app/models/spree/shipment.rb', line 55

def can_transition_from_pending_to_ready?
  order.can_ship? &&
    inventory_units.all? { |iu| iu.shipped? || iu.allow_ship? || iu.canceled? } &&
    (order.paid? || !Spree::Config[:require_payment_to_ship])
end

#can_transition_from_pending_to_shipped?Boolean

Returns:

  • (Boolean)


51
52
53
# File 'app/models/spree/shipment.rb', line 51

def can_transition_from_pending_to_shipped?
  !requires_shipment?
end

#currencyObject



84
85
86
# File 'app/models/spree/shipment.rb', line 84

def currency
  order ? order.currency : Spree::Config[:currency]
end

#determine_state(order) ⇒ Object



203
204
205
206
207
208
209
210
211
212
213
214
# File 'app/models/spree/shipment.rb', line 203

def determine_state(order)
  Spree.deprecator.warn "Use Spree::Shipment#recalculate_state instead"

  return 'shipped' if shipped?
  return 'canceled' if order.canceled? || inventory_units.all?(&:canceled?)
  return 'pending' unless order.can_ship?
  if can_transition_from_pending_to_ready?
    'ready'
  else
    'pending'
  end
end

#editable_by?(_user) ⇒ Boolean

Returns:

  • (Boolean)


110
111
112
# File 'app/models/spree/shipment.rb', line 110

def editable_by?(_user)
  !shipped?
end

#finalize!Object

Decrement the stock counts for all pending inventory units in this shipment and mark. Any previous non-pending inventory units are skipped as their stock had already been allocated.



118
119
120
# File 'app/models/spree/shipment.rb', line 118

def finalize!
  finalize_pending_inventory_units
end

#include?(variant) ⇒ Boolean

Returns:

  • (Boolean)


122
123
124
# File 'app/models/spree/shipment.rb', line 122

def include?(variant)
  inventory_units_for(variant).present?
end

#inventory_units_for(variant) ⇒ Object



126
127
128
# File 'app/models/spree/shipment.rb', line 126

def inventory_units_for(variant)
  inventory_units.where(variant_id: variant.id)
end

#inventory_units_for_item(line_item, variant = nil) ⇒ Object



130
131
132
# File 'app/models/spree/shipment.rb', line 130

def inventory_units_for_item(line_item, variant = nil)
  inventory_units.where(line_item_id: line_item.id, variant_id: line_item.variant.id || variant.id)
end

#item_costObject



134
135
136
# File 'app/models/spree/shipment.rb', line 134

def item_cost
  line_items.sum(&:total)
end

#manifestObject



179
180
181
# File 'app/models/spree/shipment.rb', line 179

def manifest
  @manifest ||= Spree::ShippingManifest.new(inventory_units:).items
end

#ready_or_pending?Boolean

Returns:

  • (Boolean)


138
139
140
# File 'app/models/spree/shipment.rb', line 138

def ready_or_pending?
  ready? || pending?
end

#recalculate_stateObject

Assigns the appropriate state according to the following logic:

canceled if order is canceled pending unless order is complete and order.payment_state is paid shipped if already shipped (ie. does not change the state) ready all other cases



222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'app/models/spree/shipment.rb', line 222

def recalculate_state
  self.state =
    if shipped?
      "shipped"
    elsif order.canceled? || inventory_units.all?(&:canceled?)
      "canceled"
    elsif !order.can_ship?
      "pending"
    elsif can_transition_from_pending_to_ready?
      "ready"
    else
      "pending"
    end
end

#refresh_ratesObject



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'app/models/spree/shipment.rb', line 142

def refresh_rates
  return shipping_rates if shipped?
  return [] unless can_get_rates?

  # StockEstimator.new assigment below will replace the current shipping_method
  original_shipping_method_id = shipping_method.try!(:id)

  new_rates = Spree::Config.stock.estimator_class.new.shipping_rates(to_package)

  # If one of the new rates matches the previously selected shipping
  # method, select that instead of the default provided by the estimator.
  # Otherwise, keep the default.
  selected_rate = new_rates.detect{ |rate| rate.shipping_method_id == original_shipping_method_id }
  if selected_rate
    new_rates.each do |rate|
      rate.selected = (rate == selected_rate)
    end
  end

  self.shipping_rates = new_rates
  save!

  shipping_rates
end

#requires_shipment?Boolean

Returns:

  • (Boolean)


319
320
321
# File 'app/models/spree/shipment.rb', line 319

def requires_shipment?
  !stock_location || stock_location.fulfillable?
end

#select_shipping_method(shipping_method) ⇒ Object



167
168
169
170
171
172
173
# File 'app/models/spree/shipment.rb', line 167

def select_shipping_method(shipping_method)
  estimator = Spree::Config.stock.estimator_class.new
  rates = estimator.shipping_rates(to_package, false)
  rate = rates.detect { |detected| detected.shipping_method_id == shipping_method.id }
  rate.selected = true
  self.shipping_rates = [rate]
end

#selected_shipping_rateObject



175
176
177
# File 'app/models/spree/shipment.rb', line 175

def selected_shipping_rate
  shipping_rates.detect(&:selected?)
end

#selected_shipping_rate_idObject



183
184
185
# File 'app/models/spree/shipment.rb', line 183

def selected_shipping_rate_id
  selected_shipping_rate.try(:id)
end

#selected_shipping_rate_id=(id) ⇒ Object



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'app/models/spree/shipment.rb', line 187

def selected_shipping_rate_id=(id)
  return if selected_shipping_rate_id == id
  new_rate = shipping_rates.detect { |rate| rate.id == id.to_i }
  unless new_rate
    fail(
      ArgumentError,
      "Could not find shipping rate id #{id} for shipment #{number}"
    )
  end

  transaction do
    selected_shipping_rate.update!(selected: false) if selected_shipping_rate
    new_rate.update!(selected: true)
  end
end

#set_up_inventory(state, variant, _order, line_item) ⇒ Object



237
238
239
240
241
242
243
# File 'app/models/spree/shipment.rb', line 237

def set_up_inventory(state, variant, _order, line_item)
  inventory_units.create(
    state:,
    variant_id: variant.id,
    line_item_id: line_item.id
  )
end

#shipped=(value) ⇒ Object



245
246
247
248
# File 'app/models/spree/shipment.rb', line 245

def shipped=(value)
  return unless value == '1' && shipped_at.nil?
  self.shipped_at = Time.current
end

#shipping_methodObject



250
251
252
# File 'app/models/spree/shipment.rb', line 250

def shipping_method
  selected_shipping_rate.try(:shipping_method)
end

#tax_totalObject

Only one of either included_tax_total or additional_tax_total is set This method returns the total of the two. Saves having to check if tax is included or additional.



257
258
259
# File 'app/models/spree/shipment.rb', line 257

def tax_total
  included_tax_total + additional_tax_total
end

#to_packageObject



261
262
263
264
265
266
267
268
# File 'app/models/spree/shipment.rb', line 261

def to_package
  package = Stock::Package.new(stock_location)
  package.shipment = self
  inventory_units.includes(variant: :product).joins(:variant).group_by(&:state).each do |state, state_inventory_units|
    package.add_multiple state_inventory_units, state.to_sym
  end
  package
end

#to_paramObject



270
271
272
# File 'app/models/spree/shipment.rb', line 270

def to_param
  number
end

#totalBigDecimal

Returns the amount of this shipment, taking into consideration all its adjustments.

Returns:

  • (BigDecimal)

    the amount of this shipment, taking into consideration all its adjustments.



90
91
92
# File 'app/models/spree/shipment.rb', line 90

def total
  cost + adjustment_total
end

#total_before_taxBigDecimal

Returns the amount of this item, taking into consideration all non-tax adjustments.

Returns:

  • (BigDecimal)

    the amount of this item, taking into consideration all non-tax adjustments.



96
97
98
# File 'app/models/spree/shipment.rb', line 96

def total_before_tax
  amount + adjustments.reject(&:tax?).sum(&:amount)
end

#total_excluding_vatBigDecimal

Note:

just like ‘cost`, this does not include any additional tax

Returns the amount of this shipment before VAT tax.

Returns:

  • (BigDecimal)

    the amount of this shipment before VAT tax



102
103
104
# File 'app/models/spree/shipment.rb', line 102

def total_excluding_vat
  total_before_tax - included_tax_total
end

#total_with_itemsObject



106
107
108
# File 'app/models/spree/shipment.rb', line 106

def total_with_items
  total + item_cost
end

#tracking_urlObject



274
275
276
277
278
# File 'app/models/spree/shipment.rb', line 274

def tracking_url
  return nil unless tracking && shipping_method

  @tracking_url ||= shipping_method.build_tracking_url(tracking)
end

#update_amountsObject



280
281
282
283
284
285
286
287
288
289
290
# File 'app/models/spree/shipment.rb', line 280

def update_amounts
  if selected_shipping_rate
    self.cost = selected_shipping_rate.cost
    if changed?
      update_columns(
        cost:,
        updated_at: Time.current
      )
    end
  end
end

#update_attributes_and_order(params = {}) ⇒ Object

Update Shipment and make sure Order states follow the shipment changes



293
294
295
296
297
298
299
300
301
302
303
304
# File 'app/models/spree/shipment.rb', line 293

def update_attributes_and_order(params = {})
  if update(params)
    if params.key? :selected_shipping_rate_id
      # Changing the selected Shipping Rate won't update the cost (for now)
      # so we persist the Shipment#cost before running `order.recalculate`
      update_amounts
      order.recalculate
    end

    true
  end
end

#update_stateObject

Updates the state of the Shipment bypassing any callbacks.

If this moves the shipment to the ‘shipped’ state, after_ship will be called.



310
311
312
313
314
315
316
317
# File 'app/models/spree/shipment.rb', line 310

def update_state
  old_state = state
  new_state = recalculate_state
  if new_state != old_state
    update_columns state: new_state, updated_at: Time.current
    after_ship if new_state == 'shipped'
  end
end