Class: Order

Inherits:
ActiveRecord::Base
  • Object
show all
Includes:
ActionView::Helpers::NumberHelper, ActionView::Helpers::TextHelper, ArtfullyOseHelper, Ext::DelayedIndexing, Ext::Integrations::Order, OhNoes::Destroy
Defined in:
app/models/order.rb

Overview

The base order class. An order represents a collection of items purchased at the same time.

Subclasses (and their type) should speak to the location or nature of the order, not the contents of the items WebOrder, BoxOfficeOrder for example. NOT DonationOrder, since orders may contain multiple different item types

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Ext::DelayedIndexing

included

Methods included from ArtfullyOseHelper

#amount_and_nongift, #bootstrapped_type, #build_action_path, #build_order_location, #channel_checkbox, #channel_text, #check_mark, #clean_full_error_messages, #contextual_menu, #credit_card_message, #date_field_tag, #datetime_field_tag, #events_to_options, #fully_qualified_asset_path, #get_selected_class, #icon_link_to, #icon_tag, #link_to_add_fields, #link_to_remove_fields, #nav_dropdown, #number_as_cents, #number_to_dollars, #pluralize_word, #refund_header, #select_event_for_sales_search, #select_membership_type_for_sales_search, #select_pass_type_for_sales_search, #select_show_for_sales_search, #sorted_us_state_abbreviations, #sorted_us_state_names, #thanks_message, #ticket_seller_name, #time_ago_sentence, #time_zone_description, #us_states, #verb_for_save, #widget_script, #with_kit

Methods included from LinkHelper

#active?, #active_link_to, #active_section, #calendar_active_link_to, #in_section, #in_section?, #in_sub_section

Methods included from OhNoes::Destroy

#destroy

Methods included from Ext::Integrations::Order

#fa_id, included

Instance Attribute Details

#per_item_processing_chargeObject

This is a lambda used to by the items to calculate their net



15
16
17
# File 'app/models/order.rb', line 15

def per_item_processing_charge
  @per_item_processing_charge
end

#skip_actionsObject

Returns the value of attribute skip_actions.



28
29
30
# File 'app/models/order.rb', line 28

def skip_actions
  @skip_actions
end

#skip_emailObject

Returns the value of attribute skip_email.



28
29
30
# File 'app/models/order.rb', line 28

def skip_email
  @skip_email
end

Class Method Details

.in_range(start, stop, organization_id = nil) ⇒ Object



99
100
101
102
103
104
105
106
# File 'app/models/order.rb', line 99

def self.in_range(start, stop, organization_id = nil)
  query = after(start).before(stop).includes(:items, :person, :organization).order("created_at DESC")
  if organization_id.present?
    query.where('organization_id = ?', organization_id)
  else
    query
  end
end

.locationObject



167
168
169
# File 'app/models/order.rb', line 167

def self.location
  ""
end

.membership_sale_search(search) ⇒ Object



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'app/models/order.rb', line 261

def self.membership_sale_search(search)
  standard = ::Order.
    joins(:items).
    joins("INNER JOIN memberships ON (items.product_type = #{::Item.sanitize('Membership')} AND items.product_id = memberships.id)").
    joins("INNER JOIN membership_types ON (membership_types.id = memberships.membership_type_id)").
    includes([:organization, :person]).
    group('orders.id')


  standard = standard.after(search.start) if search.start
  standard = standard.before(search.stop) if search.stop
  standard = standard.where('orders.organization_id = ?', search.organization.id) if search.organization
  standard = standard.where("membership_types.id = ?", search.membership_type.id) if search.membership_type if search.membership_type
  standard.all
end

.pass_sale_search(search) ⇒ Object



277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'app/models/order.rb', line 277

