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
- #add_variant(variant, quantity = 1, currency = nil) ⇒ Object
- #allow_cancel? ⇒ Boolean
- #allow_resume? ⇒ 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
destroy any previous adjustments.
- #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
- #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
-
#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
-
#has_unprocessed_payments? ⇒ Boolean
Used by the checkout state machine to check for unprocessed payments The Order should be unable to proceed to complete if there are unprocessed payments and there is payment required.
- #insufficient_stock_lines ⇒ Object
-
#item_count ⇒ Object
Indicates the number of items in the order.
- #merge!(order) ⇒ 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
-
#price_adjustment_totals ⇒ Object
Array of totals grouped by Adjustment#label.
-
#price_adjustments ⇒ Object
Array of adjustments that are inclusive in the variant price.
- #process_payments! ⇒ Object
- #products ⇒ Object
- #promo_total ⇒ Object
-
#promotion_credit_exists?(promotion) ⇒ 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
-
#remove_invalid_shipments! ⇒ Object
Clear shipment when transitioning to delivery step of checkout if the current shipping address is not eligible for the existing shipping method.
- #remove_variant(variant, quantity = 1) ⇒ Object
- #ship_total ⇒ Object
- #shipment ⇒ Object
- #shipped? ⇒ Boolean
- #shipped_shipments ⇒ Object
- #state_changed(name) ⇒ Object
- #tax_total ⇒ Object
-
#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.
37 38 39 |
# File 'app/models/spree/order.rb', line 37 def coupon_code @coupon_code end |
#use_billing ⇒ Object
Returns the value of attribute use_billing.
74 75 76 |
# File 'app/models/spree/order.rb', line 74 def use_billing @use_billing end |
Class Method Details
.between(start_date, end_date) ⇒ Object
93 94 95 |
# File 'app/models/spree/order.rb', line 93 def self.between(start_date, end_date) where(created_at: start_date..end_date) end |
.by_customer(customer) ⇒ Object
97 98 99 |
# File 'app/models/spree/order.rb', line 97 def self.by_customer(customer) joins(:user).where("#{Spree.user_class.table_name}.email" => customer) end |
.by_number(number) ⇒ Object
89 90 91 |
# File 'app/models/spree/order.rb', line 89 def self.by_number(number) where(number: number) end |
.by_state(state) ⇒ Object
101 102 103 |
# File 'app/models/spree/order.rb', line 101 def self.by_state(state) where(state: state) end |
.complete ⇒ Object
105 106 107 |
# File 'app/models/spree/order.rb', line 105 def self.complete where('completed_at IS NOT NULL') end |
.incomplete ⇒ Object
109 110 111 |
# File 'app/models/spree/order.rb', line 109 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
115 116 117 |
# File 'app/models/spree/order.rb', line 115 def self.register_update_hook(hook) self.update_hooks.add(hook) end |
Instance Method Details
#add_variant(variant, quantity = 1, currency = nil) ⇒ Object
270 271 272 273 274 |
# File 'app/models/spree/order.rb', line 270 def add_variant(variant, quantity = 1, currency = nil) ActiveSupport::Deprecation.warn("[SPREE] Spree::Order#add_variant will be deprecated in Spree 2.1, please use order.contents.add instead.") contents.currency = currency unless currency.nil? contents.add(variant, quantity) end |
#allow_cancel? ⇒ Boolean
250 251 252 253 |
# File 'app/models/spree/order.rb', line 250 def allow_cancel? return false unless completed? and state != 'canceled' shipment_state.nil? || %w{ready backorder pending}.include?(shipment_state) end |
#allow_resume? ⇒ Boolean
255 256 257 258 259 260 |
# File 'app/models/spree/order.rb', line 255 def allow_resume? # we shouldn't allow resume for legacy orders b/c we lack the information # necessary to restore to a previous state return false if state_changes.empty? || state_changes.last.previous_state.nil? true end |
#amount ⇒ Object
For compatiblity with Calculator::PriceSack
120 121 122 |
# File 'app/models/spree/order.rb', line 120 def amount line_items.inject(0.0) { |sum, li| sum + li.amount } end |
#associate_user!(user) ⇒ Object
Associates the specified user with the order.
296 297 298 299 300 301 302 |
# File 'app/models/spree/order.rb', line 296 def associate_user!(user) self.user = user self.email = user.email # disable validations since they can cause issues when associating # an incomplete address during the address step save(validate: false) end |
#available_payment_methods ⇒ Object
423 424 425 |
# File 'app/models/spree/order.rb', line 423 def available_payment_methods @available_payment_methods ||= PaymentMethod.available(:front_end) end |
#awaiting_returns? ⇒ Boolean
262 263 264 |
# File 'app/models/spree/order.rb', line 262 def awaiting_returns? .any? { || . } end |
#backordered? ⇒ Boolean
190 191 192 |
# File 'app/models/spree/order.rb', line 190 def backordered? shipments.any?(&:backordered?) end |
#billing_firstname ⇒ Object
447 448 449 |
# File 'app/models/spree/order.rb', line 447 def billing_firstname bill_address.try(:firstname) end |
#billing_lastname ⇒ Object
451 452 453 |
# File 'app/models/spree/order.rb', line 451 def billing_lastname bill_address.try(:lastname) end |
#can_ship? ⇒ Boolean
371 372 373 |
# File 'app/models/spree/order.rb', line 371 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.
164 165 166 |
# File 'app/models/spree/order.rb', line 164 def checkout_allowed? line_items.count > 0 end |
#clear_adjustments! ⇒ Object
destroy any previous adjustments. Adjustments will be recalculated during order update.
491 492 493 494 |
# File 'app/models/spree/order.rb', line 491 def clear_adjustments! adjustments.tax.each(&:destroy) price_adjustments.each(&:destroy) end |
#clone_billing_address ⇒ Object
241 242 243 244 245 246 247 248 |
# File 'app/models/spree/order.rb', line 241 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
156 157 158 |
# File 'app/models/spree/order.rb', line 156 def completed? !! completed_at end |
#confirmation_required? ⇒ Boolean
If true, causes the confirmation step to happen during the checkout process
174 175 176 |
# File 'app/models/spree/order.rb', line 174 def confirmation_required? payments.map(&:payment_method).compact.any?(&:payment_profiles_supported?) end |
#contains?(variant) ⇒ Boolean
324 325 326 |
# File 'app/models/spree/order.rb', line 324 def contains?(variant) find_line_item_by_variant(variant).present? end |
#contents ⇒ Object
266 267 268 |
# File 'app/models/spree/order.rb', line 266 def contents @contents ||= Spree::OrderContents.new(self) end |
#create_proposed_shipments ⇒ Object
532 533 534 535 536 537 538 539 540 541 |
# File 'app/models/spree/order.rb', line 532 def create_proposed_shipments 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.
353 354 355 |
# File 'app/models/spree/order.rb', line 353 def create_tax_charge! Spree::TaxRate.adjust(self) end |
#credit_cards ⇒ Object
375 376 377 378 |
# File 'app/models/spree/order.rb', line 375 def credit_cards credit_card_ids = payments.from_credit_card.pluck(:source_id).uniq CreditCard.scoped(conditions: { id: credit_card_ids }) end |
#currency ⇒ Object
124 125 126 |
# File 'app/models/spree/order.rb', line 124 def currency self[:currency] || Spree::Config[:currency] end |
#deliver_order_confirmation_email ⇒ Object
409 410 411 412 413 414 415 416 |
# File 'app/models/spree/order.rb', line 409 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 |
#display_adjustment_total ⇒ Object
136 137 138 |
# File 'app/models/spree/order.rb', line 136 def display_adjustment_total Spree::Money.new(adjustment_total, { currency: currency }) end |
#display_item_total ⇒ Object
132 133 134 |
# File 'app/models/spree/order.rb', line 132 def display_item_total Spree::Money.new(item_total, { currency: currency }) end |
#display_outstanding_balance ⇒ Object
128 129 130 |
# File 'app/models/spree/order.rb', line 128 def display_outstanding_balance Spree::Money.new(outstanding_balance, { currency: currency }) end |
#display_ship_total ⇒ Object
144 145 146 |
# File 'app/models/spree/order.rb', line 144 def display_ship_total Spree::Money.new(ship_total, { currency: currency }) end |
#display_tax_total ⇒ Object
140 141 142 |
# File 'app/models/spree/order.rb', line 140 def display_tax_total Spree::Money.new(tax_total, { currency: currency }) end |
#display_total ⇒ Object
148 149 150 |
# File 'app/models/spree/order.rb', line 148 def display_total Spree::Money.new(total, { currency: currency }) end |
#empty! ⇒ Object
484 485 486 487 |
# File 'app/models/spree/order.rb', line 484 def empty! line_items.destroy_all adjustments.destroy_all 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.
204 205 206 207 |
# File 'app/models/spree/order.rb', line 204 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
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 |
# File 'app/models/spree/order.rb', line 382 def finalize! touch :completed_at # lock all adjustments (coupon promotions, etc.) adjustments.each { |adjustment| adjustment.update_column('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 self.state_changes.create({ previous_state: 'cart', next_state: 'complete', name: 'order' , user_id: self.user_id }, without_protection: true) end |
#find_line_item_by_variant(variant) ⇒ Object
333 334 335 |
# File 'app/models/spree/order.rb', line 333 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
305 306 307 308 309 310 311 312 313 |
# File 'app/models/spree/order.rb', line 305 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
496 497 498 |
# File 'app/models/spree/order.rb', line 496 def has_step?(step) checkout_steps.include?(step) end |
#has_unprocessed_payments? ⇒ Boolean
Used by the checkout state machine to check for unprocessed payments The Order should be unable to proceed to complete if there are unprocessed payments and there is payment required.
181 182 183 |
# File 'app/models/spree/order.rb', line 181 def has_unprocessed_payments? payments.with_state('checkout').reload.exists? end |
#insufficient_stock_lines ⇒ Object
463 464 465 |
# File 'app/models/spree/order.rb', line 463 def insufficient_stock_lines line_items.select &:insufficient_stock? end |
#item_count ⇒ Object
Indicates the number of items in the order
186 187 188 |
# File 'app/models/spree/order.rb', line 186 def item_count line_items.inject(0) { |sum, li| sum + li.quantity } end |
#merge!(order) ⇒ Object
467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 |
# File 'app/models/spree/order.rb', line 467 def merge!(order) order.line_items.each do |line_item| next unless line_item.currency == currency current_line_item = self.line_items.find_by_variant_id(line_item.variant_id) 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 # 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
365 366 367 368 369 |
# File 'app/models/spree/order.rb', line 365 def name if (address = bill_address || ship_address) "#{address.firstname} #{address.lastname}" end end |
#outstanding_balance ⇒ Object
357 358 359 |
# File 'app/models/spree/order.rb', line 357 def outstanding_balance total - payment_total end |
#outstanding_balance? ⇒ Boolean
361 362 363 |
# File 'app/models/spree/order.rb', line 361 def outstanding_balance? self.outstanding_balance != 0 end |
#paid? ⇒ Boolean
Helper methods for checkout steps
419 420 421 |
# File 'app/models/spree/order.rb', line 419 def paid? payment_state == 'paid' end |
#payment_required? ⇒ Boolean
Is this a free order in which case the payment step should be skipped
169 170 171 |
# File 'app/models/spree/order.rb', line 169 def payment_required? total.to_f > 0.0 end |
#pending_payments ⇒ Object
427 428 429 |
# File 'app/models/spree/order.rb', line 427 def pending_payments payments.select(&:checkout?) end |
#price_adjustment_totals ⇒ Object
Array of totals grouped by Adjustment#label. Useful for displaying price adjustments on an invoice. For example, you can display tax breakout for cases where tax is included in price.
222 223 224 225 226 227 |
# File 'app/models/spree/order.rb', line 222 def price_adjustment_totals Hash[price_adjustments.group_by(&:label).map do |label, adjustments| total = adjustments.sum(&:amount) [label, Spree::Money.new(total, { currency: currency })] end] end |
#price_adjustments ⇒ Object
Array of adjustments that are inclusive in the variant price. Useful for when prices include tax (ex. VAT) and you need to record the tax amount separately.
211 212 213 214 215 216 217 |
# File 'app/models/spree/order.rb', line 211 def price_adjustments adjustments = [] line_items.each { |line_item| adjustments.concat line_item.adjustments } adjustments end |
#process_payments! ⇒ Object
431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 |
# File 'app/models/spree/order.rb', line 431 def process_payments! begin pending_payments.each do |payment| break if payment_total >= total payment.process! if payment.completed? self.payment_total += payment.amount end end rescue Core::GatewayError !!Spree::Config[:allow_checkout_on_gateway_error] end end |
#products ⇒ Object
455 456 457 |
# File 'app/models/spree/order.rb', line 455 def products line_items.map(&:product) end |
#promo_total ⇒ Object
524 525 526 |
# File 'app/models/spree/order.rb', line 524 def promo_total adjustments.eligible.promotion.map(&:amount).sum end |
#promotion_credit_exists?(promotion) ⇒ 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 to apply to order no more than once.
520 521 522 |
# File 'app/models/spree/order.rb', line 520 def promotion_credit_exists?(promotion) !! adjustments.promotion.reload.detect { |credit| credit.originator.promotion.id == promotion.id } end |
#quantity_of(variant) ⇒ Object
328 329 330 331 |
# File 'app/models/spree/order.rb', line 328 def quantity_of(variant) line_item = find_line_item_by_variant(variant) line_item ? line_item.quantity : 0 end |
#remove_invalid_shipments! ⇒ Object
Clear shipment when transitioning to delivery step of checkout if the current shipping address is not eligible for the existing shipping method
347 348 349 |
# File 'app/models/spree/order.rb', line 347 def remove_invalid_shipments! shipments.each { |s| s.destroy unless s.shipping_method.available_to_order?(self) } end |
#remove_variant(variant, quantity = 1) ⇒ Object
276 277 278 279 |
# File 'app/models/spree/order.rb', line 276 def remove_variant(variant, quantity = 1) ActiveSupport::Deprecation.warn("[SPREE] Spree::Order#remove_variant will be deprecated in Spree 2.1, please use order.contents.remove instead.") contents.remove(variant, quantity) end |
#ship_total ⇒ Object
337 338 339 |
# File 'app/models/spree/order.rb', line 337 def ship_total adjustments.shipping.map(&:amount).sum end |
#shipment ⇒ Object
315 316 317 318 |
# File 'app/models/spree/order.rb', line 315 def shipment ActiveSupport::Deprecation.warn("[SPREE] Spree::Order#shipment is typically incorrect due to multiple shipments and will be deprecated in Spree 2.1, please process Spree::Order#shipments instead.") @shipment ||= shipments.last end |
#shipped? ⇒ Boolean
528 529 530 |
# File 'app/models/spree/order.rb', line 528 def shipped? %w(partial shipped).include?(shipment_state) end |
#shipped_shipments ⇒ Object
320 321 322 |
# File 'app/models/spree/order.rb', line 320 def shipped_shipments shipments.shipped end |
#state_changed(name) ⇒ Object
500 501 502 503 504 505 506 507 508 509 510 511 |
# File 'app/models/spree/order.rb', line 500 def state_changed(name) state = "#{name}_state" if persisted? old_state = self.send("#{state}_was") self.state_changes.create({ previous_state: old_state, next_state: self.send(state), name: name, user_id: self.user_id }, without_protection: true) end end |
#tax_total ⇒ Object
341 342 343 |
# File 'app/models/spree/order.rb', line 341 def tax_total adjustments.tax.map(&:amount).sum 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
196 197 198 199 |
# File 'app/models/spree/order.rb', line 196 def tax_zone zone_address = Spree::Config[:tax_using_ship_address] ? ship_address : bill_address Zone.match(zone_address) || Zone.default_tax end |
#to_param ⇒ Object
152 153 154 |
# File 'app/models/spree/order.rb', line 152 def to_param number.to_s.to_url.upcase end |
#update! ⇒ Object
233 234 235 |
# File 'app/models/spree/order.rb', line 233 def update! updater.update end |
#update_totals ⇒ Object
237 238 239 |
# File 'app/models/spree/order.rb', line 237 def update_totals updater.update_totals end |
#updater ⇒ Object
229 230 231 |
# File 'app/models/spree/order.rb', line 229 def updater @updater ||= OrderUpdater.new(self) end |
#variants ⇒ Object
459 460 461 |
# File 'app/models/spree/order.rb', line 459 def variants line_items.map(&:variant) end |