Class: Spree::Order

Inherits:
ActiveRecord::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

Instance Attribute Details

#coupon_codeObject

Returns the value of attribute coupon_code.



23
24
25
# File 'app/models/spree/order.rb', line 23

def coupon_code
  @coupon_code
end

#use_billingObject

Returns the value of attribute use_billing.



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

def use_billing
  @use_billing
end

Class Method Details

.between(start_date, end_date) ⇒ Object



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

def self.between(start_date, end_date)
  ActiveSupport::Deprecation.warn("Order#between will be deprecated in Spree 2.3, please use either Order#created_between or Order#completed_between instead.")
  self.created_between(start_date, end_date)
end

.by_customer(customer) ⇒ Object



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

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

.by_number(number) ⇒ Object



89
90
91
# File 'app/models/spree/order.rb', line 89

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

.by_state(state) ⇒ Object



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

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

.completeObject



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

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

.incompleteObject



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

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



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

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

Instance Method Details

#all_adjustmentsObject



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

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)


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

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



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

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

#apply_free_shipping_promotionsObject



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

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



590
591
592
# File 'app/models/spree/order.rb', line 590

def approve!
  update_column(:considered_risky, false)
end

#approved?Boolean

Returns:

  • (Boolean)


572
573
574
# File 'app/models/spree/order.rb', line 572

def approved?
  !!self.approved_at
end

#approved_by(user) ⇒ Object



561
562
563
564
565
566
567
568
569
570
# File 'app/models/spree/order.rb', line 561

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

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

Associates the specified user with the order.



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

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



378
379
380
# File 'app/models/spree/order.rb', line 378

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

#awaiting_returns?Boolean

Returns:

  • (Boolean)


251
252
253
# File 'app/models/spree/order.rb', line 251

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

#backordered?Boolean

Returns:

  • (Boolean)


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

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

#billing_firstnameObject



418
419
420
# File 'app/models/spree/order.rb', line 418

def billing_firstname
  bill_address.try(:firstname)
end

#billing_lastnameObject



422
423
424
# File 'app/models/spree/order.rb', line 422

def billing_lastname
  bill_address.try(:lastname)
end

#can_add_coupon?Boolean

Returns:

  • (Boolean)


495
496
497
# File 'app/models/spree/order.rb', line 495

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

#can_approve?Boolean

Returns:

  • (Boolean)


576
577
578
# File 'app/models/spree/order.rb', line 576

def can_approve?
  !approved?
end

#can_ship?Boolean

Returns:

  • (Boolean)


329
330
331
# File 'app/models/spree/order.rb', line 329

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)


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

def checkout_allowed?
  line_items.count > 0
end

#clone_billing_addressObject



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

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)


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

def completed?
  completed_at.present?
end

#confirmation_required?Boolean

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

Returns:

  • (Boolean)


197
198
199
200
201
202
203
204
# File 'app/models/spree/order.rb', line 197

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



580
581
582
583
584
# File 'app/models/spree/order.rb', line 580

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

#considered_risky!Object



586
587
588
# File 'app/models/spree/order.rb', line 586

def considered_risky!
  update_column(:considered_risky, true)
end

#contains?(variant) ⇒ Boolean

Returns:

  • (Boolean)


291
292
293
# File 'app/models/spree/order.rb', line 291

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

#contentsObject



255
256
257
# File 'app/models/spree/order.rb', line 255

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

#create_proposed_shipmentsObject



504
505
506
507
508
509
510
511
512
513
514
# File 'app/models/spree/order.rb', line 504

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.



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

def create_tax_charge!
  Spree::TaxRate.adjust(self, line_items)
  Spree::TaxRate.adjust(self, shipments) if shipments.any?
end

#credit_cardsObject



333
334
335
336
# File 'app/models/spree/order.rb', line 333

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

#currencyObject



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

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

#deliver_order_confirmation_emailObject



368
369
370
371
# File 'app/models/spree/order.rb', line 368

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

#display_additional_tax_totalObject



154
155
156
# File 'app/models/spree/order.rb', line 154

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

#display_adjustment_totalObject



146
147
148
# File 'app/models/spree/order.rb', line 146

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

#display_included_tax_totalObject



150
151
152
# File 'app/models/spree/order.rb', line 150

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

#display_item_totalObject



142
143
144
# File 'app/models/spree/order.rb', line 142

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