def self.pass_sale_search(search)
  standard = ::Order.
    where("orders.type != ?", ::Reseller::Order.name).
    joins(:items).
    joins("INNER JOIN passes ON (items.product_type = #{::Item.sanitize('Pass')} AND items.product_id = passes.id)").
    joins("INNER JOIN pass_types ON (pass_types.id = passes.pass_type_id)").
    includes([:organization, :person]).
    group('orders.id')


  standard = standard.after(search.start) if search.start
  standard = standard.before(search.stop) if search.stop
  standard = standard.where('orders.organization_id = ?', search.organization.id) if search.organization
  standard = standard.where("pass_types.id = ?", search.pass_type.id) if search.pass_type if search.pass_type
  standard.all
end

Instance Method Details

#<<(products) ⇒ Object



212
213
214
# File 'app/models/order.rb', line 212

def <<(products)
  self.items << Array.wrap(products).collect { |product|  Item.for(product, @per_item_processing_charge) }
end

#action_classObject



443
444
445
# File 'app/models/order.rb', line 443

def action_class
  GetAction
end

#action_details(collection) ⇒ Object



518
519
520
521
# File 'app/models/order.rb', line 518

def action_details(collection)
  klass = Kernel.const_get(collection.first.class.name)
  klass.to_sentence(collection)
end

#all_donationsObject



239
240
241
# File 'app/models/order.rb', line 239

def all_donations
  all_items.select(&:donation?)
end

#all_itemsObject



226
227
228
# File 'app/models/order.rb', line 226

def all_items
  merge_and_sort_items
end

#all_ticketsObject



230
231
232
# File 'app/models/order.rb', line 230

def all_tickets
  all_items.select(&:ticket?)
end

#anonymous_purchase?Boolean

Returns:

  • (Boolean)


451
452
453
# File 'app/models/order.rb', line 451

def anonymous_purchase?
  person.try(:dummy?) || false
end

#artfully?Boolean

Returns:

  • (Boolean)


159
160
161
# File 'app/models/order.rb', line 159

def artfully?
  !transaction_id.nil?
end

#assign_buyer_to_tickets(person) ⇒ Object



455
456
457
# File 'app/models/order.rb', line 455

def assign_buyer_to_tickets(person)
  tickets.each {|item| item.assign_person(person) }
end

#assign_person(person, create_actions = true) ⇒ Object



523
524
525
526
527
528
529
530
531
532
533
534
535
# File 'app/models/order.rb', line 523

def assign_person(person, create_actions = true)
  if anonymous_purchase? && !person.new_record?
    transaction do
      self.person = person
      save!
      assign_person_to_actions(person)
      assign_buyer_to_tickets(person)
      children.each do |child_order|
        child_order.assign_person(person, false)
      end
    end
  end
end

#assign_person_to_actions(person) ⇒ Object



459
460
461
462
463
464
# File 'app/models/order.rb', line 459

def assign_person_to_actions(person)
  actions.all.each do |action|
    action.person = person
    action.save!
  end
end

#assignable?Boolean

Returns:

  • (Boolean)


200
201
202
# File 'app/models/order.rb', line 200

def assignable?
  anonymous_purchase? && parent.nil?
end

#calculate_lifetime_valueObject

This method is called after_destroy, so *we cannot async this method*



435
436
437
438
439
440
441
# File 'app/models/order.rb', line 435

def calculate_lifetime_value
  # All of these methods are async on person
  self.person.calculate_lifetime_value
  self.person.calculate_lifetime_ticket_value
  self.person.calculate_lifetime_donations
  self.person.calculate_lifetime_memberships
end

#calculate_when_revenue_appliesObject



150
151
152
153
# File 'app/models/order.rb', line 150

def calculate_when_revenue_applies
  #This should be created_at, but we don't have created_at when before_save fires
  self.revenue_applies_at = self.created_at || DateTime.now
end

#cash?Boolean

Returns:

  • (Boolean)


394
395
396
# File 'app/models/order.rb', line 394

def cash?
  payment_method.eql? CashPayment.payment_method
end

#check?Boolean

Returns:

  • (Boolean)


398
399
400
# File 'app/models/order.rb', line 398

def check?
  payment_method.eql? CheckPayment.payment_method
