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
TODO should be deprecated by split shipments convenience method since many stores will not allow user to create multiple shipments.
- #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.
40 41 42 |
# File 'app/models/spree/order.rb', line 40 def coupon_code @coupon_code end |
#use_billing ⇒ Object
Returns the value of attribute use_billing.
77 78 79 |
# File 'app/models/spree/order.rb', line 77 def use_billing @use_billing end |
Class Method Details
.between(start_date, end_date) ⇒ Object
96 97 98 |
# File 'app/models/spree/order.rb', line 96 def self.between(start_date, end_date) where(created_at: start_date..end_date) end |
.by_customer(customer) ⇒ Object
100 101 102 |
# File 'app/models/spree/order.rb', line 100 def self.by_customer(customer) joins(:user).where("#{Spree.user_class.table_name}.email" => customer) end |
.by_number(number) ⇒ Object
92 93 94 |
# File 'app/models/spree/order.rb', line 92 def self.by_number(number) where(number: number) end |
.by_state(state) ⇒ Object
104 105 106 |
# File 'app/models/spree/order.rb', line 104 def self.by_state(state) where(state: state) end |
.complete ⇒ Object
108 109 110 |
# File 'app/models/spree/order.rb', line 108 def self.complete where('completed_at IS NOT NULL') end |
.incomplete ⇒ Object
112 113 114 |
# File 'app/models/spree/order.rb', line 112 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
118 119 120 |
# File 'app/models/spree/order.rb', line 118 def self.register_update_hook(hook) self.update_hooks.add(hook) end |
Instance Method Details
#add_variant(variant, quantity = 1, currency = nil) ⇒ Object
274 275 276 277 278 |
# File 'app/models/spree/order.rb', line 274 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
254 255 256 257 |
# File 'app/models/spree/order.rb', line 254 def allow_cancel? return false unless completed? and state != 'canceled' shipment_state.nil? || %w{ready backorder pending}.include?(shipment_state) end |
#allow_resume? ⇒ Boolean
259 260 261 262 263 264 |
# File 'app/models/spree/order.rb', line 259 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
123 124 125 |
# File 'app/models/spree/order.rb', line 123 def amount line_items.inject(0.0) { |sum, li| sum + li.amount } end |
#associate_user!(user) ⇒ Object
Associates the specified user with the order.
300 301 302 303 304 305 306 |
# File 'app/models/spree/order.rb', line 300 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
428 429 430 |
# File 'app/models/spree/order.rb', line 428 def available_payment_methods @available_payment_methods ||= PaymentMethod.available(:front_end) end |
#awaiting_returns? ⇒ Boolean
266 267 268 |
# File 'app/models/spree/order.rb', line 266 def awaiting_returns? .any? { || . } end |
#backordered? ⇒ Boolean
194 195 196 |
# File 'app/models/spree/order.rb', line 194 def backordered? shipments.any?(&:backordered?) end |
#billing_firstname ⇒ Object
452 453 454 |
# File 'app/models/spree/order.rb', line 452 def billing_firstname bill_address.try(:firstname) end |
#billing_lastname ⇒ Object
456 457 458 |
# File 'app/models/spree/order.rb', line 456 def billing_lastname bill_address.try(:lastname) end |
#can_ship? ⇒ Boolean
376 377 378 |
# File 'app/models/spree/order.rb', line 376 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.
167 168 169 |
# File 'app/models/spree/order.rb', line 167 def checkout_allowed? line_items.count > 0 end |
#clear_adjustments! ⇒ Object
destroy any previous adjustments. Adjustments will be recalculated during order update.
496 497 498 499 |
# File 'app/models/spree/order.rb', line 496 def clear_adjustments! adjustments.tax.each(&:destroy) price_adjustments.each(&:destroy) end |
#clone_billing_address ⇒ Object
245 246 247 248 249 250 251 252 |
# File 'app/models/spree/order.rb', line 245 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
159 160 161 |
# File 'app/models/spree/order.rb', line 159 def completed? !! completed_at end |
#confirmation_required? ⇒ Boolean
If true, causes the confirmation step to happen during the checkout process
178 179 180 |
# File 'app/models/spree/order.rb', line 178 def confirmation_required? payments.map(&:payment_method).compact.any?(&:payment_profiles_supported?) end |
#contains?(variant) ⇒ Boolean
329 330 331 |
# File 'app/models/spree/order.rb', line 329 def contains?(variant) find_line_item_by_variant(variant).present? end |
#contents ⇒ Object
270 271 272 |
# File 'app/models/spree/order.rb', line 270 def contents @contents ||= Spree::OrderContents.new(self) end |
#create_proposed_shipments ⇒ Object
537 538 539 540 541 542 543 544 545 546 |
# File 'app/models/spree/order.rb', line 537 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.
358 359 360 |
# File 'app/models/spree/order.rb', line 358 def create_tax_charge! Spree::TaxRate.adjust(self) end |
#credit_cards ⇒ Object
380 381 382 383 |
# File 'app/models/spree/order.rb', line 380 def credit_cards credit_card_ids = payments.from_credit_card.pluck(:source_id).uniq CreditCard.scoped(conditions: { id: credit_card_ids }) end |
#currency ⇒ Object
127 128 129 |
# File 'app/models/spree/order.rb', line 127 def currency self[:currency] || Spree::Config[:currency] end |
#deliver_order_confirmation_email ⇒ Object
414 415 416 417 418 419 420 421 |
# File 'app/models/spree/order.rb', line 414 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
139 140 141 |
# File 'app/models/spree/order.rb', line 139 def display_adjustment_total Spree::Money.new(adjustment_total, { currency: currency }) end |
#display_item_total ⇒ Object
135 136 137 |
# File 'app/models/spree/order.rb', line 135 def display_item_total Spree::Money.new(item_total, { currency: currency }) end |
#display_outstanding_balance ⇒ Object
131 132 133 |
# File 'app/models/spree/order.rb', line 131 def display_outstanding_balance Spree::Money.new(outstanding_balance, { currency: currency }) end |
#display_ship_total ⇒ Object
147 148 149 |
# File 'app/models/spree/order.rb', line 147 def display_ship_total Spree::Money.new(ship_total, { currency: currency }) end |
#display_tax_total ⇒ Object
143 144 145 |
# File 'app/models/spree/order.rb', line 143 def display_tax_total Spree::Money.new(tax_total, { currency: currency }) end |
#display_total ⇒ Object
151 152 153 |
# File 'app/models/spree/order.rb', line 151 def display_total Spree::Money.new(total, { currency: currency }) end |
#empty! ⇒ Object
489 490 491 492 |
# File 'app/models/spree/order.rb', line 489 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.
208 209 210 211 |
# File 'app/models/spree/order.rb', line 208 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
387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 |
# File 'app/models/spree/order.rb', line 387 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
338 339 340 |
# File 'app/models/spree/order.rb', line 338 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
309 310 311 312 313 314 315 316 317 |
# File 'app/models/spree/order.rb', line 309 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
501 502 503 |
# File 'app/models/spree/order.rb', line 501 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.
185 186 187 |
# File 'app/models/spree/order.rb', line 185 def has_unprocessed_payments? payments.with_state('checkout').reload.exists? end |
#insufficient_stock_lines ⇒ Object
468 469 470 |
# File 'app/models/spree/order.rb', line 468 def insufficient_stock_lines line_items.select &:insufficient_stock? end |
#item_count ⇒ Object
Indicates the number of items in the order
190 191 192 |
# File 'app/models/spree/order.rb', line 190 def item_count line_items.inject(0) { |sum, li| sum + li.quantity } end |
#merge!(order) ⇒ Object
472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 |
# File 'app/models/spree/order.rb', line 472 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
370 371 372 373 374 |
# File 'app/models/spree/order.rb', line 370 def name if (address = bill_address || ship_address) "#{address.firstname} #{address.lastname}" end end |
#outstanding_balance ⇒ Object
362 363 364 |
# File 'app/models/spree/order.rb', line 362 def outstanding_balance total - payment_total end |
#outstanding_balance? ⇒ Boolean
366 367 368 |
# File 'app/models/spree/order.rb', line 366 def outstanding_balance? self.outstanding_balance != 0 end |
#paid? ⇒ Boolean
Helper methods for checkout steps
424 425 426 |
# File 'app/models/spree/order.rb', line 424 def paid? payment_state == 'paid' end |
#payment_required? ⇒ Boolean
Is this a free order in which case the payment step should be skipped
172 173 174 175 |
# File 'app/models/spree/order.rb', line 172 def payment_required? update_totals total.to_f > 0.0 end |
#pending_payments ⇒ Object
432 433 434 |
# File 'app/models/spree/order.rb', line 432 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.
226 227 228 229 230 231 |
# File 'app/models/spree/order.rb', line 226 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.
215 216 217 218 219 220 221 |
# File 'app/models/spree/order.rb', line 215 def price_adjustments adjustments = [] line_items.each { |line_item| adjustments.concat line_item.adjustments } adjustments end |
#process_payments! ⇒ Object
436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 |
# File 'app/models/spree/order.rb', line 436 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
460 461 462 |
# File 'app/models/spree/order.rb', line 460 def products line_items.map(&:product) end |
#promo_total ⇒ Object
529 530 531 |
# File 'app/models/spree/order.rb', line 529 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.
525 526 527 |
# File 'app/models/spree/order.rb', line 525 def promotion_credit_exists?(promotion) !! adjustments.promotion.reload.detect { |credit| credit.originator.promotion.id == promotion.id } end |
#quantity_of(variant) ⇒ Object
333 334 335 336 |
# File 'app/models/spree/order.rb', line 333 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
352 353 354 |
# File 'app/models/spree/order.rb', line 352 def remove_invalid_shipments! shipments.each { |s| s.destroy unless s.shipping_method.available_to_order?(self) } end |
#remove_variant(variant, quantity = 1) ⇒ Object
280 281 282 283 |
# File 'app/models/spree/order.rb', line 280 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
342 343 344 |
# File 'app/models/spree/order.rb', line 342 def ship_total adjustments.shipping.map(&:amount).sum end |
#shipment ⇒ Object
TODO should be deprecated by split shipments convenience method since many stores will not allow user to create multiple shipments
321 322 323 |
# File 'app/models/spree/order.rb', line 321 def shipment @shipment ||= shipments.last end |
#shipped? ⇒ Boolean
533 534 535 |
# File 'app/models/spree/order.rb', line 533 def shipped? %w(partial shipped).include?(shipment_state) end |
#shipped_shipments ⇒ Object
325 326 327 |
# File 'app/models/spree/order.rb', line 325 def shipped_shipments shipments.shipped end |
#state_changed(name) ⇒ Object
505 506 507 508 509 510 511 512 513 514 515 516 |
# File 'app/models/spree/order.rb', line 505 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
346 347 348 |
# File 'app/models/spree/order.rb', line 346 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
200 201 202 203 |
# File 'app/models/spree/order.rb', line 200 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
155 156 157 |
# File 'app/models/spree/order.rb', line 155 def to_param number.to_s.to_url.upcase end |
#update! ⇒ Object
237 238 239 |
# File 'app/models/spree/order.rb', line 237 def update! updater.update end |
#update_totals ⇒ Object
241 242 243 |
# File 'app/models/spree/order.rb', line 241 def update_totals updater.update_totals end |
#updater ⇒ Object
233 234 235 |
# File 'app/models/spree/order.rb', line 233 def updater @updater ||= OrderUpdater.new(self) end |
#variants ⇒ Object
464 465 466 |
# File 'app/models/spree/order.rb', line 464 def variants line_items.map(&:variant) end |