Class: Spree::Order

Inherits:
Object
  • Object
show all
Extended by:
DisplayMoney
Includes:
MemoizedData, Metadata, Metafields, MultiSearchable, NumberAsParam, NumberIdentifier, AddressBook, Checkout, CurrencyUpdater, Digital, Emails, GiftCard, Payments, StoreCredit, Webhooks, Security::Orders, SingleStoreResource, VendorConcern
Defined in:
app/models/spree/order.rb,
app/models/spree/order/emails.rb,
app/models/spree/order/digital.rb,
app/models/spree/order/checkout.rb,
app/models/spree/order/payments.rb,
app/models/spree/order/webhooks.rb,
app/models/spree/order/gift_card.rb,
app/models/spree/order/address_book.rb,
app/models/spree/order/store_credit.rb,
app/models/spree/order/currency_updater.rb

Defined Under Namespace

Modules: AddressBook, Checkout, CurrencyUpdater, Digital, Emails, GiftCard, Payments, StoreCredit, Webhooks

Constant Summary collapse

PAYMENT_STATES =
%w(balance_due credit_owed failed paid void)
SHIPMENT_STATES =
%w(backorder canceled partial pending ready shipped)
LINE_ITEM_REMOVABLE_STATES =
%w(cart address delivery payment confirm resumed)
MEMOIZED_METHODS =
%w(tax_zone)
MONEY_THRESHOLD =
100_000_000
MONEY_VALIDATION =
{
  presence: true,
  numericality: {
    greater_than: -MONEY_THRESHOLD,
    less_than: MONEY_THRESHOLD,
    allow_blank: true
  },
  format: { with: /\A-?\d+(?:\.\d{1,2})?\z/, allow_blank: true }
}.freeze
POSITIVE_MONEY_VALIDATION =
MONEY_VALIDATION.deep_dup.tap do |validation|
  validation.fetch(:numericality)[:greater_than_or_equal_to] = 0
end.freeze
NEGATIVE_MONEY_VALIDATION =
MONEY_VALIDATION.deep_dup.tap do |validation|
  validation.fetch(:numericality)[:less_than_or_equal_to] = 0
end.freeze
ASSOCIATED_USER_ATTRIBUTES =
[:user_id, :email, :created_by_id, :bill_address_id, :ship_address_id]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from DisplayMoney

money_methods

Methods included from GiftCard

#apply_gift_card, #gift_card_total, #recalculate_gift_card, #redeem_gift_card, #remove_gift_card

Methods included from Webhooks

#send_order_canceled_webhook, #send_order_placed_webhook, #send_order_resumed_webhook

Methods included from Emails

#deliver_order_confirmation_email, #deliver_store_owner_order_notification_email, #deliver_store_owner_order_notification_email?, #send_cancel_email

Methods included from AddressBook

#bill_address_attributes=, #bill_address_id=, #clone_billing_address, #clone_shipping_address, #ship_address_attributes=, #ship_address_id=

Methods included from StoreCredit

#add_store_credit_payments, #available_store_credits, #could_use_store_credit?, #covered_by_store_credit?, #display_order_total_after_store_credit, #display_store_credit_remaining_after_capture, #display_total_applicable_store_credit, #display_total_applied_store_credit, #display_total_available_store_credit, #display_total_minus_store_credits, #order_total_after_store_credit, #remove_store_credit_payments, #total_applicable_store_credit, #total_applied_store_credit, #total_available_store_credit, #total_minus_store_credits, #using_store_credit?

Methods included from Digital

#create_digital_links, #digital?, #digital_line_items, #digital_links, #some_digital?, #with_digital_assets?

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.



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

def coupon_code
  @coupon_code
end

#temporary_addressObject

Returns the value of attribute temporary_address.



94
95
96
# File 'app/models/spree/order.rb', line 94

def temporary_address
  @temporary_address
end

#temporary_credit_cardObject

Returns the value of attribute temporary_credit_card.



94
95
96
# File 'app/models/spree/order.rb', line 94

def temporary_credit_card
  @temporary_credit_card