end

#contact_emailObject



428
429
430
# File 'app/models/order.rb', line 428

def contact_email
  items.try(:first).try(:show).try(:event).try(:contact_email)
end

#create_donation_actionsObject



466
467
468
469
470
471
472
473
474
475
476
477
478
# File 'app/models/order.rb', line 466

def create_donation_actions
  self.items.select(&:donation?).collect do |item|
    action                    = GiveAction.new
    action.person             = self.person
    action.subject            = self
    action.organization_id    = self.organization.id
    action.details            = self.donation_details
    action.occurred_at        = self.created_at
    action.subtype            = "Monetary"
    action.save!
    action
  end
end

#create_generic_action(collection_name) ⇒ Object

Creates actions for collection_name

For example, if we’re creating actions for the passses on this order, call order.create_generic_action(‘passes’) order.rb must respond to .send(collection_name) and the underlying class must implement to_sentence

For example, order.create_generic_action(‘passes’), Pass.to_sentence must be implemented



505
506
507
508
509
510
511
512
513
514
515
516
# File 'app/models/order.rb', line 505

def create_generic_action(collection_name)
  action                  = self.action_class.new
  action.person           = self.person
  action.subject          = self
  action.organization     = self.organization
  action.details          = action_details(self.send(collection_name).collect(&:product)) + ". " + (self.notes || "")
  action.occurred_at      = self.created_at

  action.subtype          = action.subtype
  action.save!
  action      
end

#create_purchase_actionObject



480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
# File 'app/models/order.rb', line 480

def create_purchase_action
  unless self.all_tickets.empty?
    action                  = self.purchase_action_class.new
    action.person           = self.person
    action.subject          = self
    action.organization     = self.organization
    action.details          = self.ticket_details
    action.occurred_at      = self.created_at

    #Weird, but Rails can't initialize these so the subtype is hardcoded in the model
    action.subtype          = action.subtype
    action.import           = self.import if self.import
    action.save!
    action
  end
end

#credit?Boolean

Returns:

  • (Boolean)


390
391
392
# File 'app/models/order.rb', line 390

def credit?
  payment_method.eql? CreditCardPayment.payment_method
end

#destroyable?Boolean

Returns:

  • (Boolean)


183
184
185
# File 'app/models/order.rb', line 183

def destroyable?
  ( (type.eql? "ApplicationOrder") || (type.eql? "ImportedOrder") ) && !is_fafs? && !artfully? && has_single_donation?
end

#discount_codesObject



121
122
123
# File 'app/models/order.rb', line 121

def discount_codes
  tickets.collect(&:discount).uniq.compact
end

#discounts_usedObject

Will return an array of all discount codes on all items on this order



346
347
348
# File 'app/models/order.rb', line 346

def discounts_used
  items.map{|i| i.discount.try(:code)}.reject(&:blank?).uniq
end

#donation_detailsObject



374
375
376
377
378
379
380
# File 'app/models/order.rb', line 374

def donation_details
  if is_fafs?
    "#{number_as_cents sum_donations} donation made through Fractured Atlas"
  else
    "#{number_as_cents sum_donations} donation"
  end
end

#donationsObject



243
244
245
# File 'app/models/order.rb', line 243

def donations
  items.select(&:donation?)
end

#editable?Boolean

Returns:

  • (Boolean)


204
205
206
# File 'app/models/order.rb', line 204

def editable?
  ( (type.eql? "ApplicationOrder") || (type.eql? "ImportedOrder") ) && !is_fafs? && !artfully? && has_single_donation? 
end

#eventsObject

Pass through methods to help out with the checkout thanks page



117
118
119
# File 'app/models/order.rb', line 117

def events
  tickets.collect(&:product).collect(&:event).uniq
end

#exchangeable_itemsObject



307
308
309
# File 'app/models/order.rb', line 307

def exchangeable_items
  items.select(&:exchangeable?)
end

#exchangesObject



