artfully_ose 1.2.0.pre.21 → 1.2.0.pre.23

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. checksums.yaml +8 -8
  2. data/app/assets/javascripts/change-membership.js +8 -4
  3. data/app/assets/javascripts/custom/show.js +13 -8
  4. data/app/assets/javascripts/locationselector.js +1 -1
  5. data/app/assets/javascripts/store/store.js +7 -0
  6. data/app/assets/stylesheets/application.sass +21 -1
  7. data/app/assets/stylesheets/sass/_tags.sass +13 -2
  8. data/app/controllers/discounts_controller.rb +8 -1
  9. data/app/controllers/events_controller.rb +29 -13
  10. data/app/controllers/events_pass_types_controller.rb +75 -0
  11. data/app/controllers/imports_controller.rb +10 -3
  12. data/app/controllers/membership_types_controller.rb +1 -0
  13. data/app/controllers/pass_types_controller.rb +42 -0
  14. data/app/controllers/passes_controller.rb +13 -0
  15. data/app/controllers/passes_kits_controller.rb +25 -0
  16. data/app/controllers/passes_reports_controller.rb +5 -0
  17. data/app/controllers/sales_controller.rb +0 -2
  18. data/app/controllers/searches_controller.rb +10 -3
  19. data/app/controllers/sections_controller.rb +1 -1
  20. data/app/controllers/segments_controller.rb +4 -4
  21. data/app/controllers/shows_controller.rb +21 -47
  22. data/app/controllers/store/checkouts_controller.rb +6 -1
  23. data/app/controllers/store/orders_controller.rb +2 -0
  24. data/app/controllers/store/passes_controller.rb +9 -0
  25. data/app/controllers/store/store_controller.rb +7 -2
  26. data/app/helpers/link_helper.rb +10 -0
  27. data/app/mailers/reports_mailer.rb +5 -4
  28. data/app/models/cart.rb +21 -16
  29. data/app/models/checkout.rb +6 -1
  30. data/app/models/daily_membership_report.rb +1 -1
  31. data/app/models/daily_pass_report.rb +48 -0
  32. data/app/models/database_views/item_view.rb +69 -19
  33. data/app/models/event.rb +5 -0
  34. data/app/models/events_pass_type.rb +16 -0
  35. data/app/models/ext/integrations.rb +1 -1
  36. data/app/models/ext/preprocessor.rb +1 -0
  37. data/app/models/import.rb +18 -15
  38. data/app/models/imports/donations_import.rb +1 -1
  39. data/app/models/imports/events_import.rb +33 -28
  40. data/app/models/item.rb +13 -3
  41. data/app/models/job/daily_email_report_job.rb +8 -2
  42. data/app/models/job/order_processor.rb +10 -1
  43. data/app/models/job/show_creator.rb +2 -1
  44. data/app/models/kit.rb +1 -1
  45. data/app/models/kits/passes_kit.rb +62 -0
  46. data/app/models/membership.rb +19 -6
  47. data/app/models/membership_change.rb +18 -7
  48. data/app/models/membership_comp.rb +1 -0
  49. data/app/models/membership_type.rb +1 -1
  50. data/app/models/order.rb +18 -9
  51. data/app/models/order_handler.rb +22 -0
  52. data/app/models/organization.rb +7 -0
  53. data/app/models/pass.rb +45 -0
  54. data/app/models/pass_type.rb +19 -0
  55. data/app/models/person.rb +4 -0
  56. data/app/models/search.rb +170 -63
  57. data/app/models/section.rb +2 -0
  58. data/app/models/show.rb +26 -2
  59. data/app/models/show_touch.rb +12 -0
  60. data/app/models/ticket.rb +6 -0
  61. data/app/models/ticket/pricing.rb +1 -0
  62. data/app/models/ticket/reports.rb +16 -0
  63. data/app/models/ticket/sale_transitions.rb +4 -0
  64. data/app/models/ticket/transfers.rb +4 -1
  65. data/app/presenters/event_presenter.rb +1 -1
  66. data/app/views/discounts/_form.html.haml +1 -1
  67. data/app/views/events/_menu.html.haml +4 -13
  68. data/app/views/events/_share_and_sell.haml +2 -1
  69. data/app/views/events_pass_types/_form.html.haml +25 -0
  70. data/app/views/events_pass_types/edit.html.haml +22 -0
  71. data/app/views/events_pass_types/index.html.haml +43 -0
  72. data/app/views/events_pass_types/new.html.haml +22 -0
  73. data/app/views/imports/index.html.haml +2 -2
  74. data/app/views/layouts/_menu.html.haml +2 -1
  75. data/app/views/layouts/storefront.html.haml +3 -1
  76. data/app/views/membership_cancellations/_form.html.haml +1 -1
  77. data/app/views/membership_cancellations/_processing.html.haml +1 -3
  78. data/app/views/membership_types/index.html.haml +1 -1
  79. data/app/views/memberships/index.html.haml +16 -25
  80. data/app/views/orders/_item_table.haml +2 -2
  81. data/app/views/pass_types/_form.html.haml +70 -0
  82. data/app/views/pass_types/_pass_type_fees.html.haml +48 -0
  83. data/app/views/pass_types/edit.html.haml +4 -0
  84. data/app/views/pass_types/index.html.haml +32 -0
  85. data/app/views/pass_types/new.html.haml +4 -0
  86. data/app/views/passes/index.html.haml +40 -0
  87. data/app/views/passes_kits/edit.html.haml +30 -0
  88. data/app/views/passes_reports/index.html.haml +2 -0
  89. data/app/views/people/_header.html.haml +6 -1
  90. data/app/views/reports_mailer/daily.html.haml +19 -0
  91. data/app/views/searches/_form.html.haml +17 -1
  92. data/app/views/shared/_show_time_and_calendar.html.haml +21 -0
  93. data/app/views/shared/_tags.html.haml +2 -2
  94. data/app/views/shows/_controls.html.haml +4 -4
  95. data/app/views/shows/_glance.html.haml +0 -4
  96. data/app/views/shows/_sections_table.html.haml +6 -4
  97. data/app/views/shows/_work_with.html.haml +2 -2
  98. data/app/views/shows/index.html.haml +79 -20
  99. data/app/views/shows/new.html.haml +1 -21
  100. data/app/views/statements/_passes_table.html.haml +25 -0
  101. data/app/views/statements/show.html.haml +4 -1
  102. data/app/views/store/checkouts/thanks.html.haml +13 -5
  103. data/app/views/store/orders/show.html.haml +26 -1
  104. data/app/views/store/passes/index.html.haml +33 -0
  105. data/config/locales/en.yml +2 -0
  106. data/config/routes.rb +8 -2
  107. data/db/migrate/20140207135731_update_items_view.rb +38 -0
  108. data/db/migrate/20140210154723_add_cached_stats.rb +5 -0
  109. data/db/migrate/20140218202726_cache_stats.rb +7 -0
  110. data/db/migrate/20140304171625_passes_ahoy.rb +52 -0
  111. data/db/migrate/20140304174807_add_passes_kit.rb +5 -0
  112. data/db/migrate/20140307144454_add_events_pass_types.rb +9 -0
  113. data/db/migrate/20140307193350_add_pass_id_to_cart.rb +7 -0
  114. data/db/migrate/20140314162422_add_total_paid_to_membership.rb +13 -0
  115. data/db/migrate/20140319191237_add_show_dates_to_advanced_search.rb +6 -0
  116. data/db/migrate/20140328172333_add_cols_to_events_pass_types.rb +6 -0
  117. data/db/migrate/20140328174217_add_deleted_at_to_ept.rb +9 -0
  118. data/db/migrate/20140328185432_add_active_to_ept.rb +5 -0
  119. data/db/migrate/20140328192612_add_pass_type_id_to_search.rb +5 -0
  120. data/lib/artfully_ose.rb +3 -3
  121. data/lib/artfully_ose/version.rb +1 -1
  122. data/spec/factories/kit_factories.rb +5 -0
  123. data/spec/factories/membership_factories.rb +1 -0
  124. metadata +45 -6
