Class: Spree::Order

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

Defined Under Namespace

Modules: Checkout

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Checkout

included

Instance Attribute Details

#use_billingObject

Returns the value of attribute use_billing.



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

def use_billing
  @use_billing
end

Class Method Details

.between(start_date, end_date) ⇒ Object



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

def self.between(start_date, end_date)
  where(:created_at => start_date..end_date)
end

.by_customer(customer) ⇒ Object



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

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

.by_number(number) ⇒ Object



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

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

.by_state(state) ⇒ Object



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

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

.completeObject



92
93
94
# File 'app/models/spree/order.rb', line 92

def self.complete
  where('completed_at IS NOT NULL')
end

.incompleteObject



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

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#updat



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

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

Instance Method Details

#add_variant(variant, quantity = 1) ⇒ Object



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

def add_variant(variant, quantity = 1)
  current_item = find_line_item_by_variant(variant)
  if current_item
    current_item.quantity += quantity
    current_item.save
  else
    current_item = LineItem.new(:quantity => quantity)
    current_item.variant = variant
    current_item.price   = variant.price
    self.line_items << current_item
  end

  self.reload
  current_item
end

#allow_cancel?Boolean

Returns:

  • (Boolean)


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

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

#allow_resume?Boolean

Returns:

  • (Boolean)


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

def allow_resume?
  # we shouldn't allow resume for legacy orders b/c we lack the information necessary to restore to a previous state
  return false if state_changes.empty? || state_changes.last.previous_state.nil?
  true
end

#amountObject

For compatiblity with Calculator::PriceSack



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

def amount
  line_items.sum(&:amount)
end

#associate_user!(user) ⇒ Object

Associates the specified user with the order.



259
260
261
262
263
264
265
# File 'app/models/spree/order.rb', line 259

def associate_user!(user)
  self.user = user
  self.email = user.email
  # disable validations since they can cause issues when associating
  # an incomplete address during the address step
  save(:validate => false)
end

#available_payment_methodsObject



399
400
401
# File 'app/models/spree/order.rb', line 399

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

#available_shipping_methods(display_on = nil) ⇒ Object

Helper methods for checkout steps



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

def available_shipping_methods(display_on = nil)
  return [] unless ship_address
  ShippingMethod.all_available(self, display_on)
end

#backordered?Boolean

Indicates whether there are any backordered InventoryUnits associated with the Order.

Returns:

  • (Boolean)


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

def backordered?
  return false unless Spree::Config[:track_inventory_levels]
  inventory_units.backorder.present?
end

#billing_firstnameObject



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

def billing_firstname
  bill_address.try(:firstname)
end

#billing_lastnameObject



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

def billing_lastname
  bill_address.try(:lastname)
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)


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

def checkout_allowed?
  line_items.count > 0
end

#clear_adjustments!Object

destroy any previous adjustments. Adjustments will be recalculated during order update.



449
450
451
452
# File 'app/models/spree/order.rb', line 449

def clear_adjustments!
  adjustments.tax.each(&:destroy)
  price_adjustments.each(&:destroy)
end

#clone_billing_addressObject



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

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)


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

def completed?
  !! completed_at
end

#confirmation_required?Boolean

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

Returns:

  • (Boolean)


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

def confirmation_required?
  payment_method && payment_method.payment_profiles_supported?
end

#contains?(variant) ⇒ Boolean

Returns:

  • (Boolean)


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

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

#create_shipment!Object

Creates a new shipment (adjustment is created by shipment model)



317
318
319
320
321
322
323
324
325
326
327
# File 'app/models/spree/order.rb', line 317

def create_shipment!
  shipping_method(true)
  if shipment.present?
    shipment.update_attributes!(:shipping_method => shipping_method)
  else
    self.shipments << Shipment.create!({ :order => self,
                                      :shipping_method => shipping_method,
                                      :address => self.ship_address,
                                      :inventory_units => self.inventory_units}, :without_protection => true)
  end
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.



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

def create_tax_charge!
  Spree::TaxRate.adjust(self)
end

#credit_cardsObject



343
344
345
346
# File 'app/models/spree/order.rb', line 343

def credit_cards
  credit_card_ids = payments.from_credit_card.map(&:source_id).uniq
  CreditCard.scoped(:conditions => { :id => credit_card_ids })
end

#deliver_order_confirmation_emailObject



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

def deliver_order_confirmation_email
  begin
    OrderMailer.confirm_email(self).deliver
  rescue Exception => e
    logger.error("#{e.class.name}: #{e.message}")
    logger.error(e.backtrace * "\n")
  end
end

#display_totalObject



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

def display_total
  Spree::Money.new(total)
end

#empty!Object



442
443
444
445
# File 'app/models/spree/order.rb', line 442

def empty!
  line_items.destroy_all
  adjustments.destroy_all
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)


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

def exclude_tax?
  return false unless Spree::Config[:prices_inc_tax]
  return 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



350
351
352
353
354
355
356
357
358
359
360
361
362
363
# File 'app/models/spree/order.rb', line 350

def finalize!
  touch :completed_at
  InventoryUnit.assign_opening_inventory(self)
  # lock any optional adjustments (coupon promotions, etc.)
  adjustments.optional.each { |adjustment| adjustment.update_column('locked', true) }
  deliver_order_confirmation_email

  self.state_changes.create({
    :previous_state => 'cart',
    :next_state     => 'complete',
    :name           => 'order' ,
    :user_id        => self.user_id
  }, :without_protection => true)
end

#find_line_item_by_variant(variant) ⇒ Object



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

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

#generate_order_numberObject

