Class: Spree::Order

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

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#use_billingObject

Returns the value of attribute use_billing.



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

def use_billing
  @use_billing
end

Class Method Details

.between(start_date, end_date) ⇒ Object



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

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

.by_customer(customer) ⇒ Object



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

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

.by_number(number) ⇒ Object



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

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

.by_state(state) ⇒ Object



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

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

.completeObject



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

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

.incompleteObject



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

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



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

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

Instance Method Details

#add_variant(variant, quantity = 1) ⇒ Object



274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'app/models/spree/order.rb', line 274

def add_variant(variant, quantity = 1)
  current_item = contains?(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

  # populate line_items attributes for additional_fields entries
  # that have populate => [:line_item]
  Variant.additional_fields.select { |f| !f[:populate].nil? && f[:populate].include?(:line_item) }.each do |field|
    value = ''

    if field[:only].nil? || field[:only].include?(:variant)
      value = variant.send(field[:name].gsub(' ', '_').downcase)
    elsif field[:only].include?(:product)
      value = variant.product.send(field[:name].gsub(' ', '_').downcase)
    end
    current_item.update_attribute(field[:name].gsub(' ', '_').downcase, value)
  end

  self.reload
  current_item
end

#allow_cancel?Boolean

Returns:

  • (Boolean)


263
264
265
266
# File 'app/models/spree/order.rb', line 263

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

#allow_resume?Boolean

Returns:

  • (Boolean)


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

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



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

def amount
  line_items.map(&:amount).sum
end

#available_payment_methodsObject



434
435
436
# File 'app/models/spree/order.rb', line 434

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

#available_shipping_methods(display_on = nil) ⇒ Object

Helper methods for checkout steps



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

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)


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

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

#billing_firstnameObject



446
447
448
# File 'app/models/spree/order.rb', line 446

def billing_firstname
  bill_address.try(:firstname)
end

#billing_lastnameObject



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

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)


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

def checkout_allowed?
  line_items.count > 0
end

#clone_billing_addressObject



254
255
256
257
258
259
260
261
# File 'app/models/spree/order.rb', line 254

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)


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

def completed?
  !! completed_at
end

#contains?(variant) ⇒ Boolean

Returns:

  • (Boolean)


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

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

#create_shipment!Object

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



347
348
349
350
351
352
353
354
355
356
357
358
# File 'app/models/spree/order.rb', line 347

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.



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

def create_tax_charge!
  # destroy any previous adjustments (eveything is recalculated from scratch)
  adjustments.tax.each(&:destroy)
  price_adjustments.each(&:destroy)

  TaxRate.match(self).each { |rate| rate.adjust(self) }
end

#creditcardsObject



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

def creditcards
  creditcard_ids = payments.from_creditcard.map(&:source_id).uniq
  Creditcard.scoped(:conditions => { :id => creditcard_ids })
end

#deliver_order_confirmation_emailObject



400
401
402
403
404
405
406
407
# File 'app/models/spree/order.rb', line 400

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

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


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

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



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

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

  self.state_changes.create({
    :previous_state => 'cart',
    :next_state     => 'complete',
    :name           => 'order' ,
    :user_id        => (User.respond_to?(:current) && User.current.try(:id)) || self.user_id
  }, :without_protection => true)
end

#generate_order_numberObject

FIXME refactor this method and implement validation using validates_* utilities



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

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

#insufficient_stock_linesObject



458
459
460
# File 'app/models/spree/order.rb', line 458

def insufficient_stock_lines
  line_items.select &:insufficient_stock?
end

#item_countObject

Indicates the number of items in the order



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

def item_count
  line_items.map(&:quantity).sum
end

#merge!(order) ⇒ Object



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

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

#nameObject



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

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

#outstanding_balanceObject



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

def outstanding_balance
  total - payment_total
end

#outstanding_balance?Boolean

Returns:

  • (Boolean)


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

def outstanding_balance?
 self.outstanding_balance != 0
end

#paid?Boolean

Returns:

  • (Boolean)


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

def paid?
  payment_state == 'paid'
end

#paymentObject



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

def payment
  payments.first
end

#payment_methodObject



438
439
440
441
442
443
444
# File 'app/models/spree/order.rb', line 438

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)


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

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.



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

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.



182
183
184
185
186
187
188
189
190
# File 'app/models/spree/order.rb', line 182

def price_adjustments
  adjustments = []

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

  adjustments
end

#process_payments!Object



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

def process_payments!
  ret = payments.each(&:process!)
end

#productsObject



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

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

#quantity_of(variant) ⇒ Object



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

def quantity_of(variant)
  line_item = line_items.find { |line_item| line_item.variant_id == variant.id }
  line_item ? line_item.quantity : 0
end

#rate_hashObject



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

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

#restore_stateObject



238
239
240
241
242
243
244
245
246
247
248
249
# File 'app/models/spree/order.rb', line 238

def restore_state
  # pop the resume event so we can see what the event before that was
  state_changes.pop if state_changes.last.name == 'resume'
  update_attribute('state', state_changes.last.previous_state)

  if paid?
    raise 'do something with inventory'
    #Spree::InventoryUnit.assign_opening_inventory(self) if inventory_units.empty?
    #shipment.inventory_units = inventory_units
    #shipment.ready!
  end
end

#ship_totalObject



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

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

#shipmentObject

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



315
316
317
# File 'app/models/spree/order.rb', line 315

def shipment
  @shipment ||= shipments.last
end

#tax_totalObject



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

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



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

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



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

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.)



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'app/models/spree/order.rb', line 210

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