end

#use_billingObject

Returns the value of attribute use_billing.



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

def use_billing
  @use_billing
end

#use_shippingObject

Returns the value of attribute use_shipping.



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

def use_shipping
  @use_shipping
end

Class Method Details

.multi_search(query) ⇒ Object



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 216

def self.multi_search(query)
  sanitized_query = sanitize_query_for_multi_search(query)
  return none if query.blank?

  query_pattern = "%#{sanitized_query}%"

  conditions = []
  conditions << arel_table[:number].lower.matches(query_pattern)

  conditions << multi_search_condition(Spree::Address, :firstname, sanitized_query)
  conditions << multi_search_condition(Spree::Address, :lastname, sanitized_query)

  full_name = NameOfPerson::PersonName.full(sanitized_query)

  if full_name.first.present? && full_name.last.present?
    conditions << multi_search_condition(Spree::Address, :firstname, full_name.first)
    conditions << multi_search_condition(Spree::Address, :lastname, full_name.last)
  end

  left_joins(:bill_address).where(arel_table[:email].lower.eq(query.downcase)).or(where(conditions.reduce(:or)))
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



240
241
242
# File 'app/models/spree/order.rb', line 240

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

Instance Method Details

#all_inventory_units_returned?Boolean

Returns:



387
388
389
# File 'app/models/spree/order.rb', line 387

def all_inventory_units_returned?
  inventory_units.all?(&:returned?)
end

#all_line_itemsObject



901
902
903
# File 'app/models/spree/order.rb', line 901

def all_line_items
  line_items
end

#all_line_items_invalid?Boolean

Checks if all line items cannot be shipped

Returns:



682
683
684
# File 'app/models/spree/order.rb', line 682

def all_line_items_invalid?
  line_items_without_shipping_rates.size == line_items.count
end

#allow_cancel?Boolean

Returns:



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

def allow_cancel?
  return false if !completed? || canceled?

  shipment_state.nil? || %w{ready backorder pending canceled}.include?(shipment_state)
end

#amountObject

For compatibility with Calculator::PriceSack



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

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

#analytics_subtotalFloat

Returns the subtotal used for analytics integrations It’s a sum of the item total and the promo total

Returns:

  • (Float)


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

def analytics_subtotal
  (item_total + line_items.sum(:promo_total)).to_f
end

#apply_free_shipping_promotionsObject



686
687
688
689
690
691
# File 'app/models/spree/order.rb', line 686

def apply_free_shipping_promotions
  Spree::PromotionHandler::FreeShipping.new(self).activate
  shipments.each { |shipment| Spree::Adjustable::AdjustmentsUpdater.update(shipment) }
  create_shipment_tax_charge!
  update_with_updater!
end

#apply_unassigned_promotionsObject

Applies user promotions when login after filling the cart



694
695
696
# File 'app/models/spree/order.rb', line 694

def apply_unassigned_promotions
  ::Spree::PromotionHandler::Cart.new(self).activate
end

#approve!Object



809
810
811
812
813
814
# File 'app/models/spree/order.rb', line 809

def approve!
  update_column(:considered_risky, false)

  # Manually publish update event since update_column bypasses callbacks
  publish_event('order.approved')
end

#approved?Boolean

Returns:



781
782
783
# File 'app/models/spree/order.rb', line 781

def approved?
  !!approved_at
end

#approved_by(user) ⇒ Object



768
769
770
771
772
773
774
775
776
777
778
779
# File 'app/models/spree/order.rb', line 768

def approved_by(user)
  approved_at = Time.current
  changes = { approver_id: user.id, approved_at: approved_at }

  transaction do
    approve!
    update_columns(changes)
  end

  # Manually publish update event since update_columns bypasses callbacks
  publish_event('order.approved')
end

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

Associates the specified user with the order.



392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
# File 'app/models/spree/order.rb', line 392

