Class: Spree::Order

Inherits:
Base
  • Object
show all
Includes:
Checkout, CurrencyUpdater
Defined in:
app/models/spree/order.rb,
app/models/spree/order/checkout.rb,
app/models/spree/order/currency_updater.rb

Defined Under Namespace

Modules: Checkout, CurrencyUpdater

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from CurrencyUpdater

#price_from_line_item, #update_line_item_currencies!, #update_line_item_price!

Methods included from Checkout

included

Methods inherited from Base

page

Methods included from Preferences::Preferable

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

Instance Attribute Details

#coupon_codeObject

Returns the value of attribute coupon_code.



18
19
20
# File 'app/models/spree/order.rb', line 18

def coupon_code
  @coupon_code
end

#temporary_addressObject

Returns the value of attribute temporary_address.



19
20
21
# File 'app/models/spree/order.rb', line 19

def temporary_address
  @temporary_address
end

#use_billingObject

Returns the value of attribute use_billing.



68
69
70
# File 'app/models/spree/order.rb', line 68

def use_billing
  @use_billing
end

Class Method Details

.by_customer(customer) ⇒ Object



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

def self.by_customer(customer)
  joins(:user).where("#{Spree.user_class.table_name}.email" => customer)
end

.by_number(number) ⇒ Object



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

def self.by_number(number)
  where(number: number)
end

.by_state(state) ⇒ Object



100
101
102
# File 'app/models/spree/order.rb', line 100

def self.by_state(state)
  where(state: state)
end

.completeObject



104
105
106
# File 'app/models/spree/order.rb', line 104

def self.complete
  where.not(completed_at: nil)
end

.incompleteObject



108
109
110
# File 'app/models/spree/order.rb', line 108

def self.incomplete
  where(completed_at: nil)
end

.register_update_hook(hook) ⇒ Object

Use this method in other gems that wish to register their own custom logic that should be called after Order#update



114
115
116
# File 'app/models/spree/order.rb', line 114

def self.register_update_hook(hook)
  self.update_hooks.add(hook)
end

Instance Method Details

#all_adjustmentsObject



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

def all_adjustments
  Adjustment.where("order_id = :order_id OR (adjustable_id = :order_id AND adjustable_type = 'Spree::Order')",
    order_id: self.id)
end

#allow_cancel?Boolean

Returns:

  • (Boolean)


232
233
234
235
# File 'app/models/spree/order.rb', line 232

def allow_cancel?
  return false unless completed? and state != 'canceled'
  shipment_state.nil? || %w{ready backorder pending}.include?(shipment_state)
end

#amountObject

For compatiblity with Calculator::PriceSack



124
125
126
# File 'app/models/spree/order.rb', line 124

def amount
  line_items.inject(0.0) { |sum, li| sum + li.amount }
end

#apply_free_shipping_promotionsObject



505
506
507
508
509
510
# File 'app/models/spree/order.rb', line 505

def apply_free_shipping_promotions
  Spree::PromotionHandler::FreeShipping.new(self).activate
  shipments.each { |shipment| ItemAdjustments.new(shipment).update }
  updater.update_shipment_total
  persist_totals
end

#approve!Object



578
579
580
# File 'app/models/spree/order.rb', line 578

def approve!
  update_column(:considered_risky, false)
end

#approved?Boolean

Returns:

  • (Boolean)


560
561
562
# File 'app/models/spree/order.rb', line 560

def approved?
  !!self.approved_at
end

#approved_by(user) ⇒ Object



550
551
552
553
554
555
556
557
558
# File 'app/models/spree/order.rb', line 550

def approved_by(user)
  self.transaction do
    approve!
    self.update_columns(
      approver_id: user.id,
      approved_at: Time.now,
    )
  end
end

#associate_user!(user, override_email = true) ⇒ Object

Associates the specified user with the order.



246
247
248
249
250
251
252
253
254
255
256
257
# File 'app/models/spree/order.rb', line 246

def associate_user!(user, override_email = true)
  self.user = user
  attrs_to_set = { user_id: user.id }
  attrs_to_set[:email] = user.email if override_email
  attrs_to_set[:created_by_id] = user.id if self.created_by.blank?
  assign_attributes(attrs_to_set)

  if persisted?
    # immediately persist the changes we just made, but don't use save since we might have an invalid address associated
    self.class.unscoped.where(id: id).update_all(attrs_to_set)
  end
