Class: Spree::Order
Defined Under Namespace
Modules: Checkout, CurrencyUpdater, Payments
Classes: CannotRebuildShipments, InsufficientStock
Constant Summary
collapse
- ORDER_NUMBER_LENGTH =
9
- ORDER_NUMBER_LETTERS =
false
- ORDER_NUMBER_PREFIX =
'R'
Instance Attribute Summary collapse
Class Method Summary
collapse
Instance Method Summary
collapse
money_methods
#price_from_line_item, #update_line_item_currencies!, #update_line_item_price!
Methods included from Checkout
included
Methods inherited from Base
page
#default_preferences, #defined_preferences, #get_preference, #has_preference!, #has_preference?, #preference_default, #preference_type, #set_preference
Instance Attribute Details
#coupon_code ⇒ Object
Returns the value of attribute coupon_code.
31
32
33
|
# File 'app/models/spree/order.rb', line 31
def coupon_code
@coupon_code
end
|
#temporary_address ⇒ Object
Returns the value of attribute temporary_address.
32
33
34
|
# File 'app/models/spree/order.rb', line 32
def temporary_address
@temporary_address
end
|
#temporary_credit_card ⇒ Object
Returns the value of attribute temporary_credit_card.
32
33
34
|
# File 'app/models/spree/order.rb', line 32
def temporary_credit_card
@temporary_credit_card
end
|
#use_billing ⇒ Object
Returns the value of attribute use_billing.
83
84
85
|
# File 'app/models/spree/order.rb', line 83
def use_billing
@use_billing
end
|
Class Method Details
.by_customer(customer) ⇒ Object
118
119
120
|
# File 'app/models/spree/order.rb', line 118
def self.by_customer(customer)
joins(:user).where("#{Spree.user_class.table_name}.email" => customer)
end
|
.by_number(number) ⇒ Object
105
106
107
|
# File 'app/models/spree/order.rb', line 105
def self.by_number(number)
where(number: number)
end
|
.by_state(state) ⇒ Object
122
123
124
|
# File 'app/models/spree/order.rb', line 122
def self.by_state(state)
where(state: state)
end
|
.complete ⇒ Object
126
127
128
|
# File 'app/models/spree/order.rb', line 126
def self.complete
where.not(completed_at: nil)
end
|
.incomplete ⇒ Object
130
131
132
|
# File 'app/models/spree/order.rb', line 130
def self.incomplete
where(completed_at: nil)
end
|
.register_line_item_comparison_hook(hook) ⇒ Object
Use this method in other gems that wish to register their own custom logic that should be called when determining if two line items are equal.
142
143
144
|
# File 'app/models/spree/order.rb', line 142
def self.register_line_item_comparison_hook(hook)
self.line_item_comparison_hooks.add(hook)
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
136
137
138
|
# File 'app/models/spree/order.rb', line 136
def self.register_update_hook(hook)
self.update_hooks.add(hook)
end
|
Instance Method Details
#add_store_credit_payments ⇒ Object
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
|
# File 'app/models/spree/order.rb', line 640
def add_store_credit_payments
payments.store_credits.checkout.each(&:invalidate!)
authorized_total = payments.pending.sum(:amount)
remaining_total = outstanding_balance - authorized_total
if user && user.store_credits.any?
payment_method = Spree::PaymentMethod::StoreCredit.first
user.store_credits.order_by_priority.each do |credit|
break if remaining_total.zero?
next if credit.amount_remaining.zero?
amount_to_take = [credit.amount_remaining, remaining_total].min
payments.create!(source: credit,
payment_method: payment_method,
amount: amount_to_take,
state: 'checkout',
response_code: credit.generate_authorization_code)
remaining_total -= amount_to_take
end
end
other_payments = payments.checkout.not_store_credits
if remaining_total.zero?
other_payments.each(&:invalidate!)
elsif other_payments.size == 1
other_payments.first.update_attributes!(amount: remaining_total)
end
payments.reset
if payments.where(state: %w(checkout pending)).sum(:amount) != total
errors.add(:base, Spree.t("store_credit.errors.unable_to_fund")) and return false
end
end
|
#all_adjustments ⇒ Object
146
147
148
149
|
# File 'app/models/spree/order.rb', line 146
def all_adjustments
Adjustment.where("order_id = :order_id OR (adjustable_id = :order_id AND adjustable_type = 'Spree::Order')",
order_id: self.id)
end
|
#all_inventory_units_returned? ⇒ Boolean
231
232
233
|
# File 'app/models/spree/order.rb', line 231
def all_inventory_units_returned?
inventory_units.all? { |inventory_unit| inventory_unit.returned? }
end
|
#allow_cancel? ⇒ Boolean
226
227
228
229
|
# File 'app/models/spree/order.rb', line 226
def allow_cancel?
return false unless completed? and state != 'canceled'
shipment_state.nil? || %w{ready backorder pending}.include?(shipment_state)
end
|
#amount ⇒ Object
For compatiblity with Calculator::PriceSack
152
153
154
|
# File 'app/models/spree/order.rb', line 152
def amount
line_items.inject(0.0) { |sum, li| sum + li.amount }
end
|
520
521
522
523
524
525
|
# File 'app/models/spree/order.rb', line 520
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
|
#approved? ⇒ Boolean
579
580
581
|
# File 'app/models/spree/order.rb', line 579
def approved?
!!self.approved_at
end
|
#associate_user!(user, override_email = true) ⇒ Object
Associates the specified user with the order.
248
249
250
251
252
253
254
255
256
257
258
259
260
|
# File 'app/models/spree/order.rb', line 248
def associate_user!(user, override_email = true)
self.user = user
attrs_to_set = { user_id: user.try(:id) }
attrs_to_set[:email] = user.try(:email) if override_email
attrs_to_set[:created_by_id] = user.try(:id) if self.created_by.blank?
if persisted?
self.class.unscoped.where(id: id).update_all(attrs_to_set)
end
assign_attributes(attrs_to_set)
end
|
#available_payment_methods ⇒ Object
399
400
401
|
# File 'app/models/spree/order.rb', line 399
def available_payment_methods
@available_payment_methods ||= (PaymentMethod.available(:front_end) + PaymentMethod.available(:both)).uniq
end
|
#backordered? ⇒ Boolean
194
195
196
|
# File 'app/models/spree/order.rb', line 194
def backordered?
shipments.any?(&:backordered?)
end
|
#billing_firstname ⇒ Object
403
404
405
|
# File 'app/models/spree/order.rb', line 403
def billing_firstname
bill_address.try(:firstname)
end
|
#billing_lastname ⇒ Object
407
408
409
|
# File 'app/models/spree/order.rb', line 407
def billing_lastname
bill_address.try(:lastname)
end
|
#can_add_coupon? ⇒ Boolean
493
494
495
|
# File 'app/models/spree/order.rb', line 493
def can_add_coupon?
Spree::Promotion.order_activatable?(self)
end
|
#can_approve? ⇒ Boolean
583
584
585
|
# File 'app/models/spree/order.rb', line 583
def can_approve?
!approved?
end
|
#can_ship? ⇒ Boolean
347
348
349
|
# File 'app/models/spree/order.rb', line 347
def can_ship?
self.complete? || self.resumed? || self.awaiting_return? || self.returned?
end
|
#canceled_by(user) ⇒ Object
569
570
571
572
573
574
575
576
577
|
# File 'app/models/spree/order.rb', line 569
def canceled_by(user)
self.transaction do
cancel!
self.update_columns(
canceler_id: user.id,
canceled_at: Time.now,
)
end
end
|
#cancellations ⇒ Object
243
244
245
|
# File 'app/models/spree/order.rb', line 243
def cancellations
@cancellations ||= Spree::OrderCancellations.new(self)
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.
181
182
183
|
# File 'app/models/spree/order.rb', line 181
def checkout_allowed?
line_items.count > 0
end
|
#clone_billing_address ⇒ Object
217
218
219
220
221
222
223
224
|
# File 'app/models/spree/order.rb', line 217
def clone_billing_address
if bill_address and self.ship_address.nil?
self.ship_address = bill_address.dup
else
self.ship_address.attributes = bill_address.attributes.except('id', 'updated_at', 'created_at')
end
true
end
|
#completed? ⇒ Boolean
173
174
175
|
# File 'app/models/spree/order.rb', line 173
def completed?
completed_at.present?
end
|
#confirmation_required? ⇒ Boolean
190
191
192
|
# File 'app/models/spree/order.rb', line 190
def confirmation_required?
true
end
|
#contains?(variant, options = {}) ⇒ Boolean
287
288
289
|
# File 'app/models/spree/order.rb', line 287
def contains?(variant, options = {})
find_line_item_by_variant(variant, options).present?
end
|
#contents ⇒ Object
235
236
237
|
# File 'app/models/spree/order.rb', line 235
def contents
@contents ||= Spree::OrderContents.new(self)
end
|
#covered_by_store_credit? ⇒ Boolean
Also known as:
covered_by_store_credit
682
683
684
685
|
# File 'app/models/spree/order.rb', line 682
def covered_by_store_credit?
return false unless user
user.total_available_store_credit >= total
end
|
#create_proposed_shipments ⇒ Object
508
509
510
511
512
513
514
515
516
517
518
|
# File 'app/models/spree/order.rb', line 508
def create_proposed_shipments
if completed?
raise CannotRebuildShipments.new(Spree.t(:cannot_rebuild_shipments_order_completed))
elsif shipments.any? { |s| !s.pending? }
raise CannotRebuildShipments.new(Spree.t(:cannot_rebuild_shipments_shipments_not_pending))
else
adjustments.shipping.destroy_all
shipments.destroy_all
self.shipments = Spree::Stock::Coordinator.new(self).shipments
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.
322
323
324
325
326
327
|
# File 'app/models/spree/order.rb', line 322
def create_tax_charge!
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_cards ⇒ Object
351
352
353
354
|
# File 'app/models/spree/order.rb', line 351
def credit_cards
credit_card_ids = payments.from_credit_card.pluck(:source_id).uniq
CreditCard.where(id: credit_card_ids)
end
|
#currency ⇒ Object
161
162
163
|
# File 'app/models/spree/order.rb', line 161
def currency
self[:currency] || Spree::Config[:currency]
end
|
#deliver_order_confirmation_email ⇒ Object
389
390
391
392
|
# File 'app/models/spree/order.rb', line 389
def deliver_order_confirmation_email
OrderMailer.confirm_email(self.id).deliver_now
update_column(:confirmation_delivered, true)
end
|
#display_store_credit_remaining_after_capture ⇒ Object
709
710
711
|
# File 'app/models/spree/order.rb', line 709
def display_store_credit_remaining_after_capture
Spree::Money.new(total_available_store_credit - total_applicable_store_credit, { currency: currency })
end
|
#display_total_applicable_store_credit ⇒ Object
705
706
707
|
# File 'app/models/spree/order.rb', line 705
def display_total_applicable_store_credit
Spree::Money.new(-total_applicable_store_credit, { currency: currency })
end
|
#empty! ⇒ Object
459
460
461
462
463
464
465
466
467
|
# File 'app/models/spree/order.rb', line 459
def empty!
line_items.destroy_all
updater.update_item_count
adjustments.destroy_all
shipments.destroy_all
update_totals
persist_totals
end
|
#ensure_line_item_variants_are_not_deleted ⇒ Object
Check to see if any line item variants are soft, deleted. If so add error and restart checkout.
418
419
420
421
422
423
424
425
426
|
# File 'app/models/spree/order.rb', line 418
def ensure_line_item_variants_are_not_deleted
if line_items.any? { |li| li.variant.paranoia_destroyed? }
errors.add(:base, Spree.t(:deleted_variants_present))
restart_checkout_flow
false
else
true
end
end
|
#ensure_shipping_address ⇒ Object
502
503
504
505
506
|
# File 'app/models/spree/order.rb', line 502
def ensure_shipping_address
unless ship_address && ship_address.valid?
errors.add(:base, Spree.t(:ship_address_required)) and return false
end
end
|
#ensure_updated_shipments ⇒ Object
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
532
533
534
535
536
537
538
539
|
# File 'app/models/spree/order.rb', line 532
def ensure_updated_shipments
if !completed? && shipments.all?(&:pending?)
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
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
|
# File 'app/models/spree/order.rb', line 363
def finalize!
all_adjustments.each{|a| a.close}
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?
end
|
#find_line_item_by_variant(variant, options = {}) ⇒ Object
296
297
298
299
300
301
|
# File 'app/models/spree/order.rb', line 296
def find_line_item_by_variant(variant, options = {})
line_items.detect { |line_item|
line_item.variant_id == variant.id &&
line_item_options_match(line_item, options)
}
end
|
#fulfill! ⇒ Object
383
384
385
386
387
|
# File 'app/models/spree/order.rb', line 383
def fulfill!
shipments.each { |shipment| shipment.update!(self) if shipment.persisted? }
updater.update_shipment_state
save!
end
|
#fully_discounted? ⇒ Boolean
Also known as:
fully_discounted
619
620
621
|
# File 'app/models/spree/order.rb', line 619
def fully_discounted?
adjustment_total + line_items.map(&:final_amount).sum == 0.0
end
|
#generate_order_number(options = {}) ⇒ Object
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
|
# File 'app/models/spree/order.rb', line 262
def generate_order_number(options = {})
options[:length] ||= ORDER_NUMBER_LENGTH
options[:letters] ||= ORDER_NUMBER_LETTERS
options[:prefix] ||= ORDER_NUMBER_PREFIX
possible = (0..9).to_a
possible += ('A'..'Z').to_a if options[:letters]
self.number ||= loop do
random = "#{options[:prefix]}#{(0...options[:length]).map { possible.shuffle.first }.join}"
if self.class.exists?(number: random)
options[:length] += 1 if self.class.count > (10 ** options[:length] / 2)
else
break random
end
end
end
|
609
610
611
612
|
# File 'app/models/spree/order.rb', line 609
def has_non_reimbursement_related_refunds?
refunds.non_reimbursement.exists? ||
payments.offset_payment.exists?
end
|
#has_step?(step) ⇒ Boolean
469
470
471
|
# File 'app/models/spree/order.rb', line 469
def has_step?(step)
checkout_steps.include?(step)
end
|
#insufficient_stock_lines ⇒ Object
411
412
413
|
# File 'app/models/spree/order.rb', line 411
def insufficient_stock_lines
line_items.select(&:insufficient_stock?)
end
|
#is_risky? ⇒ Boolean
565
566
567
|
# File 'app/models/spree/order.rb', line 565
def is_risky?
self.payments.risky.count > 0
end
|
#line_item_options_match(line_item, options) ⇒ Object
This method enables extensions to participate in the “Are these line items equal” decision.
When adding to cart, an extension would send something like: params=…
and would provide:
def product_customizations_match
312
313
314
315
316
317
318
|
# File 'app/models/spree/order.rb', line 312
def line_item_options_match(line_item, options)
return true unless options
self.line_item_comparison_hooks.all? { |hook|
self.send(hook, line_item, options)
}
end
|
#merge!(order, user = nil) ⇒ Object
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
|
# File 'app/models/spree/order.rb', line 428
def merge!(order, user = nil)
order.line_items.each do |other_order_line_item|
next unless other_order_line_item.currency == currency
current_line_item = self.line_items.detect { |my_li|
my_li.variant == other_order_line_item.variant &&
self.line_item_comparison_hooks.all? { |hook|
self.send(hook, my_li, other_order_line_item.serializable_hash)
}
}
if current_line_item
current_line_item.quantity += other_order_line_item.quantity
current_line_item.save!
else
other_order_line_item.order_id = self.id
other_order_line_item.save!
end
end
self.associate_user!(user) if !self.user && !user.blank?
updater.update
order.line_items.reload
order.destroy
end
|
#name ⇒ Object
341
342
343
344
345
|
# File 'app/models/spree/order.rb', line 341
def name
if (address = bill_address || ship_address)
"#{address.firstname} #{address.lastname}"
end
end
|
#order_total_after_store_credit ⇒ Object
693
694
695
|
# File 'app/models/spree/order.rb', line 693
def order_total_after_store_credit
total - total_applicable_store_credit
end
|
#outstanding_balance ⇒ Object
329
330
331
332
333
334
335
|
# File 'app/models/spree/order.rb', line 329
def outstanding_balance
if state == 'canceled'
-1 * payment_total
else
total - payment_total
end
end
|
#outstanding_balance? ⇒ Boolean
337
338
339
|
# File 'app/models/spree/order.rb', line 337
def outstanding_balance?
self.outstanding_balance != 0
end
|
#paid? ⇒ Boolean
Helper methods for checkout steps
395
396
397
|
# File 'app/models/spree/order.rb', line 395
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
186
187
188
|
# File 'app/models/spree/order.rb', line 186
def payment_required?
total.to_f > 0.0
end
|
#pre_tax_item_amount ⇒ Object
Sum of all line item amounts pre-tax
157
158
159
|
# File 'app/models/spree/order.rb', line 157
def pre_tax_item_amount
line_items.to_a.sum(&:pre_tax_amount)
end
|
#quantity ⇒ Object
605
606
607
|
# File 'app/models/spree/order.rb', line 605
def quantity
line_items.sum(:quantity)
end
|
#quantity_of(variant, options = {}) ⇒ Object
291
292
293
294
|
# File 'app/models/spree/order.rb', line 291
def quantity_of(variant, options = {})
line_item = find_line_item_by_variant(variant, options)
line_item ? line_item.quantity : 0
end
|
#refresh_shipment_rates ⇒ Object
551
552
553
|
# File 'app/models/spree/order.rb', line 551
def refresh_shipment_rates
shipments.map(&:refresh_rates)
end
|
#reload(options = nil) ⇒ Object
600
601
602
603
|
# File 'app/models/spree/order.rb', line 600
def reload(options=nil)
remove_instance_variable(:@tax_zone) if defined?(@tax_zone)
super
end
|
#restart_checkout_flow ⇒ Object
541
542
543
544
545
546
547
548
549
|
# File 'app/models/spree/order.rb', line 541
def restart_checkout_flow
return if self.state == 'cart'
self.update_columns(
state: 'cart',
updated_at: Time.now,
)
self.next! if self.line_items.size > 0
end
|
#set_shipments_cost ⇒ Object
559
560
561
562
563
|
# File 'app/models/spree/order.rb', line 559
def set_shipments_cost
shipments.each(&:update_amounts)
updater.update_shipment_total
persist_totals
end
|
#shipped? ⇒ Boolean
498
499
500
|
# File 'app/models/spree/order.rb', line 498
def shipped?
%w(partial shipped).include?(shipment_state)
end
|
#shipped_shipments ⇒ Object
283
284
285
|
# File 'app/models/spree/order.rb', line 283
def shipped_shipments
shipments.shipped
end
|
#shipping ⇒ Object
239
240
241
|
# File 'app/models/spree/order.rb', line 239
def shipping
@shipping ||= Spree::OrderShipping.new(self)
end
|
#shipping_discount ⇒ Object
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
555
556
557
|
# File 'app/models/spree/order.rb', line 555
def shipping_eq_billing_address?
(bill_address.empty? && ship_address.empty?) || bill_address.same_as?(ship_address)
end
|
#state_changed(name) ⇒ Object
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
|
# File 'app/models/spree/order.rb', line 473
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_address ⇒ Object
Returns the address for taxation based on configuration
205
206
207
|
# File 'app/models/spree/order.rb', line 205
def tax_address
Spree::Config[:tax_using_ship_address] ? ship_address : bill_address
end
|
#tax_total ⇒ Object
636
637
638
|
# File 'app/models/spree/order.rb', line 636
def tax_total
additional_tax_total + included_tax_total
end
|
#tax_zone ⇒ Object
Returns the relevant zone (if any) to be used for taxation purposes. Uses default tax zone unless there is a specific match
200
201
202
|
# File 'app/models/spree/order.rb', line 200
def tax_zone
@tax_zone ||= Zone.match(tax_address) || Zone.default_tax
end
|
#to_param ⇒ Object
169
170
171
|
# File 'app/models/spree/order.rb', line 169
def to_param
number.to_s.to_url.upcase
end
|
#token ⇒ Object
614
615
616
617
|
# File 'app/models/spree/order.rb', line 614
def token
ActiveSupport::Deprecation.warn("Spree::Order#token is DEPRECATED, please use #guest_token instead.", caller)
guest_token
end
|
#total_applicable_store_credit ⇒ Object
697
698
699
700
701
702
703
|
# File 'app/models/spree/order.rb', line 697
def total_applicable_store_credit
if confirm? || complete?
payments.store_credits.valid.sum(:amount)
else
[total, (user.try(:total_available_store_credit) || 0.0)].min
end
end
|
#total_available_store_credit ⇒ Object
688
689
690
691
|
# File 'app/models/spree/order.rb', line 688
def total_available_store_credit
return 0.0 unless user
user.total_available_store_credit
end
|
#unreturned_exchange? ⇒ Boolean
624
625
626
627
628
629
630
631
632
633
634
|
# File 'app/models/spree/order.rb', line 624
def unreturned_exchange?
shipment = self.shipments.first
shipment.present? ? (shipment.created_at < self.created_at - 1) : false
end
|
#update! ⇒ Object
213
214
215
|
# File 'app/models/spree/order.rb', line 213
def update!
updater.update
end
|
#update_line_items(line_item_params) ⇒ Object
moved from api order_decorator. This is a better place for it.
588
589
590
591
592
593
594
595
596
597
598
|
# File 'app/models/spree/order.rb', line 588
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
|
#updater ⇒ Object
209
210
211
|
# File 'app/models/spree/order.rb', line 209
def updater
@updater ||= OrderUpdater.new(self)
end
|
#valid_credit_cards ⇒ Object
356
357
358
359
|
# File 'app/models/spree/order.rb', line 356
def valid_credit_cards
credit_card_ids = payments.from_credit_card.valid.pluck(:source_id).uniq
CreditCard.where(id: credit_card_ids)
end
|