#display_outstanding_balanceObject



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

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

#display_shipment_totalObject Also known as: display_ship_total



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

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

#display_tax_totalObject



158
159
160
# File 'app/models/spree/order.rb', line 158

def display_tax_total
  Spree::Money.new(included_tax_total + additional_tax_total, { currency: currency })
end

#display_totalObject



167
168
169
# File 'app/models/spree/order.rb', line 167

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

#empty!Object



461
462
463
464
465
466
467
468
469
# File 'app/models/spree/order.rb', line 461

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



430
431
432
433
434
# File 'app/models/spree/order.rb', line 430

def ensure_line_items_are_in_stock
  if insufficient_stock_lines.present?
    errors.add(:base, Spree.t(:insufficient_stock_lines_present)) and return false
  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



528
529
530
531
532
533
534
# File 'app/models/spree/order.rb', line 528

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

#exclude_tax?Boolean

Indicates whether tax should be backed out of the price calcualtions in cases where prices include tax but the customer is not required to pay taxes in that case.

Returns:

  • (Boolean)


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

def exclude_tax?
  return false unless Spree::Config[:prices_inc_tax]
  tax_zone != Zone.default_tax
end

#finalize!Object

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



340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
# File 'app/models/spree/order.rb', line 340

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



300
301
302
# File 'app/models/spree/order.rb', line 300

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

#fulfill!Object



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

def fulfill!
  shipments.each { |shipment| shipment.update!(self) if shipment.persisted? }
  updater.update_shipment_state
  save!
end

#generate_order_number(digits = 9) ⇒ Object



273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'app/models/spree/order.rb', line 273

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)


471
472
473
# File 'app/models/spree/order.rb', line 471

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

#insufficient_stock_linesObject



426
427
428
# File 'app/models/spree/order.rb', line 426

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

#is_risky?Boolean

Returns:

  • (Boolean)


557
558
559
# File 'app/models/spree/order.rb', line 557

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

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



436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
# File 'app/models/spree/order.rb', line 436

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



323
324
325
326
327
# File 'app/models/spree/order.rb', line 323

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

#outstanding_balanceObject



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

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)


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

def outstanding_balance?
 self.outstanding_balance != 0
end

#paid?Boolean

Helper methods for checkout steps

Returns:

  • (Boolean)


374
375
376
# File 'app/models/spree/order.rb', line 374

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)


192
193
194
# File 'app/models/spree/order.rb', line 192

def payment_required?
  total.to_f > 0.0
end

#pending_paymentsObject



382
383
384
# File 'app/models/spree/order.rb', line 382

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



399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
# File 'app/models/spree/order.rb', line 399

def process_payments!
  if pending_payments.empty?
    raise Core::GatewayError.new Spree.t(:no_pending_payments)
  else
    pending_payments.each do |payment|
      break if payment_total >= total

      payment.process!

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

#quantity_of(variant) ⇒ Object



295
296
297
298
# File 'app/models/spree/order.rb', line 295

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

#refresh_shipment_ratesObject



543
544
545
# File 'app/models/spree/order.rb', line 543

def refresh_shipment_rates
  shipments.map &:refresh_rates
end

#reload(options = nil) ⇒ Object



607
608
609
610
# File 'app/models/spree/order.rb', line 607

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

#restart_checkout_flowObject



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

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

#set_shipments_costObject



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

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

#shipped?Boolean

Returns:

  • (Boolean)


500
501
502
# File 'app/models/spree/order.rb', line 500

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

#shipped_shipmentsObject



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

def shipped_shipments
  shipments.shipped
end

#shipping_discountObject



171
172
173
# File 'app/models/spree/order.rb', line 171

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

#shipping_eq_billing_address?Boolean

Returns:

  • (Boolean)


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

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

#state_changed(name) ⇒ Object



475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
# File 'app/models/spree/order.rb', line 475

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



225
226
227
# File 'app/models/spree/order.rb', line 225

def tax_address
  Spree::Config[:tax_using_ship_address] ? ship_address : bill_address
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



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

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

#to_paramObject



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

def to_param
  number.to_s.to_url.upcase
end

#update!Object



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

def update!
  updater.update
end

#update_line_items(line_item_params) ⇒ Object

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



595
596
597
598
599
600
601
602
603
604
605
# File 'app/models/spree/order.rb', line 595

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



229
230
231
# File 'app/models/spree/order.rb', line 229

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