end

#available_payment_methodsObject



360
361
362
# File 'app/models/spree/order.rb', line 360

def available_payment_methods
  @available_payment_methods ||= (PaymentMethod.available(:front_end) + PaymentMethod.available(:both)).uniq
end

#awaiting_returns?Boolean

Returns:

  • (Boolean)


237
238
239
# File 'app/models/spree/order.rb', line 237

def awaiting_returns?
  return_authorizations.any? { |return_authorization| return_authorization.authorized? }
end

#backordered?Boolean

Returns:

  • (Boolean)


200
201
202
# File 'app/models/spree/order.rb', line 200

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

#billing_firstnameObject



404
405
406
# File 'app/models/spree/order.rb', line 404

def billing_firstname
  bill_address.try(:firstname)
end

#billing_lastnameObject



408
409
410
# File 'app/models/spree/order.rb', line 408

def billing_lastname
  bill_address.try(:lastname)
end

#can_add_coupon?Boolean

Returns:

  • (Boolean)


484
485
486
# File 'app/models/spree/order.rb', line 484

def can_add_coupon?
  Spree::Promotion.order_activatable?(self)
end

#can_approve?Boolean

Returns:

  • (Boolean)


564
565
566
# File 'app/models/spree/order.rb', line 564

def can_approve?
  !approved?
end

#can_ship?Boolean

Returns:

  • (Boolean)


317
318
319
# File 'app/models/spree/order.rb', line 317

def can_ship?
  self.complete? || self.resumed? || self.awaiting_return? || self.returned?
end

#checkout_allowed?Boolean

Indicates whether or not the user is allowed to proceed to checkout. Currently this is implemented as a check for whether or not there is at least one LineItem in the Order. Feel free to override this logic in your own application if you require additional steps before allowing a checkout.

Returns:

  • (Boolean)


181
182
183
# File 'app/models/spree/order.rb', line 181

def checkout_allowed?
  line_items.count > 0
end

#clone_billing_addressObject



223
224
225
226
227
228
229
230
# File 'app/models/spree/order.rb', line 223

def clone_billing_address
  if bill_address and self.ship_address.nil?
    self.ship_address = bill_address.clone
  else
    self.ship_address.attributes = bill_address.attributes.except('id', 'updated_at', 'created_at')
  end
  true
end

#completed?Boolean

Returns:

  • (Boolean)


173
174
175
# File 'app/models/spree/order.rb', line 173

def completed?
  completed_at.present?
end

#confirmation_required?Boolean

If true, causes the confirmation step to happen during the checkout process

Returns:

  • (Boolean)


191
192
193
194
195
196
197
198
# File 'app/models/spree/order.rb', line 191

def confirmation_required?
  Spree::Config[:always_include_confirm_step] ||
    payments.valid.map(&:payment_method).compact.any?(&:payment_profiles_supported?) ||
    # Little hacky fix for #4117
    # If this wasn't here, order would transition to address state on confirm failure
    # because there would be no valid payments any more.
    state == 'confirm'
end

#consider_riskObject



568
569
570
571
572
# File 'app/models/spree/order.rb', line 568

def consider_risk
  if is_risky? && !approved?
    considered_risky!
  end
end

#considered_risky!Object



574
575
576
# File 'app/models/spree/order.rb', line 574

def considered_risky!
  update_column(:considered_risky, true)
end

#contains?(variant) ⇒ Boolean

Returns:

  • (Boolean)


277
278
279
# File 'app/models/spree/order.rb', line 277

def contains?(variant)
  find_line_item_by_variant(variant).present?
end

#contentsObject



241
242
243
# File 'app/models/spree/order.rb', line 241

def contents
  @contents ||= Spree::OrderContents.new(self)
end

#create_proposed_shipmentsObject



493
494
495
496
497
498
499
500
501
502
503
# File 'app/models/spree/order.rb', line 493

def create_proposed_shipments
  adjustments.shipping.delete_all
  shipments.destroy_all

  packages = Spree::Stock::Coordinator.new(self).packages
  packages.each do |package|
    shipments << package.to_shipment
  end

  shipments
