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
82 83 84 |
# File 'app/models/spree/order.rb', line 82 def self.between(start_date, end_date) where(created_at: start_date..end_date) end |
.by_customer(customer) ⇒ Object
86 87 88 |
# File 'app/models/spree/order.rb', line 86 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
90 91 92 |
# File 'app/models/spree/order.rb', line 90 def self.by_state(state) where(state: state) end |
.complete ⇒ Object
94 95 96 |
# File 'app/models/spree/order.rb', line 94 def self.complete where.not(completed_at: nil) end |
.incomplete ⇒ Object
98 99 100 |
# File 'app/models/spree/order.rb', line 98 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
104 105 106 |
# File 'app/models/spree/order.rb', line 104 def self.register_update_hook(hook) self.update_hooks.add(hook) end |
Instance Method Details
#allow_cancel? ⇒ Boolean
231 232 233 234 |
# File 'app/models/spree/order.rb', line 231 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
109 110 111 |
# File 'app/models/spree/order.rb', line 109 def amount line_items.inject(0.0) { |sum, li| sum + li.amount } end |
#associate_user!(user) ⇒ Object
Associates the specified user with the order.
245 246 247 248 249 250 251 252 253 254 |
# File 'app/models/spree/order.rb', line 245 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
353 354 355 |
# File 'app/models/spree/order.rb', line 353 def available_payment_methods @available_payment_methods ||= (PaymentMethod.available(:front_end) + PaymentMethod.available(:both)).uniq end |
#awaiting_returns? ⇒ Boolean
236 237 238 |
# File 'app/models/spree/order.rb', line 236 def awaiting_returns? .any? { || . } end |
#backordered? ⇒ Boolean
177 178 179 |
# File 'app/models/spree/order.rb', line 177 def backordered? shipments.any?(&:backordered?) end |
#billing_firstname ⇒ Object
393 394 395 |
# File 'app/models/spree/order.rb', line 393 def billing_firstname bill_address.try(:firstname) end |
#billing_lastname ⇒ Object
397 398 399 |
# File 'app/models/spree/order.rb', line 397 def billing_lastname bill_address.try(:lastname) end |
#can_ship? ⇒ Boolean
308 309 310 |
# File 'app/models/spree/order.rb', line 308 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.
153 154 155 |
# File 'app/models/spree/order.rb', line 153 def checkout_allowed? line_items.count > 0 end |
#clear_adjustments! ⇒ Object
438 439 440 441 |
# File 'app/models/spree/order.rb', line 438 def clear_adjustments! self.adjustments.destroy_all self.line_item_adjustments.destroy_all end |
#clone_billing_address ⇒ Object
222 223 224 225 226 227 228 229 |
# File 'app/models/spree/order.rb', line 222 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
145 146 147 |
# File 'app/models/spree/order.rb', line 145 def completed? completed_at.present? || complete? end |
#confirmation_required? ⇒ Boolean
If true, causes the confirmation step to happen during the checkout process
163 164 165 166 167 168 169 170 |
# File 'app/models/spree/order.rb', line 163 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
271 272 273 |
# File 'app/models/spree/order.rb', line 271 def contains?(variant) find_line_item_by_variant(variant).present? end |
#contents ⇒ Object
240 241 242 |
# File 'app/models/spree/order.rb', line 240 def contents @contents ||= Spree::OrderContents.new(self) end |
#create_proposed_shipments ⇒ Object
493 494 495 496 497 498 499 500 501 502 503 |
# File 'app/models/spree/order.rb', line 493 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.
290 291 292 |
# File 'app/models/spree/order.rb', line 290 def create_tax_charge! Spree::TaxRate.adjust(self) end |
#credit_cards ⇒ Object
312 313 314 315 |
# File 'app/models/spree/order.rb', line 312 def credit_cards credit_card_ids = payments.from_credit_card.pluck(:source_id).uniq CreditCard.where(id: credit_card_ids) end |
#currency ⇒ Object
113 114 115 |
# File 'app/models/spree/order.rb', line 113 def currency self[:currency] || Spree::Config[:currency] end |
#deliver_order_confirmation_email ⇒ Object
339 340 341 342 343 344 345 346 |
# File 'app/models/spree/order.rb', line 339 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
485 486 487 |
# File 'app/models/spree/order.rb', line 485 def discount_total promo_total + manual_adjustment_total end |
#display_adjustment_total ⇒ Object
125 126 127 |
# File 'app/models/spree/order.rb', line 125 def display_adjustment_total Spree::Money.new(adjustment_total, { currency: currency }) end |
#display_item_total ⇒ Object
121 122 123 |
# File 'app/models/spree/order.rb', line 121 def display_item_total Spree::Money.new(item_total, { currency: currency }) end |
#display_outstanding_balance ⇒ Object
117 118 119 |
# File 'app/models/spree/order.rb', line 117 def display_outstanding_balance Spree::Money.new(outstanding_balance, { currency: currency }) end |
#display_ship_total ⇒ Object
133 134 135 |
# File 'app/models/spree/order.rb', line 133 def display_ship_total Spree::Money.new(ship_total, { currency: currency }) end |
#display_tax_total ⇒ Object
129 130 131 |
# File 'app/models/spree/order.rb', line 129 def display_tax_total Spree::Money.new(tax_total, { currency: currency }) end |
#display_total ⇒ Object
137 138 139 |
# File 'app/models/spree/order.rb', line 137 def display_total Spree::Money.new(total, { currency: currency }) end |
#empty! ⇒ Object
433 434 435 436 |
# File 'app/models/spree/order.rb', line 433 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
510 511 512 513 514 515 |
# File 'app/models/spree/order.rb', line 510 def ensure_updated_shipments if shipments.any? 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.
190 191 192 193 |
# File 'app/models/spree/order.rb', line 190 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
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 |
# File 'app/models/spree/order.rb', line 319 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
280 281 282 |
# File 'app/models/spree/order.rb', line 280 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
257 258 259 260 261 262 263 264 265 |
# File 'app/models/spree/order.rb', line 257 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
443 444 445 |
# File 'app/models/spree/order.rb', line 443 def has_step?(step) checkout_steps.include?(step) end |
#insufficient_stock_lines ⇒ Object
409 410 411 |
# File 'app/models/spree/order.rb', line 409 def insufficient_stock_lines line_items.select &:insufficient_stock? end |
#is_risky? ⇒ Boolean
529 530 531 532 533 534 535 536 |
# File 'app/models/spree/order.rb', line 529 def is_risky? self.payments.where(%{ (avs_response IS NOT NULL and avs_response != 'D') or (cvv_response_code IS NOT NULL and cvv_response_code != 'M') or cvv_response_message IS NOT NULL or state = 'failed' }.squish!).uniq.count > 0 end |
#item_count ⇒ Object
Indicates the number of items in the order
173 174 175 |
# File 'app/models/spree/order.rb', line 173 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.
203 204 205 206 207 208 |
# File 'app/models/spree/order.rb', line 203 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
481 482 483 |
# File 'app/models/spree/order.rb', line 481 def manual_adjustment_total adjustments.eligible.manual.sum(:amount) end |
#merge!(order, user = nil) ⇒ Object
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 |
# File 'app/models/spree/order.rb', line 413 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
302 303 304 305 306 |
# File 'app/models/spree/order.rb', line 302 def name if (address = bill_address || ship_address) "#{address.firstname} #{address.lastname}" end end |
#outstanding_balance ⇒ Object
294 295 296 |
# File 'app/models/spree/order.rb', line 294 def outstanding_balance total - payment_total end |
#outstanding_balance? ⇒ Boolean
298 299 300 |
# File 'app/models/spree/order.rb', line 298 def outstanding_balance? self.outstanding_balance != 0 end |
#paid? ⇒ Boolean
Helper methods for checkout steps
349 350 351 |
# File 'app/models/spree/order.rb', line 349 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
158 159 160 |
# File 'app/models/spree/order.rb', line 158 def payment_required? total.to_f > 0.0 end |
#pending_payments ⇒ Object
357 358 359 |
# File 'app/models/spree/order.rb', line 357 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
374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 |
# File 'app/models/spree/order.rb', line 374 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
401 402 403 |
# File 'app/models/spree/order.rb', line 401 def products line_items.map(&:product) end |
#promo_total ⇒ Object
477 478 479 |
# File 'app/models/spree/order.rb', line 477 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
473 474 475 |
# File 'app/models/spree/order.rb', line 473 def promotion_credit_exists?(originator) !! adjustments.includes(:originator).promotion.reload.detect { |credit| credit.originator.id == originator.id } end |
#quantity_of(variant) ⇒ Object
275 276 277 278 |
# File 'app/models/spree/order.rb', line 275 def quantity_of(variant) line_item = find_line_item_by_variant(variant) line_item ? line_item.quantity : 0 end |
#refresh_shipment_rates ⇒ Object
521 522 523 |
# File 'app/models/spree/order.rb', line 521 def refresh_shipment_rates shipments.map &:refresh_rates end |
#restart_checkout_flow ⇒ Object
517 518 519 |
# File 'app/models/spree/order.rb', line 517 def restart_checkout_flow self.update_column(:state, checkout_steps.first) end |
#ship_total ⇒ Object
284 285 286 |
# File 'app/models/spree/order.rb', line 284 def ship_total adjustments.shipping.sum(:amount) end |
#shipped? ⇒ Boolean
489 490 491 |
# File 'app/models/spree/order.rb', line 489 def shipped? %w(partial shipped).include?(shipment_state) end |
#shipped_shipments ⇒ Object
267 268 269 |
# File 'app/models/spree/order.rb', line 267 def shipped_shipments shipments.shipped end |
#shipping_eq_billing_address? ⇒ Boolean
525 526 527 |
# File 'app/models/spree/order.rb', line 525 def shipping_eq_billing_address? (bill_address.empty? && ship_address.empty?) || bill_address.same_as?(ship_address) end |
#state_changed(name) ⇒ Object
447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 |
# File 'app/models/spree/order.rb', line 447 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
196 197 198 |
# File 'app/models/spree/order.rb', line 196 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
183 184 185 |
# File 'app/models/spree/order.rb', line 183 def tax_zone Zone.match(tax_address) || Zone.default_tax end |
#to_param ⇒ Object
141 142 143 |
# File 'app/models/spree/order.rb', line 141 def to_param number.to_s.to_url.upcase end |
#update! ⇒ Object
214 215 216 |
# File 'app/models/spree/order.rb', line 214 def update! updater.update end |
#update_totals ⇒ Object
218 219 220 |
# File 'app/models/spree/order.rb', line 218 def update_totals updater.update_totals end |
#updater ⇒ Object
210 211 212 |
# File 'app/models/spree/order.rb', line 210 def updater @updater ||= OrderUpdater.new(self) end |
#variants ⇒ Object
405 406 407 |
# File 'app/models/spree/order.rb', line 405 def variants line_items.map(&:variant) end |