FIXME refactor this method and implement validation using validates_* utilities



268
269
270
271
272
273
274
275
276
# File 'app/models/spree/order.rb', line 268

def generate_order_number
  record = true
  while record
    random = "R#{Array.new(9){rand(9)}.join}"
    record = self.class.where(:number => random).first
  end
  self.number = random if self.number.blank?
  self.number
end

#has_step?(step) ⇒ Boolean

Returns:

  • (Boolean)


454
455
456
# File 'app/models/spree/order.rb', line 454

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

#insufficient_stock_linesObject



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

def insufficient_stock_lines
  line_items.select &:insufficient_stock?
end

#item_countObject

Indicates the number of items in the order



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

def item_count
  line_items.sum(:quantity)
end

#merge!(order) ⇒ Object



435
436
437
438
439
440
# File 'app/models/spree/order.rb', line 435

def merge!(order)
  order.line_items.each do |line_item|
    self.add_variant(line_item.variant, line_item.quantity)
  end
  order.destroy
end

#nameObject



337
338
339
340
341
# File 'app/models/spree/order.rb', line 337

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

#outstanding_balanceObject



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

def outstanding_balance
  total - payment_total
end

#outstanding_balance?Boolean

Returns:

  • (Boolean)


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

def outstanding_balance?
 self.outstanding_balance != 0
end

#paid?Boolean

Returns:

  • (Boolean)


391
392
393
# File 'app/models/spree/order.rb', line 391

def paid?
  payment_state == 'paid'
end

#paymentObject



395
396
397
# File 'app/models/spree/order.rb', line 395

def payment
  payments.first
end

#payment_methodObject



403
404
405
406
407
408
409
# File 'app/models/spree/order.rb', line 403

def payment_method
  if payment and payment.payment_method
    payment.payment_method
  else
    available_payment_methods.first
  end
end

#payment_required?Boolean

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

Returns:

  • (Boolean)


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

def payment_required?
  total.to_f > 0.0
end

#price_adjustment_totalsObject

Array of totals grouped by Adjustment#label. Useful for displaying price adjustments on an invoice. For example, you can display tax breakout for cases where tax is included in price.



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

def price_adjustment_totals
  totals = {}

  price_adjustments.each do |adjustment|
    label = adjustment.label
    totals[label] ||= 0
    totals[label] = totals[label] + adjustment.amount
  end

  totals
end

#price_adjustmentsObject

Array of adjustments that are inclusive in the variant price. Useful for when prices include tax (ex. VAT) and you need to record the tax amount separately.



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

def price_adjustments
  adjustments = []

  line_items.each do |line_item|
    adjustments.concat line_item.adjustments
  end

  adjustments
end

#process_payments!Object



411
412
413
414
415
416
417
# File 'app/models/spree/order.rb', line 411

def process_payments!
  begin
    payments.each(&:process!)
  rescue Core::GatewayError
    !!Spree::Config[:allow_checkout_on_gateway_error]
  end
end

#productsObject



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

def products
  line_items.map { |li| li.variant.product }
end

#quantity_of(variant) ⇒ Object



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

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

#rate_hashObject



381
382
383
384
385
386
387
388
389
# File 'app/models/spree/order.rb', line 381

def rate_hash
  @rate_hash ||= available_shipping_methods(:front_end).collect do |ship_method|
    next unless cost = ship_method.calculator.compute(self)
    ShippingRate.new( :id => ship_method.id,
                      :shipping_method => ship_method,
                      :name => ship_method.name,
                      :cost => cost)
  end.compact.sort_by { |r| r.cost }
end

#remove_invalid_shipments!Object

Clear shipment when transitioning to delivery step of checkout if the current shipping address is not eligible for the existing shipping method



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

def remove_invalid_shipments!
  shipments.each { |s| s.destroy unless s.shipping_method.available_to_order?(self) }
end

#ship_totalObject



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

def ship_total
  adjustments.shipping.map(&:amount).sum
end

#shipmentObject

convenience method since many stores will not allow user to create multiple shipments



279
280
281
# File 'app/models/spree/order.rb', line 279

def shipment
  @shipment ||= shipments.last
end

#tax_totalObject



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

def tax_total
  adjustments.tax.map(&:amount).sum
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



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

def tax_zone
  zone_address = Spree::Config[:tax_using_ship_address] ? ship_address : bill_address
  Zone.match(zone_address) || Zone.default_tax
end

#to_paramObject



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

def to_param
  number.to_s.to_url.upcase
end

#update!Object

This is a multi-purpose method for processing logic related to changes in the Order. It is meant to be called from various observers so that the Order is aware of changes that affect totals and other values stored in the Order. This method should never do anything to the Order that results in a save call on the object (otherwise you will end up in an infinite recursion as the associations try to save and then in turn try to call update! again.)



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'app/models/spree/order.rb', line 194

def update!
  update_totals
  update_payment_state

  # give each of the shipments a chance to update themselves
  shipments.each { |shipment| shipment.update!(self) }#(&:update!)
  update_shipment_state
  update_adjustments
  # update totals a second time in case updated adjustments have an effect on the total
  update_totals

  update_attributes_without_callbacks({
    :payment_state => payment_state,
    :shipment_state => shipment_state,
    :item_total => item_total,
    :adjustment_total => adjustment_total,
    :payment_total => payment_total,
    :total => total
  })

  #ensure checkout payment always matches order total
  if payment and payment.checkout? and payment.amount != total
    payment.update_attributes_without_callbacks(:amount => total)
  end

  update_hooks.each { |hook| self.send hook }
end