end

#create_tax_charge!Object

Creates new tax charges if there are any applicable rates. If prices already include taxes then price adjustments are created instead.



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

def create_tax_charge!
  # We want to only look up the applicable tax zone once and pass it to TaxRate calculation to avoid duplicated lookups.
  order_tax_zone = self.tax_zone
  Spree::TaxRate.adjust(order_tax_zone, line_items)
  Spree::TaxRate.adjust(order_tax_zone, shipments) if shipments.any?
end

#credit_cardsObject



321
322
323
324
# File 'app/models/spree/order.rb', line 321

def credit_cards
  credit_card_ids = payments.from_credit_card.pluck(:source_id).uniq
  CreditCard.where(id: credit_card_ids)
end

#currencyObject



128
129
130
# File 'app/models/spree/order.rb', line 128

def currency
  self[:currency] || Spree::Config[:currency]
end

#deliver_order_confirmation_emailObject



350
351
352
353
# File 'app/models/spree/order.rb', line 350

def deliver_order_confirmation_email
  OrderMailer.confirm_email(self.id).deliver
  update_column(:confirmation_delivered, true)
end

#display_additional_tax_totalObject



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

def display_additional_tax_total
  Spree::Money.new(additional_tax_total, { currency: currency })
end

#display_adjustment_totalObject



140
141
142
# File 'app/models/spree/order.rb', line 140

def display_adjustment_total
  Spree::Money.new(adjustment_total, { currency: currency })
end

#display_included_tax_totalObject



144
145
146
# File 'app/models/spree/order.rb', line 144

def display_included_tax_total
  Spree::Money.new(included_tax_total, { currency: currency })
end

#display_item_totalObject



136
137
138
# File 'app/models/spree/order.rb', line 136

def display_item_total
  Spree::Money.new(item_total, { currency: currency })
end

#display_outstanding_balanceObject



132
133
134
# File 'app/models/spree/order.rb', line 132

def display_outstanding_balance
  Spree::Money.new(outstanding_balance, { currency: currency })
end

#display_shipment_totalObject Also known as: display_ship_total



156
157
158
# File 'app/models/spree/order.rb', line 156

def display_shipment_total
  Spree::Money.new(shipment_total, { currency: currency })
end

#display_tax_totalObject



152
153
154
# File 'app/models/spree/order.rb', line 152

def display_tax_total
  Spree::Money.new(tax_total, { currency: currency })
end

#display_totalObject



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

def display_total
  Spree::Money.new(total, { currency: currency })
end

#empty!Object



450
451
452
453
454
455
456
457
458
# File 'app/models/spree/order.rb', line 450

def empty!
  line_items.destroy_all
  updater.update_item_count
  adjustments.destroy_all
  shipments.destroy_all

  update_totals
  persist_totals
end

#ensure_line_items_are_in_stockObject



416
417
418
419
420
421
422
423
424
# File 'app/models/spree/order.rb', line 416

def ensure_line_items_are_in_stock
  if insufficient_stock_lines.present?
    errors.add(:base, Spree.t(:insufficient_stock_lines_present))
    restart_checkout_flow
    false
  else
    true
  end
end

#ensure_updated_shipmentsObject

Clean shipments and make order back to address state

At some point the might need to force the order to transition from address to delivery again so that proper updated shipments are created. e.g. customer goes back from payment step and changes order items



517
518
519
520
521
522
523
# File 'app/models/spree/order.rb', line 517

def ensure_updated_shipments
  if shipments.any? && !self.completed?
    self.shipments.destroy_all
    self.update_column(:shipment_total, 0)
    restart_checkout_flow
  end
end

#finalize!Object

Finalizes an in progress order after checkout is complete. Called after transition to complete state when payments will have been processed



328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
# File 'app/models/spree/order.rb', line 328

def finalize!
  # lock all adjustments (coupon promotions, etc.)
  all_adjustments.each{|a| a.close}

  # update payment and shipment(s) states, and save
  updater.update_payment_state
  shipments.each do |shipment|
    shipment.update!(self)
    shipment.finalize!
  end

  updater.update_shipment_state
  save
  updater.run_hooks

  touch :completed_at

  deliver_order_confirmation_email unless confirmation_delivered?

  consider_risk
