Class: Order

Inherits:
ActiveRecord::Base
  • Object
show all
Defined in:
app/models/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.



183
184
185
# File 'app/models/order.rb', line 183

def use_billing
  @use_billing
end

Class Method Details

.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



52
53
54
# File 'app/models/order.rb', line 52

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

Instance Method Details

#add_variant(variant, quantity = 1) ⇒ Object



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

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

  current_item
end

#allow_cancel?Boolean

Returns:

  • (Boolean)


196
197
198
199
# File 'app/models/order.rb', line 196

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

#allow_resume?Boolean

Returns:

  • (Boolean)


201
202
203
204
205
# File 'app/models/order.rb', line 201

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_events.empty? || state_events.last.previous_state.nil?
  true
end

#available_payment_methodsObject



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

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

#available_shipping_methods(display_on = nil) ⇒ Object

Helper methods for checkout steps



335
336
337
338
# File 'app/models/order.rb', line 335

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)


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

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

#billing_firstnameObject



367
368
369
# File 'app/models/order.rb', line 367

def billing_firstname
  bill_address.try(:firstname)
end

#billing_lastnameObject



371
372
373
# File 'app/models/order.rb', line 371

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)


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

def checkout_allowed?
  line_items.count > 0
end

#clone_billing_addressObject



185
186
187
188
189
190
191
192
# File 'app/models/order.rb', line 185

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)


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

def completed?
  !! completed_at
end

#contains?(variant) ⇒ Boolean

Returns:

  • (Boolean)


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

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)



280
281
282
283
284
285
286
287
288
289
290
# File 'app/models/order.rb', line 280

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

end

#create_tax_charge!Object

Creates a new tax charge if applicable. Uses the highest possible matching rate and destroys any previous tax charges if they were created by rates that no longer apply. for the vat case adjutments according to default country are created



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

def create_tax_charge!
  adjustments.tax.each {|e| e.destroy }
  matching_rates = TaxRate.match(ship_address)
  if matching_rates.empty? and Spree::Config[:show_price_inc_vat]
  # somebody may be able to make the search shorter here , some unremember bug caused this
    matching_rates = TaxRate.all.select{|rate| # get all rates that apply to default country
        rate.zone.country_list.collect{|c| c.id}.include?(Spree::Config[:default_country_id]) }
  end
  matching_rates.each do |rate|
    rate.create_adjustment( "#{rate.calculator.description} #{rate.amount*100}%" , self, self, true)
  end
end

#creditcardsObject



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

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

#finalize!Object

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



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

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) }
  OrderMailer.confirm_email(self).deliver

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

#generate_order_numberObject

FIXME refactor this method and implement validation using validates_* utilities



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

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

#insufficient_stock_linesObject



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

def insufficient_stock_lines
  line_items.select &:insufficient_stock?
end

#ip_addressObject

delegate :ip_address, :to => :checkout



35
36
37
# File 'app/models/order.rb', line 35

def ip_address
  '192.168.1.100'
end

#item_countObject

Indicates the number of items in the order



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

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

#nameObject



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

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

#outstanding_balanceObject



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

def outstanding_balance
  total - payment_total
end

#outstanding_balance?Boolean

Returns:

  • (Boolean)


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

def outstanding_balance?
 self.outstanding_balance != 0
end

#paymentObject



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

def payment
  payments.first
end

#payment_methodObject



359
360
361
362
363
364
365
# File 'app/models/order.rb', line 359

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)


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

def payment_required?
  total.to_f > 0.0
end

#process_payments!Object



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

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

#productsObject



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

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

#rate_hashObject



340
341
342
343
344
345
346
347
348
349
# File 'app/models/order.rb', line 340

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

#restore_stateObject



168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'app/models/order.rb', line 168

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

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

end

#ship_totalObject



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

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

#shipmentObject

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



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

def shipment
  @shipment ||= shipments.last
end

#tax_totalObject



259
260
261
# File 'app/models/order.rb', line 259

def tax_total
  adjustments.tax.map(&:amount).sum
end

#to_paramObject



56
57
58
# File 'app/models/order.rb', line 56

def to_param
  number.to_s.parameterize.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.)



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'app/models/order.rb', line 140

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