155
156
157
# File 'app/models/order.rb', line 155

def exchanges
  tickets.find_all(&:exchanged?)
end

#for_organization(org) ⇒ Object



208
209
210
# File 'app/models/order.rb', line 208

def for_organization(org)
  self.organization = org
end

#has_donation?Boolean

Returns:

  • (Boolean)


323
324
325
# File 'app/models/order.rb', line 323

def has_donation?
  items.select(&:donation?).present?
end

#has_membership?Boolean

Returns:

  • (Boolean)


327
328
329
# File 'app/models/order.rb', line 327

def has_membership?
  items.select(&:membership?).present?
end

#has_pass?Boolean

Returns:

  • (Boolean)


331
332
333
# File 'app/models/order.rb', line 331

def has_pass?
  items.select(&:pass?).present?
end

#has_single_donation?Boolean

Returns:

  • (Boolean)


294
295
296
# File 'app/models/order.rb', line 294

def has_single_donation?
  (donations.size == 1) && tickets.empty?
end

#has_ticket?Boolean

Returns:

  • (Boolean)


319
320
321
# File 'app/models/order.rb', line 319

def has_ticket?
  items.select(&:ticket?).present?
end

#imported?Boolean

Returns:

  • (Boolean)


129
130
131
# File 'app/models/order.rb', line 129

def imported?
  !self.import_id.nil?
end

#is_fafs?Boolean

Returns:

  • (Boolean)


370
371
372
# File 'app/models/order.rb', line 370

def is_fafs?
  !fa_id.nil?
end

#items_that_used_passObject



335
336
337
# File 'app/models/order.rb', line 335

def items_that_used_pass
  items.select{ |i| i.pass_id.present? }
end

#locationObject



163
164
165
# File 'app/models/order.rb', line 163

def location
  self.class.location
end

#membership_typesObject



257
258
259
# File 'app/models/order.rb', line 257

def membership_types
  memberships.collect{|item| item.product.membership_type}.uniq
end

#memberships(reload = false) ⇒ Object

TODO: Undupe these methods



249
250
251
# File 'app/models/order.rb', line 249

def memberships(reload=false)
  items(reload).select(&:membership?)
end

#non_exchanged_totalObject



175
176
177
# File 'app/models/order.rb', line 175

def non_exchanged_total
  all_items.reject(&:exchanged?).inject(0) {|sum, item| sum + item.total_price.to_i }
end

#nongift_amountObject



179
180
181
# File 'app/models/order.rb', line 179

def nongift_amount
  all_items.inject(0) {|sum, item| sum + item.nongift_amount.to_i }
end

#num_ticketsObject



315
316
317
# File 'app/models/order.rb', line 315

def num_tickets
  all_tickets.size
end

#original_orderObject



402
403
404
405
406
407
408
# File 'app/models/order.rb', line 402

def original_order
  if self.parent.nil?
    return self
  else
    return self.parent.original_order
  end
end

#originally_sold_atObject

End checkout methods



142
143
144
# File 'app/models/order.rb', line 142

def originally_sold_at
  original_order.created_at
end

#pass_codesObject



125
126
127
# File 'app/models/order.rb', line 125

def pass_codes
  tickets.collect(&:pass).uniq.compact
end

#passes(reload = false) ⇒ Object



253
254
255
# File 'app/models/order.rb', line 253

def passes(reload=false)
  items(reload).select(&:pass?)
end

#paymentObject



216
217
218
# File 'app/models/order.rb', line 216

def payment
  CreditCardPayment.new(:transaction_id => transaction_id)
end

#processObject



191
192
193
194
# File 'app/models/order.rb', line 191

def process
  @skip_actions ||= false
  OrderProcessor.process(self, {:skip_actions => @skip_actions, :skip_email => self.skip_confirmation_email?})
end

#processor_classObject



196
197
198
# File 'app/models/order.rb', line 196

def processor_class
  OrderProcessor
end

#purchase_action_classObject



447
448
449
# File 'app/models/order.rb', line 447