end

#find_line_item_by_variant(variant) ⇒ Object



286
287
288
# File 'app/models/spree/order.rb', line 286

def find_line_item_by_variant(variant)
  line_items.detect { |line_item| line_item.variant_id == variant.id }
end

#generate_order_number(digits = 9) ⇒ Object



259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'app/models/spree/order.rb', line 259

def generate_order_number(digits = 9)
  self.number ||= loop do
     # Make a random number.
     random = "R#{Array.new(digits){rand(10)}.join}"
     # Use the random  number if no other order exists with it.
     if self.class.exists?(number: random)
       # If over half of all possible options are taken add another digit.
       digits += 1 if self.class.count > (10 ** digits / 2)
     else
       break random
     end
   end
end

#has_step?(step) ⇒ Boolean

Returns:

  • (Boolean)


460
461
462
# File 'app/models/spree/order.rb', line 460

def has_step?(step)
  checkout_steps.include?(step)
end

#insufficient_stock_linesObject



412
413
414
# File 'app/models/spree/order.rb', line 412

def insufficient_stock_lines
 line_items.select(&:insufficient_stock?)
end

#is_risky?Boolean

Returns:

  • (Boolean)


546
547
548
# File 'app/models/spree/order.rb', line 546

def is_risky?
  self.payments.risky.count > 0
end

#merge!(order, user = nil) ⇒ Object



426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
# File 'app/models/spree/order.rb', line 426

def merge!(order, user = nil)
  order.line_items.each do |line_item|
    next unless line_item.currency == currency
    current_line_item = self.line_items.find_by(variant: line_item.variant)
    if current_line_item
      current_line_item.quantity += line_item.quantity
      current_line_item.save
    else
      line_item.order_id = self.id
      line_item.save
    end
  end

  self.associate_user!(user) if !self.user && !user.blank?

  updater.update_item_count
  updater.update_item_total
  updater.persist_totals

  # So that the destroy doesn't take out line items which may have been re-assigned
  order.line_items.reload
  order.destroy
end

#nameObject



311
312
313
314
315
# File 'app/models/spree/order.rb', line 311

def name
  if (address = bill_address || ship_address)
    "#{address.firstname} #{address.lastname}"
  end
end

#outstanding_balanceObject



299
300
301
302
303
304
305
# File 'app/models/spree/order.rb', line 299

def outstanding_balance
  if self.state == 'canceled' && self.payments.present? && self.payments.completed.size > 0
    -1 * payment_total
  else
    total - payment_total
  end
end

#outstanding_balance?Boolean

Returns:

  • (Boolean)


307
308
309
# File 'app/models/spree/order.rb', line 307

def outstanding_balance?
 self.outstanding_balance != 0
end

#paid?Boolean

Helper methods for checkout steps

Returns:

  • (Boolean)


356
357
358
# File 'app/models/spree/order.rb', line 356

def paid?
  payment_state == 'paid' || payment_state == 'credit_owed'
end

#payment_required?Boolean

Is this a free order in which case the payment step should be skipped

Returns:

  • (Boolean)


186
187
188
# File 'app/models/spree/order.rb', line 186

def payment_required?
  total.to_f > 0.0
end

#pending_paymentsObject



364
365
366
# File 'app/models/spree/order.rb', line 364

def pending_payments
  payments.select { |payment| payment.checkout? || payment.pending? }
end

#process_payments!Object

processes any pending payments and must return a boolean as it’s return value is used by the checkout state_machine to determine success or failure of the ‘complete’ event for the order

Returns:

  • true if all pending_payments processed successfully

  • true if a payment failed, ie. raised a GatewayError which gets rescued and converted to TRUE when :allow_checkout_gateway_error is set to true

  • false if a payment failed, ie. raised a GatewayError which gets rescued and converted to FALSE when :allow_checkout_on_gateway_error is set to false



384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
# File 'app/models/spree/order.rb', line 384

def process_payments!\
  # Don't run if there is nothing to pay.
  return if payment_total >= total
  # Prevent orders from transitioning to complete without a successfully processed payment.
  raise Core::GatewayError.new(Spree.t(:no_payment_found)) if pending_payments.empty?

  pending_payments.each do |payment|
    break if payment_total >= total

    payment.process!

    if payment.completed?
      self.payment_total += payment.amount
    end
  end
