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
- #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
- #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
-
#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.
- #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
-
#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
- #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.
61 62 63 |
# File 'app/models/spree/order.rb', line 61 def use_billing @use_billing end |
Class Method Details
.between(start_date, end_date) ⇒ Object
80 81 82 |
# File 'app/models/spree/order.rb', line 80 def self.between(start_date, end_date) where(created_at: start_date..end_date) end |
.by_customer(customer) ⇒ Object
84 85 86 |
# File 'app/models/spree/order.rb', line 84 def self.by_customer(customer) joins(:user).where("#{Spree.user_class.table_name}.email" => customer) end |
.by_number(number) ⇒ Object
76 77 78 |
# File 'app/models/spree/order.rb', line 76 def self.by_number(number) where(number: number) end |
.by_state(state) ⇒ Object
88 89 90 |
# File 'app/models/spree/order.rb', line 88 def self.by_state(state) where(state: state) end |
.complete ⇒ Object
92 93 94 |
# File 'app/models/spree/order.rb', line 92 def self.complete where('completed_at IS NOT NULL') end |
.incomplete ⇒ Object
96 97 98 |
# File 'app/models/spree/order.rb', line 96 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
102 103 104 |
# File 'app/models/spree/order.rb', line 102 def self.register_update_hook(hook) self.update_hooks.add(hook) end |
Instance Method Details
#allow_cancel? ⇒ Boolean
224 225 226 227 |
# File 'app/models/spree/order.rb', line 224 def allow_cancel? return false unless completed? and state != 'canceled' shipment_state.nil? || %w{ready backorder pending}.include?(shipment_state) end |
#allow_resume? ⇒ Boolean
229 230 231 232 233 234 |
# File 'app/models/spree/order.rb', line 229 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
107 108 109 |
# File 'app/models/spree/order.rb', line 107 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
364 365 366 |
# File 'app/models/spree/order.rb', line 364 def available_payment_methods @available_payment_methods ||= PaymentMethod.available(:front_end) end |
#awaiting_returns? ⇒ Boolean
236 237 238 |
# File 'app/models/spree/order.rb', line 236 def awaiting_returns? .any? { || . } end |
#backordered? ⇒ Boolean
170 171 172 |
# File 'app/models/spree/order.rb', line 170 def backordered? shipments.any?(&:backordered?) end |
#billing_firstname ⇒ Object
404 405 406 |
# File 'app/models/spree/order.rb', line 404 def billing_firstname bill_address.try(:firstname) end |
#billing_lastname ⇒ Object
408 409 410 |
# File 'app/models/spree/order.rb', line 408 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.
151 152 153 |
# File 'app/models/spree/order.rb', line 151 def checkout_allowed? line_items.count > 0 end |
#clear_adjustments! ⇒ Object
446 447 448 449 |
# File 'app/models/spree/order.rb', line 446 def clear_adjustments! self.adjustments.destroy_all self.line_item_adjustments.destroy_all end |
#clone_billing_address ⇒ Object
215 216 217 218 219 220 221 222 |
# File 'app/models/spree/order.rb', line 215 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
143 144 145 |
# File 'app/models/spree/order.rb', line 143 def completed? completed_at.present? end |
#confirmation_required? ⇒ Boolean
If true, causes the confirmation step to happen during the checkout process
161 162 163 |
# File 'app/models/spree/order.rb', line 161 def confirmation_required? payments.map(&:payment_method).compact.any?(&:payment_profiles_supported?) 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
490 491 492 493 494 495 496 497 498 499 500 |
# File 'app/models/spree/order.rb', line 490 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
111 112 113 |
# File 'app/models/spree/order.rb', line 111 def currency self[:currency] || Spree::Config[:currency] end |
#deliver_order_confirmation_email ⇒ Object
350 351 352 353 354 355 356 357 |
# File 'app/models/spree/order.rb', line 350 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
123 124 125 |
# File 'app/models/spree/order.rb', line 123 def display_adjustment_total Spree::Money.new(adjustment_total, { currency: currency }) end |
#display_item_total ⇒ Object
119 120 121 |
# File 'app/models/spree/order.rb', line 119 def display_item_total Spree::Money.new(item_total, { currency: currency }) end |
#display_outstanding_balance ⇒ Object
115 116 117 |
# File 'app/models/spree/order.rb', line 115 def display_outstanding_balance Spree::Money.new(outstanding_balance, { currency: currency }) end |
#display_ship_total ⇒ Object
131 132 133 |
# File 'app/models/spree/order.rb', line 131 def display_ship_total Spree::Money.new(ship_total, { currency: currency }) end |
#display_tax_total ⇒ Object
127 128 129 |
# File 'app/models/spree/order.rb', line 127 def display_tax_total Spree::Money.new(tax_total, { currency: currency }) end |
#display_total ⇒ Object
135 136 137 |
# File 'app/models/spree/order.rb', line 135 def display_total Spree::Money.new(total, { currency: currency }) end |
#empty! ⇒ Object
441 442 443 444 |
# File 'app/models/spree/order.rb', line 441 def empty! line_items.destroy_all adjustments.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
507 508 509 510 511 512 |
# File 'app/models/spree/order.rb', line 507 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.
183 184 185 186 |
# File 'app/models/spree/order.rb', line 183 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 342 343 344 345 346 347 348 |
# 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 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
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
451 452 453 |
# File 'app/models/spree/order.rb', line 451 def has_step?(step) checkout_steps.include?(step) end |
#insufficient_stock_lines ⇒ Object
420 421 422 |
# File 'app/models/spree/order.rb', line 420 def insufficient_stock_lines line_items.select &:insufficient_stock? end |
#item_count ⇒ Object
Indicates the number of items in the order
166 167 168 |
# File 'app/models/spree/order.rb', line 166 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.
196 197 198 199 200 201 |
# File 'app/models/spree/order.rb', line 196 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 |
#merge!(order) ⇒ Object
424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 |
# File 'app/models/spree/order.rb', line 424 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: 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 # 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
360 361 362 |
# File 'app/models/spree/order.rb', line 360 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
156 157 158 |
# File 'app/models/spree/order.rb', line 156 def payment_required? total.to_f > 0.0 end |
#pending_payments ⇒ Object
368 369 370 |
# File 'app/models/spree/order.rb', line 368 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
385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 |
# File 'app/models/spree/order.rb', line 385 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
412 413 414 |
# File 'app/models/spree/order.rb', line 412 def products line_items.map(&:product) end |
#promo_total ⇒ Object
482 483 484 |
# File 'app/models/spree/order.rb', line 482 def promo_total adjustments.eligible.promotion.map(&:amount).sum 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
478 479 480 |
# File 'app/models/spree/order.rb', line 478 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
514 515 516 |
# File 'app/models/spree/order.rb', line 514 def refresh_shipment_rates shipments.map &:refresh_rates end |
#ship_total ⇒ Object
284 285 286 |
# File 'app/models/spree/order.rb', line 284 def ship_total adjustments.shipping.map(&:amount).sum end |
#shipped? ⇒ Boolean
486 487 488 |
# File 'app/models/spree/order.rb', line 486 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 |
#state_changed(name) ⇒ Object
455 456 457 458 459 460 461 462 463 464 465 466 |
# File 'app/models/spree/order.rb', line 455 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
189 190 191 |
# File 'app/models/spree/order.rb', line 189 def tax_address Spree::Config[:tax_using_ship_address] ? ship_address : bill_address end |
#tax_total ⇒ Object
288 289 290 |
# File 'app/models/spree/order.rb', line 288 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
176 177 178 |
# File 'app/models/spree/order.rb', line 176 def tax_zone Zone.match(tax_address) || Zone.default_tax end |
#to_param ⇒ Object
139 140 141 |
# File 'app/models/spree/order.rb', line 139 def to_param number.to_s.to_url.upcase end |
#update! ⇒ Object
207 208 209 |
# File 'app/models/spree/order.rb', line 207 def update! updater.update end |
#update_totals ⇒ Object
211 212 213 |
# File 'app/models/spree/order.rb', line 211 def update_totals updater.update_totals end |
#updater ⇒ Object
203 204 205 |
# File 'app/models/spree/order.rb', line 203 def updater @updater ||= OrderUpdater.new(self) end |
#variants ⇒ Object
416 417 418 |
# File 'app/models/spree/order.rb', line 416 def variants line_items.map(&:variant) end |