Class: Spree::Order
- Inherits:
-
ActiveRecord::Base
- Object
- ActiveRecord::Base
- Spree::Order
- Includes:
- Checkout
- Defined in:
- app/models/spree/order.rb,
app/models/spree/order/checkout.rb
Defined Under Namespace
Modules: Checkout
Instance Attribute Summary collapse
-
#coupon_code ⇒ Object
Returns the value of attribute coupon_code.
-
#use_billing ⇒ Object
Returns the value of attribute use_billing.
Class Method Summary collapse
- .between(start_date, end_date) ⇒ Object
- .by_customer(customer) ⇒ Object
- .by_number(number) ⇒ Object
- .by_state(state) ⇒ Object
- .complete ⇒ Object
- .incomplete ⇒ Object
-
.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.
Instance Method Summary collapse
- #allow_cancel? ⇒ Boolean
-
#amount ⇒ Object
For compatiblity with Calculator::PriceSack.
-
#associate_user!(user) ⇒ Object
Associates the specified user with the order.
- #available_payment_methods ⇒ Object
- #awaiting_returns? ⇒ Boolean
- #backordered? ⇒ Boolean
- #billing_firstname ⇒ Object
- #billing_lastname ⇒ Object
- #can_ship? ⇒ Boolean
-
#checkout_allowed? ⇒ Boolean
Indicates whether or not the user is allowed to proceed to checkout.
- #clear_adjustments! ⇒ Object
- #clone_billing_address ⇒ Object
- #completed? ⇒ Boolean
-
#confirmation_required? ⇒ Boolean
If true, causes the confirmation step to happen during the checkout process.
- #contains?(variant) ⇒ Boolean
- #contents ⇒ Object
- #create_proposed_shipments ⇒ Object
-
#create_tax_charge! ⇒ Object
Creates new tax charges if there are any applicable rates.
- #credit_cards ⇒ Object
- #currency ⇒ Object
- #deliver_order_confirmation_email ⇒ Object
- #discount_total ⇒ Object
- #display_adjustment_total ⇒ Object
- #display_item_total ⇒ Object
- #display_outstanding_balance ⇒ Object
- #display_ship_total ⇒ Object
- #display_tax_total ⇒ Object
- #display_total ⇒ Object
- #empty! ⇒ Object
-
#ensure_updated_shipments ⇒ Object
Clean shipments and make order back to address state.
-
#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.
-
#finalize! ⇒ Object
Finalizes an in progress order after checkout is complete.
- #find_line_item_by_variant(variant) ⇒ Object
-
#generate_order_number ⇒ Object
FIXME refactor this method and implement validation using validates_* utilities.
- #has_step?(step) ⇒ Boolean
- #insufficient_stock_lines ⇒ Object
- #is_risky? ⇒ Boolean
-
#item_count ⇒ Object
Indicates the number of items in the order.
-
#line_item_adjustment_totals ⇒ Object
Array of totals grouped by Adjustment#label.
- #manual_adjustment_total ⇒ Object
- #merge!(order, user = nil) ⇒ Object
- #name ⇒ Object
- #outstanding_balance ⇒ Object
- #outstanding_balance? ⇒ Boolean
-
#paid? ⇒ Boolean
Helper methods for checkout steps.
-
#payment_required? ⇒ Boolean
Is this a free order in which case the payment step should be skipped.
- #pending_payments ⇒ Object
-
#process_payments! ⇒ Object
processes any pending payments and must return a boolean as it’s return value is used by the checkout state_machine to determine success or failure of the ‘complete’ event for the order.
- #products ⇒ Object
- #promo_total ⇒ Object
-
#promotion_credit_exists?(originator) ⇒ Boolean
Tells us if there if the specified promotion is already associated with the order regardless of whether or not its currently eligible.
- #quantity_of(variant) ⇒ Object
- #refresh_shipment_rates ⇒ Object
- #restart_checkout_flow ⇒ Object
- #ship_total ⇒ Object
- #shipped? ⇒ Boolean
- #shipped_shipments ⇒ Object
- #shipping_eq_billing_address? ⇒ Boolean
- #state_changed(name) ⇒ Object
-
#tax_address ⇒ Object
Returns the address for taxation based on configuration.
-
#tax_zone ⇒ Object
Returns the relevant zone (if any) to be used for taxation purposes.
- #to_param ⇒ Object
- #update! ⇒ Object
- #update_totals ⇒ Object
- #updater ⇒ Object
- #variants ⇒ Object
Methods included from Checkout
Instance Attribute Details
#coupon_code ⇒ Object
Returns the value of attribute coupon_code.
22 23 24 |
# File 'app/models/spree/order.rb', line 22 def coupon_code @coupon_code end |
#use_billing ⇒ Object
Returns the value of attribute use_billing.
63 64 65 |
# File 'app/models/spree/order.rb', line 63 def use_billing @use_billing end |
Class Method Details
.between(start_date, end_date) ⇒ Object
85 86 87 88 |
# File 'app/models/spree/order.rb', line 85 def self.between(start_date, end_date) ActiveSupport::Deprecation.warn("Order#between will be deprecated in Spree 2.3, please use either Order#created_between or Order#completed_between instead.") self.created_between(start_date, end_date) end |
.by_customer(customer) ⇒ Object
90 91 92 |
# File 'app/models/spree/order.rb', line 90 def self.by_customer(customer) joins(:user).where("#{Spree.user_class.table_name}.email" => customer) end |
.by_number(number) ⇒ Object
78 79 80 |
# File 'app/models/spree/order.rb', line 78 def self.by_number(number) where(number: number) end |
.by_state(state) ⇒ Object
94 95 96 |
# File 'app/models/spree/order.rb', line 94 def self.by_state(state) where(state: state) end |
.complete ⇒ Object
98 99 100 |
# File 'app/models/spree/order.rb', line 98 def self.complete where.not(completed_at: nil) end |
.incomplete ⇒ Object
102 103 104 |
# File 'app/models/spree/order.rb', line 102 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#update
108 109 110 |
# File 'app/models/spree/order.rb', line 108 def self.register_update_hook(hook) self.update_hooks.add(hook) end |
Instance Method Details
#allow_cancel? ⇒ Boolean
235 236 237 238 |
# File 'app/models/spree/order.rb', line 235 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
113 114 115 |
# File 'app/models/spree/order.rb', line 113 def amount line_items.inject(0.0) { |sum, li| sum + li.amount } end |
#associate_user!(user) ⇒ Object
Associates the specified user with the order.
249 250 251 252 253 254 255 256 257 258 |
# File 'app/models/spree/order.rb', line 249 def associate_user!(user) self.user = user self.email = user.email self.created_by = user if self.created_by.blank? if persisted? # immediately persist the changes we just made, but don't use save since we might have an invalid address associated self.class.unscoped.where(id: id).update_all(email: user.email, user_id: user.id, created_by_id: self.created_by_id) end end |
#available_payment_methods ⇒ Object
357 358 359 |
# File 'app/models/spree/order.rb', line 357 def available_payment_methods @available_payment_methods ||= (PaymentMethod.available(:front_end) + PaymentMethod.available(:both)).uniq end |
#awaiting_returns? ⇒ Boolean
240 241 242 |
# File 'app/models/spree/order.rb', line 240 def awaiting_returns? .any? { || . } end |
#backordered? ⇒ Boolean
181 182 183 |
# File 'app/models/spree/order.rb', line 181 def backordered? shipments.any?(&:backordered?) end |
#billing_firstname ⇒ Object
397 398 399 |
# File 'app/models/spree/order.rb', line 397 def billing_firstname bill_address.try(:firstname) end |
#billing_lastname ⇒ Object
401 402 403 |
# File 'app/models/spree/order.rb', line 401 def billing_lastname bill_address.try(:lastname) end |
#can_ship? ⇒ Boolean
312 313 314 |
# File 'app/models/spree/order.rb', line 312 def can_ship? self.complete? || self.resumed? || self.awaiting_return? || self.returned? 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.
157 158 159 |
# File 'app/models/spree/order.rb', line 157 def checkout_allowed? line_items.count > 0 end |
#clear_adjustments! ⇒ Object
442 443 444 445 |
# File 'app/models/spree/order.rb', line 442 def clear_adjustments! self.adjustments.destroy_all self.line_item_adjustments.destroy_all end |
#clone_billing_address ⇒ Object
226 227 228 229 230 231 232 233 |
# File 'app/models/spree/order.rb', line 226 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
149 150 151 |
# File 'app/models/spree/order.rb', line 149 def completed? completed_at.present? || complete? end |
#confirmation_required? ⇒ Boolean
If true, causes the confirmation step to happen during the checkout process
167 168 169 170 171 172 173 174 |
# File 'app/models/spree/order.rb', line 167 def confirmation_required? Spree::Config[:always_include_confirm_step] || payments.valid.map(&:payment_method).compact.any?(&:payment_profiles_supported?) || # 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. state == 'confirm' end |
#contains?(variant) ⇒ Boolean
275 276 277 |
# File 'app/models/spree/order.rb', line 275 def contains?(variant) find_line_item_by_variant(variant).present? end |
#contents ⇒ Object
244 245 246 |
# File 'app/models/spree/order.rb', line 244 def contents @contents ||= Spree::OrderContents.new(self) end |
#create_proposed_shipments ⇒ Object
497 498 499 500 501 502 503 504 505 506 507 |
# File 'app/models/spree/order.rb', line 497 def create_proposed_shipments adjustments.shipping.delete_all shipments.destroy_all packages = Spree::Stock::Coordinator.new(self).packages packages.each do |package| shipments << package.to_shipment end shipments 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.
294 295 296 |
# File 'app/models/spree/order.rb', line 294 def create_tax_charge! Spree::TaxRate.adjust(self) end |
#credit_cards ⇒ Object
316 317 318 319 |
# File 'app/models/spree/order.rb', line 316 def credit_cards credit_card_ids = payments.from_credit_card.pluck(:source_id).uniq CreditCard.where(id: credit_card_ids) end |
#currency ⇒ Object
117 118 119 |
# File 'app/models/spree/order.rb', line 117 def currency self[:currency] || Spree::Config[:currency] end |
#deliver_order_confirmation_email ⇒ Object
343 344 345 346 347 348 349 350 |
# File 'app/models/spree/order.rb', line 343 def deliver_order_confirmation_email begin OrderMailer.confirm_email(self.id).deliver rescue Exception => e logger.error("#{e.class.name}: #{e.}") logger.error(e.backtrace * "\n") end end |
#discount_total ⇒ Object
489 490 491 |
# File 'app/models/spree/order.rb', line 489 def discount_total promo_total + manual_adjustment_total end |
#display_adjustment_total ⇒ Object
129 130 131 |
# File 'app/models/spree/order.rb', line 129 def display_adjustment_total Spree::Money.new(adjustment_total, { currency: currency }) end |
#display_item_total ⇒ Object
125 126 127 |
# File 'app/models/spree/order.rb', line 125 def display_item_total Spree::Money.new(item_total, { currency: currency }) end |
#display_outstanding_balance ⇒ Object
121 122 123 |
# File 'app/models/spree/order.rb', line 121 def display_outstanding_balance Spree::Money.new(outstanding_balance, { currency: currency }) end |
#display_ship_total ⇒ Object
137 138 139 |
# File 'app/models/spree/order.rb', line 137 def display_ship_total Spree::Money.new(ship_total, { currency: currency }) end |
#display_tax_total ⇒ Object
133 134 135 |
# File 'app/models/spree/order.rb', line 133 def display_tax_total Spree::Money.new(tax_total, { currency: currency }) end |
#display_total ⇒ Object
141 142 143 |
# File 'app/models/spree/order.rb', line 141 def display_total Spree::Money.new(total, { currency: currency }) end |
#empty! ⇒ Object
437 438 439 440 |
# File 'app/models/spree/order.rb', line 437 def empty! adjustments.destroy_all line_items.destroy_all 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
514 515 516 517 518 519 |
# File 'app/models/spree/order.rb', line 514 def ensure_updated_shipments if shipments.any? && !self.completed? self.shipments.destroy_all restart_checkout_flow 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.
194 195 196 197 |
# File 'app/models/spree/order.rb', line 194 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
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 |
# File 'app/models/spree/order.rb', line 323 def finalize! touch :completed_at # lock all adjustments (coupon promotions, etc.) adjustments.update_all state: 'closed' # 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 deliver_order_confirmation_email end |
#find_line_item_by_variant(variant) ⇒ Object
284 285 286 |
# File 'app/models/spree/order.rb', line 284 def find_line_item_by_variant(variant) line_items.detect { |line_item| line_item.variant_id == variant.id } end |
#generate_order_number ⇒ Object
FIXME refactor this method and implement validation using validates_* utilities
261 262 263 264 265 266 267 268 269 |
# File 'app/models/spree/order.rb', line 261 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
447 448 449 |
# File 'app/models/spree/order.rb', line 447 def has_step?(step) checkout_steps.include?(step) end |
#insufficient_stock_lines ⇒ Object
413 414 415 |
# File 'app/models/spree/order.rb', line 413 def insufficient_stock_lines line_items.select &:insufficient_stock? end |
#is_risky? ⇒ Boolean
533 534 535 536 537 538 539 540 |
# File 'app/models/spree/order.rb', line 533 def is_risky? self.payments.where(%{ (avs_response IS NOT NULL and avs_response != '' and avs_response != 'D' and avs_response != 'M') or (cvv_response_code IS NOT NULL and cvv_response_code != 'M') or cvv_response_message IS NOT NULL and cvv_response_message != '' or state = 'failed' }.squish!).uniq.count > 0 end |
#item_count ⇒ Object
Indicates the number of items in the order
177 178 179 |
# File 'app/models/spree/order.rb', line 177 def item_count line_items.inject(0) { |sum, li| sum + li.quantity } end |
#line_item_adjustment_totals ⇒ Object
Array of totals grouped by Adjustment#label. Useful for displaying line item adjustments on an invoice. For example, you can display tax breakout for cases where tax is included in price.
207 208 209 210 211 212 |
# File 'app/models/spree/order.rb', line 207 def line_item_adjustment_totals Hash[self.line_item_adjustments.eligible.group_by(&:label).map do |label, adjustments| total = adjustments.sum(&:amount) [label, Spree::Money.new(total, { currency: currency })] end] end |
#manual_adjustment_total ⇒ Object
485 486 487 |
# File 'app/models/spree/order.rb', line 485 def manual_adjustment_total adjustments.eligible.manual.sum(:amount) end |
#merge!(order, user = nil) ⇒ Object
417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 |
# File 'app/models/spree/order.rb', line 417 def merge!(order, user = nil) order.line_items.each do |line_item| next unless line_item.currency == currency current_line_item = self.line_items.find_by(variant: line_item.variant) if current_line_item current_line_item.quantity += line_item.quantity current_line_item.save else line_item.order_id = self.id line_item.save end end self.associate_user!(user) if !self.user && !user.blank? # So that the destroy doesn't take out line items which may have been re-assigned order.line_items.reload order.destroy end |
#name ⇒ Object
306 307 308 309 310 |
# File 'app/models/spree/order.rb', line 306 def name if (address = bill_address || ship_address) "#{address.firstname} #{address.lastname}" end end |
#outstanding_balance ⇒ Object
298 299 300 |
# File 'app/models/spree/order.rb', line 298 def outstanding_balance total - payment_total end |
#outstanding_balance? ⇒ Boolean
302 303 304 |
# File 'app/models/spree/order.rb', line 302 def outstanding_balance? self.outstanding_balance != 0 end |
#paid? ⇒ Boolean
Helper methods for checkout steps
353 354 355 |
# File 'app/models/spree/order.rb', line 353 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
162 163 164 |
# File 'app/models/spree/order.rb', line 162 def payment_required? total.to_f > 0.0 end |
#pending_payments ⇒ Object
361 362 363 |
# File 'app/models/spree/order.rb', line 361 def pending_payments payments.select(&:checkout?) end |
#process_payments! ⇒ Object
processes any pending payments and must return a boolean as it’s return value is used by the checkout state_machine to determine success or failure of the ‘complete’ event for the order
Returns:
-
true if all pending_payments processed successfully
-
true if a payment failed, ie. raised a GatewayError which gets rescued and converted to TRUE when :allow_checkout_gateway_error is set to true
-
false if a payment failed, ie. raised a GatewayError which gets rescued and converted to FALSE when :allow_checkout_on_gateway_error is set to false
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 |
# File 'app/models/spree/order.rb', line 378 def process_payments! if pending_payments.empty? raise Core::GatewayError.new Spree.t(:no_pending_payments) else pending_payments.each do |payment| break if payment_total >= total payment.process! if payment.completed? self.payment_total += payment.amount end end end rescue Core::GatewayError => e result = !!Spree::Config[:allow_checkout_on_gateway_error] errors.add(:base, e.) and return result end |
#products ⇒ Object
405 406 407 |
# File 'app/models/spree/order.rb', line 405 def products line_items.map(&:product) end |
#promo_total ⇒ Object
481 482 483 |
# File 'app/models/spree/order.rb', line 481 def promo_total adjustments.eligible.promotion.sum(:amount) end |
#promotion_credit_exists?(originator) ⇒ Boolean
Tells us if there if the specified promotion is already associated with the order regardless of whether or not its currently eligible. Useful because generally you would only want a promotion action to apply to order no more than once.
Receives an adjustment originator
(here a PromotionAction object) and tells if the order has adjustments from that already
477 478 479 |
# File 'app/models/spree/order.rb', line 477 def promotion_credit_exists?(originator) !! adjustments.includes(:originator).promotion.reload.detect { |credit| credit.originator.id == originator.id } end |
#quantity_of(variant) ⇒ Object
279 280 281 282 |
# File 'app/models/spree/order.rb', line 279 def quantity_of(variant) line_item = find_line_item_by_variant(variant) line_item ? line_item.quantity : 0 end |
#refresh_shipment_rates ⇒ Object
525 526 527 |
# File 'app/models/spree/order.rb', line 525 def refresh_shipment_rates shipments.map &:refresh_rates end |
#restart_checkout_flow ⇒ Object
521 522 523 |
# File 'app/models/spree/order.rb', line 521 def restart_checkout_flow self.update_column(:state, checkout_steps.first) end |
#ship_total ⇒ Object
288 289 290 |
# File 'app/models/spree/order.rb', line 288 def ship_total adjustments.shipping.sum(:amount) end |
#shipped? ⇒ Boolean
493 494 495 |
# File 'app/models/spree/order.rb', line 493 def shipped? %w(partial shipped).include?(shipment_state) end |
#shipped_shipments ⇒ Object
271 272 273 |
# File 'app/models/spree/order.rb', line 271 def shipped_shipments shipments.shipped end |
#shipping_eq_billing_address? ⇒ Boolean
529 530 531 |
# File 'app/models/spree/order.rb', line 529 def shipping_eq_billing_address? (bill_address.empty? && ship_address.empty?) || bill_address.same_as?(ship_address) end |
#state_changed(name) ⇒ Object
451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 |
# File 'app/models/spree/order.rb', line 451 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
200 201 202 |
# File 'app/models/spree/order.rb', line 200 def tax_address Spree::Config[:tax_using_ship_address] ? ship_address : bill_address 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
187 188 189 |
# File 'app/models/spree/order.rb', line 187 def tax_zone Zone.match(tax_address) || Zone.default_tax end |
#to_param ⇒ Object
145 146 147 |
# File 'app/models/spree/order.rb', line 145 def to_param number.to_s.to_url.upcase end |
#update! ⇒ Object
218 219 220 |
# File 'app/models/spree/order.rb', line 218 def update! updater.update end |
#update_totals ⇒ Object
222 223 224 |
# File 'app/models/spree/order.rb', line 222 def update_totals updater.update_totals end |
#updater ⇒ Object
214 215 216 |
# File 'app/models/spree/order.rb', line 214 def updater @updater ||= OrderUpdater.new(self) end |
#variants ⇒ Object
409 410 411 |
# File 'app/models/spree/order.rb', line 409 def variants line_items.map(&:variant) end |