def associate_user!(user, override_email = true)
  self.user           = user
  self.email          = user.email if override_email
  # we need to check if user is of admin user class to avoid mismatch type error
  # in a scenario where we have separate classes for admin and regular users
  self.created_by   ||= user if user.is_a?(Spree.admin_user_class)
  self.bill_address ||= user.bill_address
  self.ship_address ||= user.ship_address

  changes = slice(*ASSOCIATED_USER_ATTRIBUTES)

  # immediately persist the changes we just made, but don't use save
  # since we might have an invalid address associated
  ActiveRecord::Base.connected_to(role: :writing) do
    self.class.unscoped.where(id: self).update_all(changes)
  end

  # Manually publish update event since update_all bypasses callbacks
  publish_event('order.updated') if changes.present?
end

#available_payment_methods(store = nil) ⇒ Object



558
559
560
561
562
# File 'app/models/spree/order.rb', line 558

def available_payment_methods(store = nil)
  Spree::Deprecation.warn('`Order#available_payment_methods` is deprecated and will be removed in Spree 5.5. Use `collect_frontend_payment_methods` instead.')

  @available_payment_methods ||= collect_payment_methods(store)
end

#backordered?Boolean

Returns:



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

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

#backordered_variantsArray<Spree::Variant>

Returns the backordered variants for the order

Returns:



496
497
498
499
500
501
# File 'app/models/spree/order.rb', line 496

def backordered_variants
  variants.
    where(track_inventory: true).
    joins(:stock_items, :product).
    where(Spree::StockItem.table_name => { count_on_hand: ..0, backorderable: true })
end

#can_add_coupon?Boolean

Returns:



634
635
636
# File 'app/models/spree/order.rb', line 634

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

#can_approve?Boolean

Returns:



785
786
787
# File 'app/models/spree/order.rb', line 785

def can_approve?
  !approved?
end

#can_be_deleted?Boolean

Returns:



794
795
796
# File 'app/models/spree/order.rb', line 794

def can_be_deleted?
  !completed? && payments.completed.empty?
end

#can_be_destroyed?Boolean

Returns:



789
790
791
792
# File 'app/models/spree/order.rb', line 789

def can_be_destroyed?
  Spree::Deprecation.warn('Spree::Order#can_be_destroyed? is deprecated and will be removed in the next major version. Use Spree::Order#can_be_deleted? instead.')
  can_be_deleted?
end

#can_ship?Boolean

Returns:



503
504
505
# File 'app/models/spree/order.rb', line 503

def can_ship?
  complete? || resumed? || awaiting_return? || returned?
end

#canceled_by(user, canceled_at = nil) ⇒ Object



755
756
757
758
759
760
761
762
763
764
765
766
# File 'app/models/spree/order.rb', line 755

def canceled_by(user, canceled_at = nil)
  canceled_at ||= Time.current
  changes = { canceler_id: user.id, canceled_at: canceled_at }

  transaction do
    update_columns(changes)
    cancel!
  end

  # Manually publish update event since update_columns bypasses callbacks
  publish_event('order.canceled')
end

#cart_promo_totalBigDecimal

Returns item and whole order discount amount for Order without Shipment discounts (eg. Free Shipping)

Returns:

  • (BigDecimal)


876
877
878
879
880
# File 'app/models/spree/order.rb', line 876

def cart_promo_total
  all_adjustments.eligible.nonzero.promotion.
    where.not(adjustable_type: 'Spree::Shipment').
    sum(:amount)
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:



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

def checkout_allowed?
  line_items.exists?
end

#collect_backend_payment_methodsObject



829
830
831
# File 'app/models/spree/order.rb', line 829

def collect_backend_payment_methods
  store.payment_methods.active.available_on_back_end.select { |pm| pm.available_for_order?(self) }
end

#collect_frontend_payment_methodsObject



833
834
835
# File 'app/models/spree/order.rb', line 833

def collect_frontend_payment_methods
  store.payment_methods.active.available_on_front_end.select { |pm| pm.available_for_order?(self) }
end

#completed?Boolean

Returns:



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

def completed?
  completed_at.present?
end

#confirmation_required?Boolean

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

Returns:



