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
-
#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
- #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_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.
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.
62 63 64 |
# File 'app/models/spree/order.rb', line 62 def use_billing @use_billing end |
Class Method Details
.between(start_date, end_date) ⇒ Object
81 82 83 |
# File 'app/models/spree/order.rb', line 81 def self.between(start_date, end_date) where(created_at: start_date..end_date) end |
.by_customer(customer) ⇒ Object
85 86 87 |
# File 'app/models/spree/order.rb', line 85 def self.by_customer(customer) joins(:user).where("#{Spree.user_class.table_name}.email" => customer) end |
.by_number(number) ⇒ Object
77 78 79 |
# File 'app/models/spree/order.rb', line 77 def self.by_number(number) where(number: number) end |
.by_state(state) ⇒ Object
89 90 91 |
# File 'app/models/spree/order.rb', line 89 def self.by_state(state) where(state: state) end |
.complete ⇒ Object
93 94 95 |
# File 'app/models/spree/order.rb', line 93 def self.complete where('completed_at IS NOT NULL') end |
.incomplete ⇒ Object
97 98 99 |
# File 'app/models/spree/order.rb', line 97 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
103 104 105 |
# File 'app/models/spree/order.rb', line 103 def self.register_update_hook(hook) self.update_hooks.add(hook) end |
Instance Method Details
#allow_cancel? ⇒ Boolean
229 230 231 232 |
# File 'app/models/spree/order.rb', line 229 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
108 109 110 |
# File 'app/models/spree/order.rb', line 108 def amount line_items.inject(0.0) { |sum, li| sum + li.amount } end |
#associate_user!(user) ⇒ Object
Associates the specified user with the order.
243 244 245 246 247 248 249 250 251 252 |
# File 'app/models/spree/order.rb', line 243 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
362 363 364 |
# File 'app/models/spree/order.rb', line 362 def available_payment_methods @available_payment_methods ||= PaymentMethod.available(:front_end) end |
#awaiting_returns? ⇒ Boolean
234 235 236 |
# File 'app/models/spree/order.rb', line 234 def awaiting_returns? .any? { || . } end |
#backordered? ⇒ Boolean
175 176 177 |
# File 'app/models/spree/order.rb', line 175 def backordered? shipments.any?(&:backordered?) end |
#billing_firstname ⇒ Object
402 403 404 |
# File 'app/models/spree/order.rb', line 402 def billing_firstname bill_address.try(:firstname) end |
#billing_lastname ⇒ Object
406 407 408 |
# File 'app/models/spree/order.rb', line 406 def billing_lastname bill_address.try(:lastname) end |
#can_ship? ⇒ Boolean
310 311 312 |
# File 'app/models/spree/order.rb', line 310 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.
152 153 154 |
# File 'app/models/spree/order.rb', line 152 def checkout_allowed? line_items.count > 0 end |
#clear_adjustments! ⇒ Object
447 448 449 450 |
# File 'app/models/spree/order.rb', line 447 def clear_adjustments! self.adjustments.destroy_all self.line_item_adjustments.destroy_all end |
#clone_billing_address ⇒ Object
220 221 222 223 224 225 226 227 |
# File 'app/models/spree/order.rb', line 220 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
144 145 146 |
# File 'app/models/spree/order.rb', line 144 def completed? completed_at.present? || complete? end |
#confirmation_required? ⇒ Boolean
If true, causes the confirmation step to happen during the checkout process
162 163 164 165 166 167 168 |
# File 'app/models/spree/order.rb', line 162 def confirmation_required? if payments.empty? and Spree::Config[:always_include_confirm_step] true else payments.map(&:payment_method).compact.any?(&:payment_profiles_supported?) end end |
#contains?(variant) ⇒ Boolean
269 270 271 |
# File 'app/models/spree/order.rb', line 269 def contains?(variant) find_line_item_by_variant(variant).present? end |
#contents ⇒ Object
238 239 240 |
# File 'app/models/spree/order.rb', line 238 def contents @contents ||= Spree::OrderContents.new(self) end |
#create_proposed_shipments ⇒ Object
499 500 501 502 503 504 505 506 507 508 509 |
# File 'app/models/spree/order.rb', line 499 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.
292 293 294 |
# File 'app/models/spree/order.rb', line 292 def create_tax_charge! Spree::TaxRate.adjust(self) end |
#credit_cards ⇒ Object
314 315 316 317 |
# File 'app/models/spree/order.rb', line 314 def credit_cards credit_card_ids = payments.from_credit_card.pluck(:source_id).uniq CreditCard.where(id: credit_card_ids) end |
#currency ⇒ Object
112 113 114 |
# File 'app/models/spree/order.rb', line 112 def currency self[:currency] || Spree::Config[:currency] end |
#deliver_order_confirmation_email ⇒ Object
348 349 350 351 352 353 354 355 |
# File 'app/models/spree/order.rb', line 348 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
491 492 493 |
# File 'app/models/spree/order.rb', line 491 def discount_total promo_total + manual_adjustment_total end |
#display_adjustment_total ⇒ Object
124 125 126 |
# File 'app/models/spree/order.rb', line 124 def display_adjustment_total Spree::Money.new(adjustment_total, { currency: currency }) end |
#display_item_total ⇒ Object
120 121 122 |
# File 'app/models/spree/order.rb', line 120 def display_item_total Spree::Money.new(item_total, { currency: currency }) end |
#display_outstanding_balance ⇒ Object
116 117 118 |
# File 'app/models/spree/order.rb', line 116 def display_outstanding_balance Spree::Money.new(outstanding_balance, { currency: currency }) end |
#display_ship_total ⇒ Object
132 133 134 |
# File 'app/models/spree/order.rb', line 132 def display_ship_total Spree::Money.new(ship_total, { currency: currency }) end |
#display_tax_total ⇒ Object
128 129 130 |
# File 'app/models/spree/order.rb', line 128 def display_tax_total Spree::Money.new(tax_total, { currency: currency }) end |
#display_total ⇒ Object
136 137 138 |
# File 'app/models/spree/order.rb', line 136 def display_total Spree::Money.new(total, { currency: currency }) end |
#empty! ⇒ Object
442 443 444 445 |
# File 'app/models/spree/order.rb', line 442 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
516 517 518 519 520 521 |
# File 'app/models/spree/order.rb', line 516 def ensure_updated_shipments if shipments.any? self.shipments.destroy_all self.update_column(:state, "address") 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.
188 189 190 191 |
# File 'app/models/spree/order.rb', line 188 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
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 |
# File 'app/models/spree/order.rb', line 321 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 self.state_changes.create( previous_state: 'cart', next_state: 'complete', name: 'order' , user_id: self.user_id ) end |
#find_line_item_by_variant(variant) ⇒ Object
278 279 280 |
# File 'app/models/spree/order.rb', line 278 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
255 256 257 258 259 260 261 262 263 |
# File 'app/models/spree/order.rb', line 255 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
452 453 454 |
# File 'app/models/spree/order.rb', line 452 def has_step?(step) checkout_steps.include?(step) end |
#insufficient_stock_lines ⇒ Object
418 419 420 |
# File 'app/models/spree/order.rb', line 418 def insufficient_stock_lines line_items.select &:insufficient_stock? end |
#item_count ⇒ Object
Indicates the number of items in the order
171 172 173 |
# File 'app/models/spree/order.rb', line 171 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.
201 202 203 204 205 206 |
# File 'app/models/spree/order.rb', line 201 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
487 488 489 |
# File 'app/models/spree/order.rb', line 487 def manual_adjustment_total adjustments.eligible.manual.sum(:amount) end |
#merge!(order, user = nil) ⇒ Object
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 |
# File 'app/models/spree/order.rb', line 422 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
304 305 306 307 308 |
# File 'app/models/spree/order.rb', line 304 def name if (address = bill_address || ship_address) "#{address.firstname} #{address.lastname}" end end |
#outstanding_balance ⇒ Object
296 297 298 |
# File 'app/models/spree/order.rb', line 296 def outstanding_balance total - payment_total end |
#outstanding_balance? ⇒ Boolean
300 301 302 |
# File 'app/models/spree/order.rb', line 300 def outstanding_balance? self.outstanding_balance != 0 end |
#paid? ⇒ Boolean
Helper methods for checkout steps
358 359 360 |
# File 'app/models/spree/order.rb', line 358 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
157 158 159 |
# File 'app/models/spree/order.rb', line 157 def payment_required? total.to_f > 0.0 end |
#pending_payments ⇒ Object
366 367 368 |
# File 'app/models/spree/order.rb', line 366 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
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 |
# File 'app/models/spree/order.rb', line 383 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
410 411 412 |
# File 'app/models/spree/order.rb', line 410 def products line_items.map(&:product) end |
#promo_total ⇒ Object
483 484 485 |
# File 'app/models/spree/order.rb', line 483 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
479 480 481 |
# File 'app/models/spree/order.rb', line 479 def promotion_credit_exists?(originator) !! adjustments.includes(:originator).promotion.reload.detect { |credit| credit.originator.id == originator.id } end |
#quantity_of(variant) ⇒ Object
273 274 275 276 |
# File 'app/models/spree/order.rb', line 273 def quantity_of(variant) line_item = find_line_item_by_variant(variant) line_item ? line_item.quantity : 0 end |
#refresh_shipment_rates ⇒ Object
523 524 525 |
# File 'app/models/spree/order.rb', line 523 def refresh_shipment_rates shipments.map &:refresh_rates end |
#ship_total ⇒ Object
282 283 284 |
# File 'app/models/spree/order.rb', line 282 def ship_total adjustments.shipping.sum(:amount) end |
#shipped? ⇒ Boolean
495 496 497 |
# File 'app/models/spree/order.rb', line 495 def shipped? %w(partial shipped).include?(shipment_state) end |
#shipped_shipments ⇒ Object
265 266 267 |
# File 'app/models/spree/order.rb', line 265 def shipped_shipments shipments.shipped end |
#shipping_eq_billing_address? ⇒ Boolean
527 528 529 |
# File 'app/models/spree/order.rb', line 527 def shipping_eq_billing_address? (bill_address.empty? && ship_address.empty?) || bill_address.same_as?(ship_address) end |
#state_changed(name) ⇒ Object
456 457 458 459 460 461 462 463 464 465 466 467 |
# File 'app/models/spree/order.rb', line 456 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 ) end end |
#tax_address ⇒ Object
Returns the address for taxation based on configuration
194 195 196 |
# File 'app/models/spree/order.rb', line 194 def tax_address Spree::Config[:tax_using_ship_address] ? ship_address : bill_address end |
#tax_total ⇒ Object
286 287 288 |
# File 'app/models/spree/order.rb', line 286 def tax_total adjustments.tax.sum(:amount) 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
181 182 183 |
# File 'app/models/spree/order.rb', line 181 def tax_zone Zone.match(tax_address) || Zone.default_tax end |
#to_param ⇒ Object
140 141 142 |
# File 'app/models/spree/order.rb', line 140 def to_param number.to_s.to_url.upcase end |
#update! ⇒ Object
212 213 214 |
# File 'app/models/spree/order.rb', line 212 def update! updater.update end |
#update_totals ⇒ Object
216 217 218 |
# File 'app/models/spree/order.rb', line 216 def update_totals updater.update_totals end |
#updater ⇒ Object
208 209 210 |
# File 'app/models/spree/order.rb', line 208 def updater @updater ||= OrderUpdater.new(self) end |
#variants ⇒ Object
414 415 416 |
# File 'app/models/spree/order.rb', line 414 def variants line_items.map(&:variant) end |