Class: Spree::Shipment

Inherits:
Base
  • Object
show all
Extended by:
DisplayMoney
Includes:
Metadata, NumberAsParam, NumberIdentifier, Spree::Security::Shipments, Webhooks::HasWebhooks
Defined in:
app/models/spree/shipment.rb

Defined Under Namespace

Classes: ManifestItem

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from DisplayMoney

money_methods

Methods included from NumberAsParam

#to_param

Methods inherited from Base

belongs_to_required_by_default, for_store, has_many_inversing, json_api_columns, json_api_permitted_attributes, json_api_type, page, spree_base_scopes, spree_base_uniqueness_scope

Methods included from Preferences::Preferable

#clear_preferences, #default_preferences, #defined_preferences, #deprecated_preferences, #get_preference, #has_preference!, #has_preference?, #preference_default, #preference_deprecated, #preference_type, #set_preference

Instance Attribute Details

#special_instructionsObject

Returns the value of attribute special_instructions.



37
38
39
# File 'app/models/spree/shipment.rb', line 37

def special_instructions
  @special_instructions
end

Instance Method Details

#add_shipping_method(shipping_method, selected = false) ⇒ Object



99
100
101
# File 'app/models/spree/shipment.rb', line 99

def add_shipping_method(shipping_method, selected = false)
  shipping_rates.create(shipping_method: shipping_method, selected: selected, cost: cost)
end

#after_cancelObject



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

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

#after_resumeObject



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

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

#backordered?Boolean

Returns:

  • (Boolean)


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

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

#determine_state(order) ⇒ Object

Determines the appropriate state according to the following logic:

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



120
121
122
123
124
125
126
127
# File 'app/models/spree/shipment.rb', line 120

def determine_state(order)
  return 'canceled' if order.canceled?
  return 'pending' unless order.can_ship?
  return 'pending' if inventory_units.any? &:backordered?
  return 'shipped' if shipped?

  order.paid? || Spree::Config[:auto_capture_on_dispatch] ? 'ready' : 'pending'
end

#discounted_costObject Also known as: discounted_amount



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

def discounted_cost
  cost + promo_total
end

#final_priceObject



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

def final_price
  cost + adjustment_total
end

#final_price_with_itemsObject



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

def final_price_with_items
  item_cost + final_price
end

#finalize!Object



148
149
150
151
# File 'app/models/spree/shipment.rb', line 148

def finalize!
  inventory_units.finalize_units!
  after_resume
end

#free?Boolean

Returns:

  • (Boolean)


142
143
144
145
146
# File 'app/models/spree/shipment.rb', line 142

def free?
  return true if final_price == BigDecimal(0)

  adjustments.promotion.any? { |p| p.source.type == 'Spree::Promotion::Actions::FreeShipping' }
end

#include?(variant) ⇒ Boolean

Returns:

  • (Boolean)


153
154
155
# File 'app/models/spree/shipment.rb', line 153

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

#inventory_units_for(variant) ⇒ Object



157
158
159
# File 'app/models/spree/shipment.rb', line 157

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

#inventory_units_for_item(line_item, variant = nil) ⇒ Object



161
162
163
# File 'app/models/spree/shipment.rb', line 161

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



165
166
167
# File 'app/models/spree/shipment.rb', line 165

def item_cost
  manifest.map { |m| (m.line_item.price + (m.line_item.adjustment_total / m.line_item.quantity)) * m.quantity }.sum
end

#line_itemsObject



169
170
171
# File 'app/models/spree/shipment.rb', line 169

def line_items
  inventory_units.includes(:line_item).map(&:line_item).uniq
end

#manifestObject



175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'app/models/spree/shipment.rb', line 175

def manifest
  # Grouping by the ID means that we don't have to call out to the association accessor
  # This makes the grouping by faster because it results in less SQL cache hits.
  inventory_units.group_by(&:variant_id).map do |_variant_id, units|
    units.group_by(&:line_item_id).map do |_line_item_id, units|
      states = {}
      units.group_by(&:state).each { |state, iu| states[state] = iu.sum(&:quantity) }

      line_item = units.first.line_item
      variant = units.first.variant
      ManifestItem.new(line_item, variant, units.sum(&:quantity), states)
    end
  end.flatten
end

#process_order_paymentsObject



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'app/models/spree/shipment.rb', line 190

def process_order_payments
  pending_payments = order.pending_payments.
                     sort_by(&:uncaptured_amount).reverse

  shipment_to_pay = final_price_with_items
  payments_amount = 0

  payments_pool = pending_payments.each_with_object([]) do |payment, pool|
    break if payments_amount >= shipment_to_pay

    payments_amount += payment.uncaptured_amount
    pool << payment
  end

  payments_pool.each do |payment|
    capturable_amount = if payment.amount >= shipment_to_pay
                          shipment_to_pay
                        else
                          payment.amount
                        end

    cents = (capturable_amount * 100).to_i
    payment.capture!(cents)
    shipment_to_pay -= capturable_amount
  end