315
316
317
318
319
320
321
322
# File 'app/models/spree/order.rb', line 315

def confirmation_required?
  Spree::Config[:always_include_confirm_step] ||
    payments.valid.map(&:payment_method).compact.any?(&:confirmation_required?) ||
    # 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.
    confirm?
end

#consider_riskObject



798
799
800
# File 'app/models/spree/order.rb', line 798

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

#considered_risky!Object



802
803
804
805
806
807
# File 'app/models/spree/order.rb', line 802

def considered_risky!
  update_column(:considered_risky, true)

  # Manually publish update event since update_column bypasses callbacks
  publish_event('order.updated')
end

#create_proposed_shipmentsObject



646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
# File 'app/models/spree/order.rb', line 646

def create_proposed_shipments
  all_adjustments.shipping.delete_all

  shipment_ids = shipments.map(&:id)
  StateChange.where(stateful_type: 'Spree::Shipment', stateful_id: shipment_ids).delete_all
  ShippingRate.where(shipment_id: shipment_ids).delete_all

  shipments.delete_all

  # Inventory Units which are not associated to any shipment (unshippable)
  # and are not returned or shipped should be deleted
  inventory_units.on_hand_or_backordered.delete_all

  self.shipments = Spree::Stock::Coordinator.new(self).shipments
end

#create_shipment_tax_charge!Object



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

def create_shipment_tax_charge!
  Spree::TaxRate.adjust(self, shipments) if shipments.any?
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.



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

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

#credit_cardsObject



511
512
513
514
# File 'app/models/spree/order.rb', line 511

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

#delivery_required?Boolean

Does this order require a delivery (physical or digital).

Returns:



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

def delivery_required?
  true # true for Spree, can be decorated
end

#disassociate_user!Object



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

def disassociate_user!
  nullified_attributes = ASSOCIATED_USER_ATTRIBUTES.index_with(nil)

  update!(nullified_attributes)
end

#email_required?Boolean

Returns:



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

def email_required?
  require_email
end

#empty!Object



591
592
593
594
595
596
# File 'app/models/spree/order.rb', line 591

def empty!
  raise Spree.t(:cannot_empty_completed_order) if completed?

  result = Spree.cart_empty_service.call(order: self)
  result.value
end

#ensure_line_item_variants_are_not_discontinuedObject

Check to see if any line item variants are discontinued. If so add error and restart checkout.



571
572
573
574
575
576
577
578
579
# File 'app/models/spree/order.rb', line 571

def ensure_line_item_variants_are_not_discontinued
  if line_items.any? { |li| !li.variant || li.variant.discontinued? }
    restart_checkout_flow
    errors.add(:base, Spree.t(:discontinued_variants_present))
    false
  else
    true
  end
end

#ensure_line_items_are_in_stockObject



581
582
583
584
585
586
587
588
589
# File 'app/models/spree/order.rb', line 581

def ensure_line_items_are_in_stock
  if insufficient_stock_lines.present?
    restart_checkout_flow
    errors.add(:base, Spree.t(:insufficient_stock_lines_present))
    false
  else
    true
  end
end

#ensure_store_presenceObject



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

def ensure_store_presence
  self.store ||= Spree::Store.default
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



703
704
705
706
707
708
709
710
711
712
713
# File 'app/models/spree/order.rb', line 703

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

    # Manually publish update event since update_column bypasses callbacks
    publish_event('order.updated')

    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



523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
# File 'app/models/spree/order.rb', line 523