def purchase_action_class
  GetAction
end

#record_exchange!(exchanged_items) ⇒ Object



220
221
222
223
224
# File 'app/models/order.rb', line 220

def record_exchange!(exchanged_items)
  items.each_with_index do |item, index|
    item.to_exchange! exchanged_items[index]
  end
end

#refundable_itemsObject



302
303
304
305
# File 'app/models/order.rb', line 302

def refundable_items
  return [] unless Payment.create(payment_method).refundable?
  items.select(&:refundable?)
end

#returnable_itemsObject



311
312
313
# File 'app/models/order.rb', line 311

def returnable_items
  items.select { |i| i.returnable? and i.comped? and not i.refundable? }
end

#revenue_applies_to_range(start_date, end_date) ⇒ Object



146
147
148
# File 'app/models/order.rb', line 146

def revenue_applies_to_range(start_date, end_date)
  start_date < self.created_at && self.created_at < end_date
end

#sell_ticketsObject



418
419
420
421
422
# File 'app/models/order.rb', line 418

def sell_tickets
  all_tickets.each do |item|
    item.product.sell_to(self.person, self.created_at)
  end
end

#service_feeObject



112
113
114
# File 'app/models/order.rb', line 112

def service_fee
  items.collect(&:service_fee).sum
end

#settleable_donationsObject



298
299
300
# File 'app/models/order.rb', line 298

def settleable_donations
  all_donations.reject(&:modified?)
end

#showObject



108
109
110
# File 'app/models/order.rb', line 108

def show
  items.first.try(:show)
end

#skip_confirmation_email?Boolean

Returns:

  • (Boolean)


187
188
189
# File 'app/models/order.rb', line 187

def skip_confirmation_email?
  skip_email || anonymous_purchase? || imported? || self.person.email.blank?
end

#sum_donationsObject



339
340
341
# File 'app/models/order.rb', line 339

def sum_donations
  all_donations.collect{|item| item.total_price.to_i}.sum
end

#ticket_detailsObject



350
351
352
353
354
355
356
357
358
359
360
361
362
# File 'app/models/order.rb', line 350

def ticket_details
  String.new.tap do |details|
    if self.tickets.any?
      details << Ticket.to_sentence(self.tickets.map(&:product))
    else
      details << "No tickets"
    end

    if discounts_used.any?
      details << ", used #{'discount'.pluralize(discounts_used.length)} " + discounts_used.join(",")
    end
  end
end

#ticket_summaryObject



382
383
384
385
386
387
388
# File 'app/models/order.rb', line 382

def ticket_summary
  summary = TicketSummary.new
  items.select(&:ticket?).each do |item|
    summary << item.product
  end
  summary
end

#tickets(reload = false) ⇒ Object

TODO: Undupe these methods



235
236
237
# File 'app/models/order.rb', line 235

def tickets(reload=false)
  items(reload).select(&:ticket?)
end

#time_zoneObject



424
425
426
# File 'app/models/order.rb', line 424

def time_zone
  "Eastern Time (US & Canada)"
end

#to_comp!Object



364
365
366
367
368
# File 'app/models/order.rb', line 364

def to_comp!
  items.each do |item|
    item.to_comp!
  end
end

#totalObject



171
172
173
# File 'app/models/order.rb', line 171

def total
  all_items.inject(0) {|sum, item| sum + item.total_price.to_i }
end

#total_discountObject



133
134
135
# File 'app/models/order.rb', line 133

def total_discount
  tickets.collect(&:total_discount).sum
end

#total_with_service_feeObject



137
138
139
# File 'app/models/order.rb', line 137

def total_with_service_fee
  total + service_fee
end

#transaction_idObject

If this order has no transaction_id, run up the parent chain until we hit one This is needed for exchanges that ultimately need to be refunded



414
415
416
# File 'app/models/order.rb', line 414

def transaction_id
  read_attribute(:transaction_id) || self.parent.try(:transaction_id)
end