rescue Core::GatewayError => e
  result = !!Spree::Config[:allow_checkout_on_gateway_error]
  errors.add(:base, e.message) and return result
end

#quantityObject



604
605
606
# File 'app/models/spree/order.rb', line 604

def quantity
  line_items.sum(:quantity)
end

#quantity_of(variant) ⇒ Object



281
282
283
284
# File 'app/models/spree/order.rb', line 281

def quantity_of(variant)
  line_item = find_line_item_by_variant(variant)
  line_item ? line_item.quantity : 0
end

#refresh_shipment_ratesObject



532
533
534
# File 'app/models/spree/order.rb', line 532

def refresh_shipment_rates
  shipments.map &:refresh_rates
end

#reload(options = nil) ⇒ Object



595
596
597
598
# File 'app/models/spree/order.rb', line 595

def reload(options=nil)
  remove_instance_variable(:@tax_zone) if defined?(@tax_zone)
  super
end

#restart_checkout_flowObject



525
526
527
528
529
530
# File 'app/models/spree/order.rb', line 525

def restart_checkout_flow
  self.update_columns(
    state: checkout_steps.first,
    updated_at: Time.now,
  )
end

#set_shipments_costObject



540
541
542
543
544
# File 'app/models/spree/order.rb', line 540

def set_shipments_cost
  shipments.each(&:update_amounts)
  updater.update_shipment_total
  persist_totals
end

#shipped?Boolean

Returns:

  • (Boolean)


489
490
491
# File 'app/models/spree/order.rb', line 489

def shipped?
  %w(partial shipped).include?(shipment_state)
end

#shipped_shipmentsObject



273
274
275
# File 'app/models/spree/order.rb', line 273

def shipped_shipments
  shipments.shipped
end

#shipping_discountObject



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

def shipping_discount
  shipment_adjustments.eligible.sum(:amount) * - 1
end

#shipping_eq_billing_address?Boolean

Returns:

  • (Boolean)


536
537
538
# File 'app/models/spree/order.rb', line 536

def shipping_eq_billing_address?
  (bill_address.empty? && ship_address.empty?) || bill_address.same_as?(ship_address)
end

#state_changed(name) ⇒ Object



464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
# File 'app/models/spree/order.rb', line 464

def state_changed(name)
  state = "#{name}_state"
  if persisted?
    old_state = self.send("#{state}_was")
    new_state = self.send(state)
    unless old_state == new_state
      self.state_changes.create(
        previous_state: old_state,
        next_state:     new_state,
        name:           name,
        user_id:        self.user_id
      )
    end
  end
end

#tax_addressObject

Returns the address for taxation based on configuration



211
212
213
# File 'app/models/spree/order.rb', line 211

def tax_address
  Spree::Config[:tax_using_ship_address] ? ship_address : bill_address
end

#tax_totalObject



600
601
602
# File 'app/models/spree/order.rb', line 600

def tax_total
  included_tax_total + additional_tax_total
end

#tax_zoneObject

Returns the relevant zone (if any) to be used for taxation purposes. Uses default tax zone unless there is a specific match



206
207
208
# File 'app/models/spree/order.rb', line 206

def tax_zone
  @tax_zone ||= Zone.match(tax_address) || Zone.default_tax
end

#to_paramObject



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

def to_param
  number.to_s.to_url.upcase
end

#update!Object



219
220
221
# File 'app/models/spree/order.rb', line 219

def update!
  updater.update
end

#update_line_items(line_item_params) ⇒ Object

moved from api order_decorator. This is a better place for it.



583
584
585
586
587
588
589
590
591
592
593
# File 'app/models/spree/order.rb', line 583

def update_line_items(line_item_params)
  return if line_item_params.blank?
  line_item_params.each_value do |attributes|
    if attributes[:id].present?
      self.line_items.find(attributes[:id]).update_attributes!(attributes)
    else
      self.line_items.create!(attributes)
    end
  end
  self.ensure_updated_shipments
end

#updaterObject



215
216
217
# File 'app/models/spree/order.rb', line 215

def updater
  @updater ||= OrderUpdater.new(self)
end