end

#ready_or_pending?Boolean

Returns:

  • (Boolean)


217
218
219
# File 'app/models/spree/shipment.rb', line 217

def ready_or_pending?
  ready? || pending?
end

#refresh_rates(shipping_method_filter = ShippingMethod::DISPLAY_ON_FRONT_END) ⇒ Object



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'app/models/spree/shipment.rb', line 221

def refresh_rates(shipping_method_filter = ShippingMethod::DISPLAY_ON_FRONT_END)
  return shipping_rates if shipped?
  return [] unless can_get_rates?

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

  self.shipping_rates = Stock::Estimator.new(order).
                        shipping_rates(to_package, shipping_method_filter)

  if shipping_method
    selected_rate = shipping_rates.detect do |rate|
      if original_shipping_method_id
        rate.shipping_method_id == original_shipping_method_id
      else
        rate.selected
      end
    end
    save!
    self.selected_shipping_rate_id = selected_rate.id if selected_rate
    reload
  end

  shipping_rates
end

#selected_shipping_rate_idObject



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

def selected_shipping_rate_id
  selected_shipping_rate.try(:id)
end

#selected_shipping_rate_id=(id) ⇒ Object



251
252
253
254
255
256
# File 'app/models/spree/shipment.rb', line 251

def selected_shipping_rate_id=(id)
  # Explicitly updates the timestamp in order to bust cache dependent on "updated_at"
  shipping_rates.update_all(selected: false, updated_at: Time.current)
  shipping_rates.update(id, selected: true)
  save!
end

#set_up_inventory(state, variant, order, line_item, quantity = 1) ⇒ Object



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

def set_up_inventory(state, variant, order, line_item, quantity = 1)
  return if quantity <= 0

  inventory_units.create(
    state: state,
    variant_id: variant.id,
    order_id: order.id,
    line_item_id: line_item.id,
    quantity: quantity
  )
end

#shipped=(value) ⇒ Object



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

def shipped=(value)
  return unless value == '1' && shipped_at.nil?

  self.shipped_at = Time.current
end

#shipping_methodObject



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

def shipping_method
  selected_shipping_rate&.shipping_method || shipping_rates.first&.shipping_method
end

#tax_categoryObject



280
281
282
# File 'app/models/spree/shipment.rb', line 280

def tax_category
  selected_shipping_rate.try(:tax_rate).try(:tax_category)
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.



287
288
289
# File 'app/models/spree/shipment.rb', line 287

def tax_total
  included_tax_total + additional_tax_total
end

#to_packageObject



291
292
293
294
295
296
297
# File 'app/models/spree/shipment.rb', line 291

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

#tracking_urlObject



299
300
301
# File 'app/models/spree/shipment.rb', line 299

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

#transfer_to_location(variant, quantity, stock_location) ⇒ Object



330
331
332
333
334
335
336
# File 'app/models/spree/shipment.rb', line 330

def transfer_to_location(variant, quantity, stock_location)
  transfer_to_shipment(
    variant,
    quantity,
    order.shipments.build(stock_location: stock_location)
  )
end

#transfer_to_shipment(variant, quantity, shipment_to_transfer_to) ⇒ Object



338
339
340
341
342
343
344
345
346
347
# File 'app/models/spree/shipment.rb', line 338

def transfer_to_shipment(variant, quantity, shipment_to_transfer_to)
  Spree::FulfilmentChanger.new(
    current_stock_location: stock_location,
    desired_stock_location: shipment_to_transfer_to.stock_location,
    current_shipment: self,
    desired_shipment: shipment_to_transfer_to,
    variant: variant,
    quantity: quantity
  )
end

#update!(order) ⇒ Object

Updates various aspects of the Shipment while bypassing any callbacks. Note that this method takes an explicit reference to the Order object. This is necessary because the association actually has a stale (and unsaved) copy of the Order and so it will not yield the correct results.



320
321
322
323
324
325
326
327
328
# File 'app/models/spree/shipment.rb', line 320

def update!(order)
  old_state = state
  new_state = determine_state(order)
  update_columns(
    state: new_state,
    updated_at: Time.current
  )
  after_ship if new_state == 'shipped' && old_state != 'shipped'
end

#update_amountsObject



303
304
305
306
307
308
309
310
311
# File 'app/models/spree/shipment.rb', line 303

def update_amounts
  if selected_shipping_rate
    update_columns(
      cost: selected_shipping_rate.cost,
      adjustment_total: adjustments.additional.map(&:update!).compact.sum,
      updated_at: Time.current
    )
  end
end

#update_attributes_and_order(params = {}) ⇒ Object



313
314
315
# File 'app/models/spree/shipment.rb', line 313

def update_attributes_and_order(params = {})
  Shipments::Update.call(shipment: self, shipment_attributes: params).success?
end