def finalize!
  # lock all adjustments (coupon promotions, etc.)
  all_adjustments.each(&: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

  send_order_placed_webhook

  consider_risk

  publish_order_completed_event
end

#find_line_item_by_variant(variant, options = {}) ⇒ Object



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

def find_line_item_by_variant(variant, options = {})
  line_items.detect do |line_item|
    line_item.variant_id == variant.id &&
      Spree.cart_compare_line_items_service.new.call(order: self, line_item: line_item, options: options).value
  end
end

#fulfill!Object



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

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

#full_nameObject



471
472
473
474
475
476
477
# File 'app/models/spree/order.rb', line 471

def full_name
  @full_name ||= if user.present? && user.name.present?
                   user.full_name
                 else
                   billing_address&.full_name || email
                 end
end

#fully_discounted?Boolean Also known as: fully_discounted

determines whether the inventory is fully discounted

Returns

  • true if inventory amount is the exact negative of inventory related adjustments

  • false otherwise

Returns:



842
843
844
# File 'app/models/spree/order.rb', line 842

def fully_discounted?
  adjustment_total + line_items.map(&:final_amount).sum == 0.0
end

#fully_shipped?Boolean

Returns:



642
643
644
# File 'app/models/spree/order.rb', line 642

def fully_shipped?
  shipments.shipped.size == shipments.size
end

#has_free_shipping?Boolean

Returns:



882
883
884
885
886
887
# File 'app/models/spree/order.rb', line 882

def has_free_shipping?
  shipment_adjustments.
    joins(:promotion_action).
    where(spree_adjustments: { eligible: true, source_type: 'Spree::PromotionAction' },
          spree_promotion_actions: { type: 'Spree::Promotion::Actions::FreeShipping' }).exists?
end

Returns:



824
825
826
827
# File 'app/models/spree/order.rb', line 824

def has_non_reimbursement_related_refunds?
  refunds.non_reimbursement.exists? ||
    payments.offset_payment.exists? # how old versions of spree stored refunds
end

#has_step?(step) ⇒ Boolean

Returns:



602
603
604
# File 'app/models/spree/order.rb', line 602

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

#insufficient_stock_linesObject



564
565
566
# File 'app/models/spree/order.rb', line 564

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

#is_risky?Boolean

Returns:



751
752
753
# File 'app/models/spree/order.rb', line 751

def is_risky?
  !payments.risky.empty?
end

#line_items_without_shipping_ratesArray<Spree::LineItem>

Returns line items that have no shipping rates

Returns:



673
674
675
676
677
# File 'app/models/spree/order.rb', line 673

def line_items_without_shipping_rates
  @line_items_without_shipping_rates ||= shipments.map do |shipment|
    shipment.manifest.map(&:line_item) if shipment.shipping_rates.blank?
  end.flatten.compact
end

#log_state_changes(state_name:, old_state:, new_state:) ⇒ Object



617
618
619
620
621
622
623
624
# File 'app/models/spree/order.rb', line 617

def log_state_changes(state_name:, old_state:, new_state:)
  state_changes.create(
    previous_state: old_state,
    next_state: new_state,
    name: state_name,
    user_id: user_id
  )
end

#mergerObject



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

def merger
  @merger ||= Spree::OrderMerger.new(self)
end

#nameObject



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

def name
  if (address = bill_address || ship_address)
    address.full_name
  end
end

#order_refunded?Boolean

Checks if the order is fully refunded

Returns:



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

def order_refunded?
  return false if item_count.zero?

  (payment_state.in?(%w[void failed]) && refunds_total.positive?) ||
    refunds_total == total_minus_store_credits - additional_tax_total.abs
end

#outstanding_balanceObject



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

def outstanding_balance
  if canceled?
    -1 * payment_total
  else
    total - (payment_total + reimbursement_paid_total)
  end
end

#outstanding_balance?Boolean

Returns:



461
462
463
# File 'app/models/spree/order.rb', line 461

def outstanding_balance?
  outstanding_balance != 0
end

#paid?Boolean

Helper methods for checkout steps

Returns:



554
555
556
# File 'app/models/spree/order.rb', line 554

def paid?
  payments.valid.completed.size == payments.valid.size && payments.valid.sum(:amount) >= total
end

#partially_refunded?Boolean

Checks if the order is partially refunded

Returns:



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

def partially_refunded?
  return false if item_count.zero?
  return false if payment_state.in?(%w[void failed]) || refunds.empty?

  refunds_total < total_minus_store_credits - additional_tax_total.abs
end

#payment_methodSpree::PaymentMethod

Returns the payment method for the order

Returns:



482
483
484
# File 'app/models/spree/order.rb', line 482

def payment_method
  payments.valid.not_store_credits.first&.payment_method
end

#payment_required?Boolean

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

Returns:



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

def payment_required?
  total.to_f > 0.0
end

#payment_sourceSpree::PaymentSource

Returns the payment source for the order

Returns:



489
490
491
# File 'app/models/spree/order.rb', line 489

def payment_source
  payments.valid.not_store_credits.first&.source
end

#pre_tax_item_amountObject

Sum of all line item amounts pre-tax



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

def pre_tax_item_amount
  line_items.sum(:pre_tax_amount)
end

#pre_tax_totalObject

Sum of all line item and shipment pre-tax



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

def pre_tax_total
  pre_tax_item_amount + shipments.sum(:pre_tax_amount)
end

#promo_codeObject



847
848
849
# File 'app/models/spree/order.rb', line 847

def promo_code
  Spree::CouponCode.find_by(order: self, promotion: promotions).try(:code) || promotions.pluck(:code).compact.first
end

#quantityObject



820
821
822
# File 'app/models/spree/order.rb', line 820

def quantity
  line_items.sum(:quantity)
end

#quantity_of(variant, options = {}) ⇒ Object



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

def quantity_of(variant, options = {})
  line_item = find_line_item_by_variant(variant, options)
  line_item ? line_item.quantity : 0
end

#quick_checkout?Boolean

Check if the shipping address is a quick checkout address quick checkout addresses are incomplete as wallet providers like Apple Pay and Google Pay do not provide all the address fields until the checkout is completed (confirmed) on their side

Returns:



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

def quick_checkout?
  shipping_address.present? && shipping_address.quick_checkout?
end

#quick_checkout_available?Boolean

Check if quick checkout is available for this order Either fully digital or not digital at all

Returns:



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

def quick_checkout_available?
  payment_required? && shipments.count <= 1 && (digital? || !some_digital? || !delivery_required?)
end

#quick_checkout_require_address?Boolean

Check if quick checkout requires an address collection If the order is digital or not delivery required, then we don’t need to collect an address

Returns:



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

def quick_checkout_require_address?
  !digital? && delivery_required?
end

#refresh_shipment_rates(shipping_method_filter = ShippingMethod::DISPLAY_ON_FRONT_END) ⇒ Object



727
728
729
# File 'app/models/spree/order.rb', line 727

def refresh_shipment_rates(shipping_method_filter = ShippingMethod::DISPLAY_ON_FRONT_END)
  shipments.map { |s| s.refresh_rates(shipping_method_filter) }
end

#refunds_totalObject



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

def refunds_total
  refunds.loaded? ? refunds.sum(&:amount) : refunds.sum(:amount)
end

#reimbursement_paid_totalObject



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

def reimbursement_paid_total
  reimbursements.sum(&:paid_amount)
end

#requires_ship_address?Boolean

Returns:



905
906
907
# File 'app/models/spree/order.rb', line 905

def requires_ship_address?
  !digital?
end

#restart_checkout_flowObject



715
716
717
718
719
720
721
722
723
724
725
# File 'app/models/spree/order.rb', line 715

def restart_checkout_flow
  update_columns(
    state: 'cart',
    updated_at: Time.current
  )

  # Manually publish update event since update_columns bypasses callbacks
  publish_event('order.updated')

  next! unless line_items.empty?
end

#set_shipments_costObject



735
736
737
738
739
740
# File 'app/models/spree/order.rb', line 735

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

#shipped?Boolean

Returns:



638
639
640
# File 'app/models/spree/order.rb', line 638

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

#shipping_discountObject



266
267
268
# File 'app/models/spree/order.rb', line 266

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

#shipping_eq_billing_address?Boolean

Returns:



731
732
733
# File 'app/models/spree/order.rb', line 731

def shipping_eq_billing_address?
  bill_address == ship_address
end

#shipping_methodObject



742
743
744
745
746
747
748
749
# File 'app/models/spree/order.rb', line 742

def shipping_method
  # This query will select the first available shipping method from the shipments.
  # It will use subquery to first select the shipping method id from the shipments' selected_shipping_rate.
  Spree::ShippingMethod.
    where(id: shipments.with_selected_shipping_method.limit(1).pluck(:shipping_method_id)).
    limit(1).
    first
end

#state_changed(name) ⇒ Object



606
607
608
609
610
611
612
613
614
615
# File 'app/models/spree/order.rb', line 606

def state_changed(name)
  state = "#{name}_state"
  if persisted?
    old_state = send("#{state}_was")
    new_state = send(state)
    unless old_state == new_state
      log_state_changes(state_name: name, old_state: old_state, new_state: new_state)
    end
  end
end

#tax_addressObject

Returns the address for taxation based on configuration



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

def tax_address
  Spree::Config[:tax_using_ship_address] ? ship_address : bill_address
end

#tax_totalObject



816
817
818
# File 'app/models/spree/order.rb', line 816

def tax_total
  included_tax_total + additional_tax_total
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



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

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

#to_csv(_store = nil) ⇒ Object



889
890
891
892
893
894
895
896
897
898
899
# File 'app/models/spree/order.rb', line 889

def to_csv(_store = nil)
  metafields_for_csv ||= Spree::MetafieldDefinition.for_resource_type('Spree::Order').order(:namespace, :key).map do |mf_def|
    metafields.find { |mf| mf.metafield_definition_id == mf_def.id }&.csv_value
  end

  csv_lines = []
  all_line_items.each_with_index do |line_item, index|
    csv_lines << Spree::CSV::OrderLineItemPresenter.new(self, line_item, index, metafields_for_csv).call
  end
  csv_lines
end

#total_weightBigDecimal

Returns the total weight of the inventory units in the order This is used to calculate the shipping rates for the order

Returns:

  • (BigDecimal)

    the total weight of the inventory units in the order



666
667
668
# File 'app/models/spree/order.rb', line 666

def total_weight
  @total_weight ||= line_items.joins(:variant).includes(:variant).map(&:item_weight).sum
end

#uneditable?Boolean

Returns:



507
508
509
# File 'app/models/spree/order.rb', line 507

def uneditable?
  complete? || canceled? || returned?
end

#update_line_item_prices!Object



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

def update_line_item_prices!
  transaction do
    line_items.reload.each(&:update_price)
    save!
  end
end

#update_with_updater!Object



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

def update_with_updater!
  updater.update
end

#updaterObject



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

def updater
  @updater ||= Spree.order_updater.new(self)
end

#use_all_coupon_codesObject



598
599
600
# File 'app/models/spree/order.rb', line 598

def use_all_coupon_codes
  Spree::CouponCodes::CouponCodesHandler.new(order: self).use_all_codes
end

#valid_coupon_promotionsArray<Spree::Promotion>

Returns the valid coupon promotions for the order

Returns:



867
868
869
870
871
# File 'app/models/spree/order.rb', line 867

def valid_coupon_promotions
  promotions.
    where(id: valid_promotion_ids).
    coupons
end

#valid_credit_cardsObject



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

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

#valid_promotion_idsArray<Integer>

Returns the IDs of the valid promotions for the order

Returns:

  • (Array<Integer>)


859
860
861
862
863
# File 'app/models/spree/order.rb', line 859

def valid_promotion_ids
  all_adjustments.eligible.nonzero.promotion.promotion.eligible.nonzero.promotion.
    joins("INNER JOIN #{Spree::PromotionAction.table_name} ON #{Spree::PromotionAction.table_name}.id = #{Spree::Adjustment.table_name}.source_id").
    pluck("#{Spree::PromotionAction.table_name}.promotion_id").compact.uniq
end

#valid_promotionsArray<Spree::OrderPromotion>

Returns the valid promotions for the order

Returns:



853
854
855
# File 'app/models/spree/order.rb', line 853

def valid_promotions
  order_promotions.includes(:promotion).where(promotion_id: valid_promotion_ids).uniq(&:promotion_id)
end