@@ -28,6 +28,7 @@ class OrderProcessor < Struct.new(:order, :options)
28
28
  self.order.create_donation_actions unless skip_actions
29
29
  self.order.create_purchase_action unless skip_actions
30
30
  process_memberships
31
+ process_passes
31
32
  end
32
33
 
33
34
  begin
@@ -60,7 +61,15 @@ class OrderProcessor < Struct.new(:order, :options)
60
61
  self.order.memberships.each do |membership_item|
61
62
  Member.for(membership_item.product, self.order.person)
62
63
  end
63
- self.order.create_membership_action
64
+ self.order.create_generic_action("memberships")
65
+ end
66
+
67
+ def process_passes
68
+ self.order.passes.each do |pass_item|
69
+ pass_item.product.person = self.order.person
70
+ pass_item.product.save
71
+ end
72
+ self.order.create_generic_action("passes")
64
73
  end
65
74
 
66
75
  def generate_pdf
@@ -26,7 +26,8 @@ class ShowCreator < Struct.new(:datetimes, :show_params, :chart_params, :event,
26
26
  @show.chart_id = @show.chart.id
27
27
 
28
28
  @show.datetime = DateTime.parse(datetime_string).change(:offset => offset(datetime_string, event.time_zone))
29
- @show.go!(publish)
29
+ @show.go!(publish)
30
+ @show.refresh_stats
30
31
  end
31
32
  end
32
33
  end
data/app/models/kit.rb CHANGED
@@ -61,7 +61,7 @@ class Kit < ActiveRecord::Base
61
61
  end
62
62
 
63
63
  def self.subklasses
64
- @subklasses ||= [ TicketingKit, RegularDonationKit, SponsoredDonationKit, ResellerKit, MailchimpKit, MembershipKit, ScannableTicketsKit ].freeze
64
+ @subklasses ||= [ TicketingKit, RegularDonationKit, SponsoredDonationKit, ResellerKit, MailchimpKit, MembershipKit, PassesKit, ScannableTicketsKit ].freeze
65
65
  end
66
66
 
67
67
  def self.pad_with_new_kits(kits = [])
@@ -0,0 +1,62 @@
1
+ class PassesKit < Kit
2
+ include ActionView::Helpers::SanitizeHelper
3
+
4
+ acts_as_kit :with_approval => true, :admin_only => true do
5
+ approve :unless => :no_bank_account?
6
+
7
+ self.configurable = true
8
+
9
+ state_machine do
10
+ state :cancelled, :enter => :kit_cancelled
11
+ end
12
+
13
+ when_active do |organization|
14
+ organization.can :access, :passes
15
+ end
16
+ end
17
+
18
+ before_save :initialize_accessors
19
+ before_save :sanitize_accessors
20
+
21
+ ACCESSORS = [ :marketing_copy_heading, :marketing_copy_sidebar ]
22
+
23
+ ACCESSORS.each do |accessor|
24
+ attr_accessible accessor
25
+ end
26
+
27
+ store :settings, :accessors => ACCESSORS
28
+
29
+ def friendly_name
30
+ "Passes"
31
+ end
32
+
33
+ def no_bank_account?
34
+ errors.add(:requirements, "Your organization needs bank account information first.") if organization.bank_account.nil?
35
+ organization.bank_account.nil?
36
+ end
37
+
38
+ def pitch
39
+ "Sell Passes!"
40
+ end
41
+
42
+ def configured?
43
+ passes_state == "configured"
44
+ end
45
+
46
+ def configured!
47
+ settings[:passes_state] = "configured"
48
+ save
49
+ end
50
+
51
+ def initialize_accessors
52
+ ACCESSORS.each do |accessor|
53
+ self.send("#{accessor}=", "") if self.send("#{accessor}").nil?
54
+ end
55
+ end
56
+
57
+ def sanitize_accessors
58
+ ACCESSORS.each do |accessor|
59
+ self.send("#{accessor}=", (sanitize self.send(accessor)))
60
+ end
61
+ end
62
+ end
@@ -1,3 +1,7 @@
1
+ #
2
+ # service_fee represents the fee displayed to the patron in the cart.
3
+ # If the producer is eating the fee, service_fee is 0
4
+ #
1
5
  class Membership < ActiveRecord::Base
2
6
  belongs_to :organization
3
7
  belongs_to :member
@@ -18,17 +22,30 @@ class Membership < ActiveRecord::Base
18
22
  scope :lapsed, lambda { |time = Time.now, since = (time - 1.year)| where("ends_at < ?", time).where("ends_at > ?", since.midnight) }
19
23
  scope :past, lambda { |time = Time.now| where("ends_at < ?", time - 1.year) }
20
24
 
25
+ belongs_to :changed_membership, :class_name => "Membership"
26
+ has_one :changed_to, :class_name => "Membership", :foreign_key => "changed_membership_id"
27
+
21
28
  def self.for(membership_type, member = nil)
22
29
  new.tap do |membership|
23
30
  membership.membership_type = membership_type
24
31
  membership.organization = membership_type.organization
25
32
  membership.price = membership_type.price_for(member)
33
+ membership.cart_price = membership.price
26
34
  membership.sold_price = membership.price
35
+ membership.total_paid = membership.price
27
36
  membership.starts_at = membership_type.starts_at
28
37
  membership.ends_at = membership_type.ends_at
29
38
  end
30
39
  end
31
40
 
41
+ def changed?
42
+ self.changed_to.present?
43
+ end
44
+
45
+ def changee?
46
+ self.changed_membership.present?
47
+ end
48
+
32
49
  def update_member_counters
33
50
  self.member.try(:count_memberships)
34
51
  end
@@ -42,16 +59,12 @@ class Membership < ActiveRecord::Base
42
59
  items.first
43
60
  end
44
61
 
45
- def cart_price
46
- price
47
- end
48
-
49
62
  def self.realized_fee
50
63
  0
51
64
  end
52
65
 
53
- def realized_fee
54
- self.membership_type.hide_fee? ? self.price * MembershipType::SERVICE_FEE : 0
66
+ def realized_fee
67
+ self.membership_type.hide_fee? ? self.cart_price * MembershipType::SERVICE_FEE : 0
55
68
  end
56
69
 
57
70
  def expired?
@@ -50,7 +50,7 @@ class MembershipChange
50
50
 
51
51
  # Add the new memberships
52
52
  new_memberships.each do |membership|
53
- cart.memberships << membership
53
+ @cart.memberships << membership
54
54
  end
55
55
  end
56
56
  @cart
@@ -91,12 +91,17 @@ class MembershipChange
91
91
  @new_memberships = membership_ids.map do |old_membership_id|
92
92
  old = old_memberships[old_membership_id]
93
93
 
94
- # Create the new membership, setting sold price, and the start/end dates from the old one
95
- membership = Membership.for(membership_type)
96
- membership.starts_at = old.starts_at
97
- membership.ends_at = old.ends_at
98
- membership.price = price
99
- membership.member = person.member
94
+ membership = Membership.for(membership_type)
95
+ membership.starts_at = old.starts_at
96
+ membership.ends_at = old.ends_at
97
+
98
+ membership.price = membership.membership_type.price
99
+ membership.cart_price = price
100
+ membership.sold_price = price
101
+ membership.total_paid = old.total_paid + price
102
+ membership.member = person.member
103
+
104
+ membership.changed_membership = old
100
105
 
101
106
  membership
102
107
  end
@@ -169,8 +174,14 @@ class MembershipChange
169
174
  end
170
175
 
171
176
  class Order < ::Order
177
+ before_create :set_details
178
+
172
179
  def membership_action_class
173
180
  ChangeAction
174
181
  end
182
+
183
+ def set_details
184
+ self.details = "Membership type change."
185
+ end
175
186
  end
176
187
  end
@@ -89,6 +89,7 @@ class MembershipComp
89
89
  membership = Membership.for(self.membership_type)
90
90
  membership.ends_at = self.ends_at
91
91
  membership.sold_price = 0
92
+ membership.total_paid = 0
92
93
  membership.welcome_message = self.welcome_message
93
94
  membership.send_email = self.send_email
94
95
  membership.save
@@ -29,7 +29,7 @@ class MembershipType < ActiveRecord::Base
29
29
  memberships 'Memberships sold' do |m|
30
30
  m.count
31
31
  end
32
- members { |m| m.count }
32
+ members { |m| m.distinct(:member_id).count }
33
33
  duration
34
34
  period
35
35
  starts_at
data/app/models/order.rb CHANGED
@@ -238,6 +238,10 @@ class Order < ActiveRecord::Base
238
238
  items(reload).select(&:membership?)
239
239
  end
240
240
 
241
+ def passes(reload=false)
242
+ items(reload).select(&:pass?)
243
+ end
244
+
241
245
  def membership_types
242
246
  memberships.collect{|item| item.product.membership_type}.uniq
243
247
  end
@@ -374,7 +378,7 @@ class Order < ActiveRecord::Base
374
378
  self.person.calculate_lifetime_donations
375
379
  end
376
380
 
377
- def membership_action_class
381
+ def action_class
378
382
  GetAction
379
383
  end
380
384
 
@@ -428,24 +432,29 @@ class Order < ActiveRecord::Base
428
432
  end
429
433
  end
430
434
 
431
- def create_membership_action
432
- unless self.memberships.empty?
433
- action = self.membership_action_class.new
435
+ #
436
+ # Creates actions for collection_name
437
+ #
438
+ # For example, if we're creating actions for the passses on this order, call order.create_generic_action('passes')
439
+ # This order must respond to .send(collection_name)
440
+ #
441
+ def create_generic_action(collection_name)
442
+ unless self.passes.empty?
443
+ action = self.action_class.new
434
444
  action.person = self.person
435
445
  action.subject = self
436
446
  action.organization = self.organization
437
- action.details = membership_details
447
+ action.details = action_details(self.send(collection_name).length, collection_name)
438
448
  action.occurred_at = self.created_at
439
449
 
440
- #Weird, but Rails can't initialize these so the subtype is hardcoded in the model
441
450
  action.subtype = action.subtype
442
451
  action.save!
443
452
  action
444
453
  end
445
454
  end
446
-
447
- def membership_details
448
- details = "#{pluralize(self.memberships.size, 'membership')}"
455
+
456
+ def action_details(quantity, thing)
457
+ details = "#{pluralize(quantity, thing)}"
449
458
  details = details + ". #{self.notes}" unless self.notes.blank?
450
459
  details
451
460
  end
@@ -60,6 +60,14 @@ class OrderHandler
60
60
  @discount
61
61
  end
62
62
 
63
+ def apply_pass(params)
64
+ if params[:pass_code].present?
65
+ @pass = Pass.where(:pass_code => params[:pass_code]).first
66
+ @pass.apply_pass_to_cart(self.cart)
67
+ end
68
+ @pass
69
+ end
70
+
63
71
  def handle_donation(params, organization)
64
72
  if params[:donation_amount]
65
73
  self.cart.clear_donations
@@ -90,4 +98,18 @@ class OrderHandler
90
98
 
91
99
  end
92
100
  end
101
+
102
+ #
103
+ # Note that this handles passes *currently being purchased* NOT passes that are being applied to the cart
104
+ #
105
+ def handle_passes(params)
106
+ unless params[:pass_type].blank?
107
+ pass_type_id = params[:pass_type][:id]
108
+ quantity = params[:quantity].to_i
109
+ (1..quantity).each do |i|
110
+ self.cart.passes << Pass.for(PassType.find(pass_type_id))
111
+ end
112
+
113
+ end
114
+ end
93
115
  end
@@ -22,6 +22,9 @@ class Organization < ActiveRecord::Base
22
22
  has_many :membership_types
23
23
  has_many :memberships
24
24
  has_many :members
25
+
26
+ has_many :pass_types
27
+
25
28
  has_many :searches
26
29
 
27
30
  has_many :users, :through => :user_memberships, :order => 'user_memberships.owner desc'
@@ -110,6 +113,10 @@ class Organization < ActiveRecord::Base
110
113
  self.kits.where(:type => "MembershipKit").first
111
114
  end
112
115
 
116
+ def passes_kit
117
+ self.kits.where(:type => "PassesKit").first
118
+ end
119
+
113
120
  delegate :can?, :cannot?, :to => :ability
114
121
  def ability
115
122
  OrganizationAbility.new(self)
@@ -0,0 +1,45 @@
1
+ class Pass < ActiveRecord::Base
2
+ belongs_to :pass_type
3
+ belongs_to :person
4
+ belongs_to :organization
5
+ has_many :tickets
6
+
7
+ def self.for(pass_type)
8
+ new.tap do |pass|
9
+ pass.pass_type = pass_type
10
+ pass.organization = pass_type.organization
11
+ pass.price = pass_type.price
12
+ pass.sold_price = pass.price
13
+ pass.tickets_allowed = pass_type.tickets_allowed
14
+ pass.tickets_allowed = 0
15
+ pass.pass_code = new_pass_code
16
+ end
17
+ end
18
+
19
+ def cart_price
20
+ price
21
+ end
22
+
23
+ def realized_fee
24
+ self.pass_type.hide_fee? ? self.price * PassType::SERVICE_FEE : 0
25
+ end
26
+
27
+ def self.new_pass_code(size=8)
28
+ # Avoid confusable characters like 1/L and 0/O
29
+ charset = %w{ 2 3 4 6 7 9 A C D E F G H J K M N P Q R T V W X Y Z}
30
+ (0...size).map{ charset.to_a[rand(charset.size)] }.join
31
+ end
32
+
33
+ def apply_pass_to_cart(cart)
34
+ transaction do
35
+ cart.applied_pass = self
36
+ cart.tickets.each do |ticket|
37
+ ticket.pass = self
38
+ ticket.cart_price = 0
39
+ ticket.save
40
+ end
41
+ FeeCalculator.apply(FeeStrategy.new).to(cart)
42
+ cart.save
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,19 @@
1
+ class PassType < ActiveRecord::Base
2
+ belongs_to :organization
3
+ has_many :passes
4
+
5
+ attr_accessible :name, :description, :hide_fee, :thanks_copy, :sales_start_at, :sales_end_at,
6
+ :on_sale, :price, :tickets_allowed, :starts_at, :ends_at
7
+
8
+ validates :name, :description, :price, :tickets_allowed, :presence => true
9
+
10
+ scope :storefront, where(:on_sale => true).where("sales_start_at < ? or sales_start_at is null", DateTime.now).where("sales_end_at > ? or sales_end_at is null", DateTime.now)
11
+ scope :on_sale, where(:on_sale => true)
12
+ scope :not_ended, where('ends_at > ?', DateTime.now)
13
+
14
+ SERVICE_FEE = 0.05
15
+
16
+ def passerize
17
+ self.name.end_with?("Pass") ? self.name : self.name + " Pass"
18
+ end
19
+ end
data/app/models/person.rb CHANGED
@@ -26,6 +26,7 @@ class Person < ActiveRecord::Base
26
26
  has_one :address, :validate => false
27
27
  has_one :member
28
28
  has_many :memberships, :through => :member
29
+ has_many :passes
29
30
 
30
31
  accepts_nested_attributes_for :address, :allow_destroy => false
31
32
  accepts_nested_attributes_for :phones, :reject_if => lambda { |p| p[:number].blank? }, :allow_destroy => true
@@ -282,10 +283,12 @@ class Person < ActiveRecord::Base
282
283
  end
283
284
 
284
285
  def self.first_or_create(attributes=nil, options ={}, &block)
286
+ Rails.logger.debug("Person.first_or_create #{attributes.inspect}")
285
287
  attributes[:organization_id] ||= attributes[:organization].try(:id)
286
288
  raise(ArgumentError, "You must include an organization when searching for people") if attributes[:organization_id].blank?
287
289
 
288
290
  attributes.delete(:organization)
291
+ return Person.where(:id => attributes[:id]).where(:organization_id => attributes[:organization_id]).first if attributes[:id].present?
289
292
  return Person.create(attributes, options, &block) if attributes[:email].blank?
290
293
 
291
294
  Person.where(:email => attributes[:email]).where(:organization_id => attributes[:organization_id]).first || Person.create(attributes, options, &block)
@@ -296,6 +299,7 @@ class Person < ActiveRecord::Base
296
299
  raise(ArgumentError, "You must include an organization when searching for people") if attributes[:organization_id].blank?
297
300
 
298
301
  attributes.delete(:organization)
302
+ return Person.where(:id => attributes[:id]).where(:organization_id => attributes[:organization_id]).first if attributes[:id].present?
299
303
  return Person.new(attributes, options, &block) if attributes[:email].blank?
300
304
 
301
305
  Person.where(:email => attributes[:email]).where(:organization_id => attributes[:organization_id]).first || Person.new(attributes, options, &block)
data/app/models/search.rb CHANGED
@@ -11,9 +11,10 @@ class Search < ActiveRecord::Base
11
11
  :min_lifetime_value, :max_lifetime_value,
12
12
  :min_donations_amount, :max_donations_amount,
13
13
  :min_donations_date, :max_donations_date, :discount_code,
14
- :membership_status, :passholder, :membership_type_id, :membership_type
14
+ :membership_status, :pass_type_id, :membership_type_id, :membership_type,
15
+ :show_date_start, :show_date_end
15
16
 
16
- attr_accessor :passholder
17
+ ANY_EVENT = -1
17
18
 
18
19
  def length
19
20
  people.length
@@ -31,6 +32,10 @@ class Search < ActiveRecord::Base
31
32
  Delayed::Job.enqueue(ActionJob.new(action, people))
32
33
  end
33
34
 
35
+ def event_name
36
+ event.try(:name) || Event::ANY_EVENT_TEXT
37
+ end
38
+
34
39
  def description
35
40
  conditions = []
36
41
 
@@ -38,10 +43,13 @@ class Search < ActiveRecord::Base
38
43
 
39
44
  if event_id.present?
40
45
  if has_purchased_for
41
- conditions << "Purchased tickets for #{event.name}."
46
+ conditions << "Purchased tickets for #{event_name}."
42
47
  else
43
- conditions << "Have not purchased tickets for #{event.name}."
48
+ conditions << "Have not purchased tickets for #{event_name}."
44
49
  end
50
+
51
+ conditions << "For show dates after #{show_date_start.strftime('%D')}" if show_date_start.present?
52
+ conditions << "For show dates through #{show_date_end.strftime('%D')}" if show_date_end.present?
45
53
  end
46
54
 
47
55
  if zip.present? || state.present?
@@ -118,87 +126,186 @@ class Search < ActiveRecord::Base
118
126
  end
119
127
  end
120
128
 
129
+ def offset_show_date_start
130
+ return nil if self.show_date_start.blank?
131
+ @offset_show_date_start ||= self.show_date_start.to_datetime.change(:offset => offset(self.show_date_start))
132
+ end
133
+
134
+ def offset_show_date_end
135
+ return nil if self.show_date_end.blank?
136
+ @offset_show_date_end ||= self.show_date_end.to_datetime.end_of_day.change(:offset => offset(self.show_date_end))
137
+ end
138
+
139
+ def offset(datetime)
140
+ @offset ||= datetime.in_time_zone(ActiveSupport::TimeZone.create(self.organization.time_zone)).formatted_offset
141
+ end
142
+
121
143
  private
122
144
 
123
- def find_people
124
- column_names = Person.column_names.collect {|cn| "people.#{cn}" }
125
- column_names << "lower(people.last_name) AS ordered_last_names"
145
+ def find_people
146
+ column_names = Person.column_names.collect {|cn| "people.#{cn}" }
147
+ column_names << "lower(people.last_name) AS ordered_last_names"
126
148
 
127
- people = Person.where(:organization_id => organization_id)
128
- people = people.where(:dummy => false)
129
- people = people.order('ordered_last_names ASC')
149
+ people = Person.where(:organization_id => organization_id)
150
+ people = people.where(:dummy => false)
151
+ people = people.order('ordered_last_names ASC')
130
152
 
131
- people = people.where("people.type" => person_type) unless person_type.blank?
132
- people = people.where("people.subtype" => person_subtype) unless person_type.blank? || person_subtype.blank?
153
+ people = people.where("people.type" => person_type) unless person_type.blank?
154
+ people = people.where("people.subtype" => person_subtype) unless person_type.blank? || person_subtype.blank?
133
155
 
134
- people = people.tagged_with(tagging) unless tagging.blank?
135
- people = people.joins(:address) unless zip.blank? && state.blank?
136
-
137
- if event_id.present?
138
- people = people.joins("LEFT JOIN `tickets` ON `tickets`.`buyer_id` = `people`.`id` ")
139
- .joins("LEFT JOIN `shows` ON `shows`.`id` = `tickets`.`show_id` ")
140
- .joins("LEFT JOIN `events` ON `events`.`id` = `shows`.`event_id`")
156
+ people = people.tagged_with(tagging) unless tagging.blank?
157
+ people = people.joins(:address) unless zip.blank? && state.blank?
141
158
 
142
- if has_purchased_for
143
- people = people.where("events.id" => event_id)
144
- else
145
- people = people.where("`events`.`id` <> ? or events.id is null", event_id)
159
+ people = add_event_query(people, column_names)
160
+
161
+ people = people.where("addresses.zip" => zip.to_s) unless zip.blank?
162
+ people = people.where("addresses.state" => state) unless state.blank?
163
+ people = people.where("people.lifetime_value >= ?", min_lifetime_value * 100.0) unless min_lifetime_value.blank?
164
+ people = people.where("people.lifetime_value <= ?", max_lifetime_value * 100.0) unless max_lifetime_value.blank?
165
+
166
+ unless discount_code.blank?
167
+ people = people.joins(:orders => [:items => [:discount]])
168
+ people = (discount_code == Discount::ALL_DISCOUNTS_STRING) ? people.where("items.discount_id is not null") : people.where("discounts.code = ?", discount_code)
146
169
  end
147
- end
148
-
149
170
 
150
- people = people.where("addresses.zip" => zip.to_s) unless zip.blank?
151
- people = people.where("addresses.state" => state) unless state.blank?
152
- people = people.where("people.lifetime_value >= ?", min_lifetime_value * 100.0) unless min_lifetime_value.blank?
153
- people = people.where("people.lifetime_value <= ?", max_lifetime_value * 100.0) unless max_lifetime_value.blank?
171
+ unless [min_donations_amount, max_donations_amount, min_donations_date, max_donations_date].all?(&:blank?)
172
+ people = people.joins(:orders => :items)
173
+ people = people.where("orders.created_at >= ?", min_donations_date) unless min_donations_date.blank?
174
+ people = people.where("orders.created_at <= ?", max_donations_date + 1.day) unless max_donations_date.blank?
175
+ people = people.where("items.product_type = 'Donation'")
176
+ people = people.group("people.id")
177
+ if min_donations_amount.blank?
178
+ people = people.having("SUM(items.price + items.nongift_amount) >= 1")
179
+ else
180
+ people = people.having("SUM(items.price + items.nongift_amount) >= ?", min_donations_amount * 100.0)
181
+ end
182
+ people = people.having("SUM(items.price + items.nongift_amount) <= ?", max_donations_amount * 100.0) unless max_donations_amount.blank?
183
+ end
154
184
 
155
- unless discount_code.blank?
156
- people = people.joins(:orders => [:items => [:discount]])
157
- people = (discount_code == Discount::ALL_DISCOUNTS_STRING) ? people.where("items.discount_id is not null") : people.where("discounts.code = ?", discount_code)
185
+ ### MEMBERSHIP ##
186
+
187
+ if searching_membership?
188
+ #Necessary because we need a left join for the "Not" condition to work
189
+ people = people.joins('left join members on members.person_id = people.id')
190
+ end
191
+
192
+ if membership_status.present?
193
+ people = people.merge(Member.current) if membership_status == "Current"
194
+ people = people.merge(Member.lapsed) if membership_status == "Lapsed"
195
+ people = people.merge(Member.past) if membership_status == "Past"
196
+ people = people.where("members.id is null") if membership_status == "None"
197
+ end
198
+
199
+ if membership_type_id.present?
200
+ people = people.joins('inner join memberships on members.id = memberships.member_id')
201
+ people = people.joins('inner join membership_types on membership_types.id = memberships.membership_type_id')
202
+
203
+ people = people.where('membership_types.id = ?', membership_type_id)
204
+ end
205
+
206
+ people.select(column_names).group("people.id")
158
207
  end
159
208
 
160
- unless [min_donations_amount, max_donations_amount, min_donations_date, max_donations_date].all?(&:blank?)
161
- people = people.joins(:orders => :items)
162
- people = people.where("orders.created_at >= ?", min_donations_date) unless min_donations_date.blank?
163
- people = people.where("orders.created_at <= ?", max_donations_date + 1.day) unless max_donations_date.blank?
164
- people = people.where("items.product_type = 'Donation'")
165
- people = people.group("people.id")
166
- if min_donations_amount.blank?
167
- people = people.having("SUM(items.price + items.nongift_amount) >= 1")
168
- else
169
- people = people.having("SUM(items.price + items.nongift_amount) >= ?", min_donations_amount * 100.0)
209
+ def add_event_query(people, column_names)
210
+ if any_event?
211
+ people = add_any_event_query(people, column_names)
212
+ elsif specific_event?
213
+ people = add_specific_event_query(people, column_names)
170
214
  end
171
- people = people.having("SUM(items.price + items.nongift_amount) <= ?", max_donations_amount * 100.0) unless max_donations_amount.blank?
215
+
216
+ people
172
217
  end
173
218
 
174
- ### MEMBERSHIP ##
219
+ def add_specific_event_query(people, column_names)
220
+ if has_purchased_for
221
+ people = people.joins("LEFT JOIN `tickets` ON `tickets`.`buyer_id` = `people`.`id` ")
222
+ .joins("LEFT JOIN `shows` ON `shows`.`id` = `tickets`.`show_id` ")
223
+ .joins("LEFT JOIN `events` ON `events`.`id` = `shows`.`event_id`")
224
+
225
+ people = people.where("events.id" => event_id)
175
226
 
176
- if searching_membership?
177
- #Necessary because we need a left join for the "Not" condition to work
178
- people = people.joins('left join members on members.person_id = people.id')
179
- end
227
+ if show_date_search?
228
+ people = people.where("shows.datetime >= ?", offset_show_date_start) unless show_date_start.blank?
229
+ people = people.where("shows.datetime <= ?", offset_show_date_end) unless show_date_end.blank?
230
+ end
180
231
 
181
- if membership_status.present?
182
- people = people.merge(Member.current) if membership_status == "Current"
183
- people = people.merge(Member.lapsed) if membership_status == "Lapsed"
184
- people = people.merge(Member.past) if membership_status == "Past"
185
- people = people.where("members.id is null") if membership_status == "None"
232
+ elsif !has_purchased_for
233
+ if show_date_search?
234
+ people_subquery = Ticket.joins(:show)
235
+ .where("shows.event_id = ?", event_id)
236
+ .where("tickets.buyer_id = people.id")
237
+
238
+ people_subquery = people_subquery.where("shows.datetime >= ?", offset_show_date_start) unless show_date_start.blank?
239
+ people_subquery = people_subquery.where("shows.datetime <= ?", offset_show_date_end) unless show_date_end.blank?
240
+
241
+ people = people.where("NOT EXISTS (#{people_subquery.to_sql})")
242
+ else
243
+ #
244
+ # Had to use a correlated subquery here. Sorry.
245
+ # ActiveRecord 3.2 does not support NOT IN, so we have to do some manual work here.
246
+ # AR 4.0 has NOT IN
247
+ #
248
+ people_subquery = Person.select("people.id")
249
+ .joins("LEFT JOIN `tickets` ON `tickets`.`buyer_id` = `people`.`id` ")
250
+ .joins("LEFT JOIN `shows` ON `shows`.`id` = `tickets`.`show_id` ")
251
+ .joins("LEFT JOIN `events` ON `events`.`id` = `shows`.`event_id`")
252
+ .where("events.id" => event_id)
253
+ people = people.where("people.id not in (#{people_subquery.to_sql})")
254
+ end
255
+
256
+ people
257
+ end
258
+
259
+ people
186
260
  end
187
261
 
188
- if membership_type_id.present?
189
- people = people.joins('inner join memberships on members.id = memberships.member_id')
190
- people = people.joins('inner join membership_types on membership_types.id = memberships.membership_type_id')
262
+ def add_any_event_query(people, column_names)
263
+ people = people.joins("LEFT JOIN `tickets` ON `tickets`.`buyer_id` = `people`.`id` ")
264
+
265
+ if has_purchased_for
266
+ column_names << "count(tickets.id) as ticket_count"
267
+ if show_date_search?
268
+ people = people.joins("LEFT JOIN `shows` ON `shows`.`id` = `tickets`.`show_id` ")
269
+ people = people.where("shows.datetime >= ?", offset_show_date_start) unless show_date_start.blank?
270
+ people = people.where("shows.datetime <= ?", offset_show_date_end) unless show_date_end.blank?
271
+ end
272
+ people = people.having("ticket_count > 0")
273
+ elsif !has_purchased_for
274
+ if show_date_search?
275
+ people_subquery = Ticket.joins(:show)
276
+ .where("tickets.buyer_id = people.id")
277
+
278
+ people_subquery = people_subquery.where("shows.datetime >= ?", offset_show_date_start) unless show_date_start.blank?
279
+ people_subquery = people_subquery.where("shows.datetime <= ?", offset_show_date_end) unless show_date_end.blank?
280
+
281
+ people = people.where("NOT EXISTS (#{people_subquery.to_sql})")
282
+ else
283
+ column_names << "count(tickets.id) as ticket_count"
284
+ people = people.having("ticket_count = 0")
285
+ end
286
+ end
191
287
 
192
- people = people.where('membership_types.id = ?', membership_type_id)
288
+ people
193
289
  end
194
290
 
195
- people.select(column_names).uniq
196
- end
291
+ def searching_for_event?
292
+ event_id.present?
293
+ end
197
294
 
295
+ def show_date_search?
296
+ show_date_start.present? || show_date_end.present?
297
+ end
198
298
 
299
+ def any_event?
300
+ self.event_id == ANY_EVENT
301
+ end
199
302
 
200
- def searching_membership?
201
- membership_status.present? ||
202
- membership_type_id.present?
203
- end
303
+ def specific_event?
304
+ self.event_id.present? && self.event_id > ANY_EVENT
305
+ end
306
+
307
+ def searching_membership?
308
+ membership_status.present? ||
309
+ membership_type_id.present?
310
+ end
204
311
  end