effective_events 0.11.3 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (26) hide show
  1. checksums.yaml +4 -4
  2. data/app/datatables/admin/effective_event_registrants_datatable.rb +6 -3
  3. data/app/datatables/admin/effective_event_tickets_datatable.rb +11 -2
  4. data/app/datatables/admin/effective_events_datatable.rb +2 -0
  5. data/app/datatables/effective_event_registrants_datatable.rb +17 -3
  6. data/app/helpers/effective_events_helper.rb +34 -1
  7. data/app/models/concerns/effective_events_event_registration.rb +52 -10
  8. data/app/models/effective/event.rb +9 -7
  9. data/app/models/effective/event_registrant.rb +84 -18
  10. data/app/models/effective/event_ticket.rb +43 -13
  11. data/app/views/admin/event_tickets/_form.html.haml +44 -8
  12. data/app/views/admin/events/_form_event.html.haml +19 -13
  13. data/app/views/effective/event_registrants/_fields.html.haml +23 -8
  14. data/app/views/effective/event_registrants/_fields_member_only.html.haml +6 -0
  15. data/app/views/effective/event_registrants/_fields_member_or_non_member.html.haml +20 -0
  16. data/app/views/effective/event_registrants/_fields_questions.html.haml +11 -0
  17. data/app/views/effective/event_registrants/_fields_regular.html.haml +8 -0
  18. data/app/views/effective/event_registrations/_orders.html.haml +1 -1
  19. data/app/views/effective/event_registrations/checkout.html.haml +2 -2
  20. data/app/views/effective/event_registrations/complete.html.haml +18 -0
  21. data/app/views/effective/event_registrations/submitted.html.haml +2 -5
  22. data/app/views/effective/events/show.html.haml +1 -1
  23. data/db/migrate/101_create_effective_events.rb +19 -0
  24. data/db/seeds.rb +3 -0
  25. data/lib/effective_events/version.rb +1 -1
  26. metadata +7 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 12812ed11c2f839cd66a8efd68bd2fe954285ef3ada26fa169082d2c97279359
4
- data.tar.gz: ce5e83f092b9373ea720cb03e7524aecae7093a9561a831c68f2088cb7a4a332
3
+ metadata.gz: 5ea3b86b0a1ca6c11ee6a2a9ee5fba8dec34473c435299bed967cf129912b0dc
4
+ data.tar.gz: 1164dce2a11f7a65e95c113d2ef9f3f0c254da666c206dbba67b7729e441953f
5
5
  SHA512:
6
- metadata.gz: 3840af3be1d4ded9d04dccbf66fbee23f196aa251dfcc68d76f6cedd5534f2dfea6e19b8d4da1e3c00e4eae94633e4a2ca09a8ccc1ed4b459e3cbf607b9ebf8a
7
- data.tar.gz: d8ab4acd7c5bf18b5a17bc2098bcaca247457ea2705cff91b9da1a2b5f3257f1c392d1d57db6ea1b0436301ec2c2a15811a3004c1953fd5d13d63429bfe29c42
6
+ metadata.gz: f9c4899dfc735f56d2b34889f8905b4315dcbf59ab9a9967154ac1f8eb9a0d210982cf21d551a7d0076eeac4ec4ca6ddbaf5d1ba384257e542a5b6d6d32d22e3
7
+ data.tar.gz: ec2ed614931b908976b4abb959e9f69d4c16de58b8cafc1cc22223d1a2061fb847500e254a391dba162b5a6065b1faf00065a1be569789c8a344be6bbcaf9c29
@@ -26,12 +26,15 @@ module Admin
26
26
 
27
27
  col :purchased_order, visible: false
28
28
 
29
+ col :user
29
30
  col :first_name
30
31
  col :last_name
31
32
  col :email
32
- col :company
33
- col :number, label: 'Designations'
34
- col :notes, label: 'Restrictions and notes'
33
+ col :company, visible: false
34
+
35
+ col :response1
36
+ col :response2
37
+ col :response3
35
38
 
36
39
  actions_col
37
40
  end
@@ -16,8 +16,11 @@ module Admin
16
16
  col :event
17
17
 
18
18
  col :title
19
+ col :category, visible: false
20
+
21
+ col :early_bird_price, as: :price, visible: event.early_bird_end_at.present?
19
22
  col :regular_price, as: :price
20
- col :early_bird_price, as: :price
23
+ col :member_price, as: :price
21
24
 
22
25
  col :capacity_to_s, label: 'Capacity' do |ticket|
23
26
  if ticket.capacity.present?
@@ -25,6 +28,8 @@ module Admin
25
28
  end
26
29
  end
27
30
 
31
+ col :category, visible: false
32
+
28
33
  col :registered_event_registrants_count, label: 'Registered' do |event|
29
34
  event.event_registrants.registered.unarchived.count
30
35
  end
@@ -33,8 +38,12 @@ module Admin
33
38
  event.event_registrants.purchased.unarchived.count
34
39
  end
35
40
 
36
- col :capacity, visible: false
37
41
  col :capacity_available, visible: false
42
+ col :capacity, label: 'Capacity Total', visible: false
43
+
44
+ col :question1, visible: false
45
+ col :question2, visible: false
46
+ col :question3, visible: false
38
47
 
39
48
  actions_col
40
49
  end
@@ -42,6 +42,8 @@ module Admin
42
42
  # col :event_registrants, search: :string
43
43
  # col :event_addons, search: :string
44
44
 
45
+ col :allow_blank_registrants
46
+
45
47
  col :roles, visible: false
46
48
  col :authenticate_user, visible: false
47
49
 
@@ -4,7 +4,13 @@ class EffectiveEventRegistrantsDatatable < Effective::Datatable
4
4
  datatable do
5
5
 
6
6
  col :name do |er|
7
- "#{er.first_name} #{er.last_name}<br><small>#{mail_to(er.email)}</small>"
7
+ if er.first_name.present?
8
+ "#{er.first_name} #{er.last_name}<br><small>#{mail_to(er.email)}</small>"
9
+ elsif er.owner.present?
10
+ er.owner.to_s + ' - GUEST'
11
+ else
12
+ 'Unknown'
13
+ end
8
14
  end
9
15
 
10
16
  col :event_ticket, search: :string, label: 'Ticket' do |er|
@@ -14,13 +20,21 @@ class EffectiveEventRegistrantsDatatable < Effective::Datatable
14
20
  ].compact.join('<br>').html_safe
15
21
  end
16
22
 
23
+ col :user, visible: false
17
24
  col :first_name, visible: false
18
25
  col :last_name, visible: false
19
26
  col :email, visible: false
20
27
  col :company, visible: false
21
- col :number, visible: false, label: 'Designations'
22
28
 
23
- col :notes
29
+ col :response1, visible: false
30
+ col :response2, visible: false
31
+ col :response3, visible: false
32
+
33
+ col :details do |registrant|
34
+ [registrant.response1.presence, registrant.response2.presence, registrant.response3.presence].compact.map do |response|
35
+ content_tag(:div, response)
36
+ end.join.html_safe
37
+ end
24
38
 
25
39
  col :price, as: :price
26
40
 
@@ -7,16 +7,37 @@ module EffectiveEventsHelper
7
7
  end
8
8
  end
9
9
 
10
+ def effective_events_ticket_price(event, ticket)
11
+ raise('expected an Effective::Event') unless event.kind_of?(Effective::Event)
12
+ raise('expected an Effective::EventTicket') unless ticket.kind_of?(Effective::EventTicket)
13
+
14
+ prices = [
15
+ (ticket.early_bird_price if event.early_bird?),
16
+ (ticket.regular_price if ticket.regular? || ticket.member_or_non_member?),
17
+ (ticket.member_price if ticket.member_only? || ticket.member_or_non_member?)
18
+ ].compact.sort.uniq
19
+
20
+ if prices.length > 1
21
+ "#{(prices.first == 0 ? '$0' : price_to_currency(prices.first))} to #{(prices.last == 0 ? '$0' : price_to_currency(prices.last))}"
22
+ else
23
+ (prices.first == 0 ? '$0' : price_to_currency(prices.first))
24
+ end
25
+ end
26
+
10
27
  def effective_events_event_tickets_collection(event, namespace = nil)
11
28
  raise('expected an Effective::Event') unless event.kind_of?(Effective::Event)
12
29
 
13
30
  # Allow an admin to assign archived tickets
14
31
  authorized = (namespace == :admin)
32
+ member = current_user.try(:is_any?, :member)
33
+
15
34
  tickets = (authorized ? event.event_tickets : event.event_tickets.reject(&:archived?))
35
+ tickets = tickets.reject { |ticket| ticket.member_only? } unless (authorized || member)
16
36
 
17
37
  tickets.map do |ticket|
18
38
  title = ticket.to_s
19
- price = (ticket.price == 0 ? '$0' : price_to_currency(ticket.price))
39
+ price = effective_events_ticket_price(event, ticket)
40
+
20
41
  remaining = (ticket.capacity.present? ? "#{ticket.capacity_available} remaining" : nil)
21
42
 
22
43
  label = [title, price, remaining].compact.join(' - ')
@@ -44,4 +65,16 @@ module EffectiveEventsHelper
44
65
  [label, product.to_param, disabled].compact
45
66
  end
46
67
  end
68
+
69
+ def effective_events_event_tickets_user_hint
70
+ url = if current_user.class.try(:effective_memberships_organization_user?)
71
+ organization = current_user.membership_organizations.first || current_user.organizations.first
72
+ effective_memberships.edit_organization_path(organization, anchor: 'tab-representatives') if organization
73
+ end
74
+
75
+ return if url.blank?
76
+
77
+ "Can't find the person you need? <a href='#{url}' target='blank'>Click here</a> to add them to your organization."
78
+ end
79
+
47
80
  end
@@ -23,7 +23,8 @@ module EffectiveEventsEventRegistration
23
23
 
24
24
  acts_as_statused(
25
25
  :draft, # Just Started
26
- :submitted # All done
26
+ :submitted, # Order has been submitted with a deferred payment processor.
27
+ :completed # All done. Order purchased.
27
28
  )
28
29
 
29
30
  acts_as_wizard(
@@ -33,7 +34,8 @@ module EffectiveEventsEventRegistration
33
34
  summary: 'Review',
34
35
  billing: 'Billing Address',
35
36
  checkout: 'Checkout',
36
- submitted: 'Submitted'
37
+ submitted: 'Submitted',
38
+ complete: 'Completed'
37
39
  )
38
40
 
39
41
  acts_as_purchasable_wizard
@@ -70,6 +72,7 @@ module EffectiveEventsEventRegistration
70
72
 
71
73
  # Dates
72
74
  submitted_at :datetime
75
+ completed_at :datetime
73
76
 
74
77
  # Acts as Wizard
75
78
  wizard_steps :text, permitted: false
@@ -85,9 +88,9 @@ module EffectiveEventsEventRegistration
85
88
  }
86
89
 
87
90
  scope :sorted, -> { order(:id) }
88
-
89
- scope :in_progress, -> { where.not(status: [:submitted]) }
90
- scope :done, -> { where(status: [:submitted]) }
91
+
92
+ scope :in_progress, -> { where(status: [:draft, :submitted]) }
93
+ scope :done, -> { where(status: :completed) }
91
94
 
92
95
  scope :for, -> (user) { where(owner: user) }
93
96
 
@@ -119,6 +122,16 @@ module EffectiveEventsEventRegistration
119
122
  end
120
123
  end
121
124
 
125
+ # If we're submitted. Try to move to completed.
126
+ before_save(if: -> { submitted? }) { try_completed! }
127
+
128
+ def can_visit_step?(step)
129
+ return false if step == :complete && !completed?
130
+ return true if step == :complete && completed?
131
+
132
+ can_revisit_completed_steps(step)
133
+ end
134
+
122
135
  def required_steps
123
136
  return self.class.test_required_steps if Rails.env.test? && self.class.test_required_steps.present?
124
137
  event&.event_products.present? ? wizard_step_keys : (wizard_step_keys - [:addons])
@@ -137,6 +150,14 @@ module EffectiveEventsEventRegistration
137
150
  end
138
151
  end
139
152
 
153
+ # When the submit_order is deferred or purchased, we call submit!
154
+ # When the order is a deferred payment processor, we continue to the :submitted step
155
+ # When the order is a regular processor, the before_save will call complete! and we continue to the :complete step
156
+ # Purchasing the order later on will automatically call
157
+ def submit_wizard_on_deferred_order?
158
+ true
159
+ end
160
+
140
161
  def after_submit_purchased!
141
162
  notifications = event.event_notifications.select(&:registrant_purchased?)
142
163
  notifications.each { |notification| notification.notify!(event_registrants: event_registrants) }
@@ -149,11 +170,29 @@ module EffectiveEventsEventRegistration
149
170
  end
150
171
 
151
172
  def in_progress?
152
- draft?
173
+ draft? || submitted?
153
174
  end
154
175
 
155
176
  def done?
156
- submitted?
177
+ completed?
178
+ end
179
+
180
+ def try_completed!
181
+ return false unless submitted?
182
+ return false unless submit_order&.purchased?
183
+ complete!
184
+ end
185
+
186
+ def complete!
187
+ raise('event registration must be submitted to complete!') unless submitted?
188
+ raise('expected a purchased order') unless submit_order&.purchased?
189
+
190
+ wizard_steps[:checkout] ||= Time.zone.now
191
+ wizard_steps[:submitted] ||= Time.zone.now
192
+ wizard_steps[:complete] = Time.zone.now
193
+
194
+ completed!
195
+ true
157
196
  end
158
197
 
159
198
  # Find or build
@@ -178,7 +217,6 @@ module EffectiveEventsEventRegistration
178
217
  last_name: owner.try(:last_name),
179
218
  email: owner.try(:email),
180
219
  company: owner.try(:company),
181
- number: owner.try(:membership).try(:number) || owner.try(:number)
182
220
  )
183
221
  end
184
222
 
@@ -203,7 +241,9 @@ module EffectiveEventsEventRegistration
203
241
  def unavailable_event_tickets
204
242
  unavailable = []
205
243
 
206
- present_event_registrants.map(&:event_ticket).group_by { |t| t }.each do |event_ticket, event_tickets|
244
+ changed_event_registrants = present_event_registrants.select { |er| er.new_record? || er.event_ticket_id_changed? }
245
+
246
+ changed_event_registrants.map(&:event_ticket).group_by { |t| t }.each do |event_ticket, event_tickets|
207
247
  unavailable << event_ticket unless event.event_ticket_available?(event_ticket, quantity: event_tickets.length)
208
248
  end
209
249
 
@@ -213,7 +253,9 @@ module EffectiveEventsEventRegistration
213
253
  def unavailable_event_products
214
254
  unavailable = []
215
255
 
216
- present_event_addons.map(&:event_product).group_by { |p| p }.each do |event_product, event_products|
256
+ changed_event_products = present_event_addons.select { |er| er.new_record? || er.event_product_id_changed? }
257
+
258
+ changed_event_products.map(&:event_product).group_by { |p| p }.each do |event_product, event_products|
217
259
  unavailable << event_product unless event.event_product_available?(event_product, quantity: event_products.length)
218
260
  end
219
261
 
@@ -66,6 +66,8 @@ module Effective
66
66
  external_registration :boolean
67
67
  external_registration_url :string
68
68
 
69
+ allow_blank_registrants :boolean
70
+
69
71
  # Access
70
72
  roles_mask :integer
71
73
  authenticate_user :boolean
@@ -82,7 +84,7 @@ module Effective
82
84
  }
83
85
 
84
86
  scope :published, -> { where(draft: false).where(arel_table[:published_at].lt(Time.zone.now)) }
85
- scope :unpublished, -> { where(draft: true).where(arel_table[:published_at].gteq(Time.zone.now)) }
87
+ scope :unpublished, -> { where(draft: true).or(where(arel_table[:published_at].gteq(Time.zone.now))) }
86
88
 
87
89
  scope :upcoming, -> { where(arel_table[:end_at].gt(Time.zone.now)) }
88
90
  scope :past, -> { where(arel_table[:end_at].lteq(Time.zone.now)) }
@@ -134,27 +136,27 @@ module Effective
134
136
  validates :registration_end_at, presence: true, unless: -> { external_registration? }
135
137
 
136
138
  validate(if: -> { start_at && end_at }) do
137
- self.errors.add(:end_at, 'must be after start date') unless start_at < end_at
139
+ errors.add(:end_at, 'must be after start date') unless start_at < end_at
138
140
  end
139
141
 
140
142
  validate(if: -> { start_at && registration_start_at }) do
141
- self.errors.add(:registration_start_at, 'must be before start date') unless registration_start_at < start_at
143
+ errors.add(:registration_start_at, 'must be before start date') unless registration_start_at < start_at
142
144
  end
143
145
 
144
146
  validate(if: -> { registration_start_at && registration_end_at }) do
145
- self.errors.add(:registration_end_at, 'must be after start registration date') unless registration_start_at < registration_end_at
147
+ errors.add(:registration_end_at, 'must be after start registration date') unless registration_start_at < registration_end_at
146
148
  end
147
149
 
148
150
  validate(if: -> { start_at && early_bird_end_at }) do
149
- self.errors.add(:early_bird_end_at, 'must be before start date') unless early_bird_end_at < start_at
151
+ errors.add(:early_bird_end_at, 'must be before start date') unless early_bird_end_at < start_at
150
152
  end
151
153
 
152
154
  validate(if: -> { file.attached? }) do
153
- self.errors.add(:file, 'must be an image') unless file.image?
155
+ errors.add(:file, 'must be an image') unless file.image?
154
156
  end
155
157
 
156
158
  validate(if: -> { category.present? }) do
157
- self.errors.add(:category, 'is not included in the list') unless EffectiveEvents.categories.include?(category)
159
+ errors.add(:category, 'is not included in the list') unless EffectiveEvents.categories.include?(category)
158
160
  end
159
161
 
160
162
  def to_s
@@ -20,21 +20,33 @@ module Effective
20
20
  # This fee when checked out through the event registration
21
21
  belongs_to :event_registration, polymorphic: true, optional: true
22
22
 
23
+ # Required for member-only tickets. The user attending.
24
+ belongs_to :user, polymorphic: true, optional: true
25
+
23
26
  effective_resource do
24
- first_name :string
25
- last_name :string
26
- email :string
27
+ first_name :string
28
+ last_name :string
29
+ email :string
30
+ company :string
31
+
32
+ blank_registrant :boolean
33
+ member_registrant :boolean
27
34
 
28
- company :string
29
- number :string
30
- notes :text
35
+ # Question Responses
36
+ question1 :text
37
+ question2 :text
38
+ question3 :text
31
39
 
32
- archived :boolean
40
+ archived :boolean
33
41
 
34
42
  # Acts as Purchasable
35
- price :integer
36
- qb_item_name :string
37
- tax_exempt :boolean
43
+ price :integer
44
+ qb_item_name :string
45
+ tax_exempt :boolean
46
+
47
+ # Historical. Not used anymore.
48
+ number :string
49
+ notes :text
38
50
 
39
51
  timestamps
40
52
  end
@@ -43,17 +55,44 @@ module Effective
43
55
  scope :deep, -> { includes(:event, :event_ticket) }
44
56
  scope :registered, -> { purchased_or_deferred.unarchived }
45
57
 
46
- validates :first_name, presence: true
47
- validates :last_name, presence: true
48
- validates :email, presence: true, email: true
49
-
50
58
  before_validation(if: -> { event_registration.present? }) do
51
59
  self.event ||= event_registration.event
52
60
  self.owner ||= event_registration.owner
53
61
  end
54
62
 
55
- before_validation(if: -> { event_ticket.present? }) do
56
- assign_attributes(price: event_ticket.price) unless purchased?
63
+ before_validation(if: -> { blank_registrant? }) do
64
+ assign_attributes(user: nil, first_name: nil, last_name: nil, email: nil)
65
+ end
66
+
67
+ before_validation(if: -> { user.present? }) do
68
+ assign_attributes(first_name: user.first_name, last_name: user.last_name, email: user.email)
69
+ end
70
+
71
+ before_validation(if: -> { event_ticket.present? }, unless: -> { purchased? }) do
72
+ assign_price()
73
+ end
74
+
75
+ validates :user_id, uniqueness: { scope: [:event_id], allow_blank: true, message: 'is already registered for this event' }
76
+ validates :price, presence: true, numericality: { greater_than_or_equal_to: 0 }
77
+ validates :email, email: true
78
+
79
+ # Member Only Ticket
80
+ with_options(if: -> { event_ticket&.member_only? }, unless: -> { blank_registrant? }) do
81
+ validates :user_id, presence: { message: 'Please select a member' }
82
+ end
83
+
84
+ # Regular Ticket
85
+ with_options(if: -> { event_ticket&.regular? }, unless: -> { blank_registrant? }) do
86
+ validates :first_name, presence: true
87
+ validates :last_name, presence: true
88
+ validates :email, presence: true
89
+ end
90
+
91
+ with_options(if: -> { event_ticket&.member_or_non_member? && !blank_registrant? }) do
92
+ validates :user_id, presence: { message: 'Please select a member' }, unless: -> { first_name.present? || last_name.present? || email.present? }
93
+ validates :first_name, presence: true, unless: -> { user.present? }
94
+ validates :last_name, presence: true, unless: -> { user.present? }
95
+ validates :email, presence: true, unless: -> { user.present? }
57
96
  end
58
97
 
59
98
  def to_s
@@ -65,11 +104,15 @@ module Effective
65
104
  end
66
105
 
67
106
  def name
68
- "#{first_name} #{last_name}"
107
+ first_name.present? ? "#{first_name} #{last_name}" : "GUEST"
69
108
  end
70
109
 
71
110
  def last_first_name
72
- "#{last_name}, #{first_name}"
111
+ first_name.present? ? "#{last_name}, #{first_name}" : "GUEST"
112
+ end
113
+
114
+ def member_present?
115
+ user&.is?(:member) || (blank_registrant? && member_registrant?)
73
116
  end
74
117
 
75
118
  def tax_exempt
@@ -92,5 +135,28 @@ module Effective
92
135
  true
93
136
  end
94
137
 
138
+ private
139
+
140
+ def assign_price
141
+ raise('is already purchased') if purchased?
142
+
143
+ raise('expected an event') if event.blank?
144
+ raise('expected an event ticket') if event_ticket.blank?
145
+
146
+ price = if event.early_bird?
147
+ event_ticket.early_bird_price # Early Bird Pricing
148
+ elsif event_ticket.regular?
149
+ event_ticket.regular_price
150
+ elsif event_ticket.member_only?
151
+ event_ticket.member_price
152
+ elsif event_ticket.member_or_non_member?
153
+ (member_present? ? event_ticket.member_price : event_ticket.regular_price)
154
+ else
155
+ raise("Unexpected event ticket price calculation")
156
+ end
157
+
158
+ assign_attributes(price: price)
159
+ end
160
+
95
161
  end
96
162
  end
@@ -16,19 +16,29 @@ module Effective
16
16
 
17
17
  has_rich_text :body
18
18
 
19
+ CATEGORIES = ['Regular', 'Member Only', 'Member or Non-Member']
20
+
19
21
  effective_resource do
20
- title :string
21
- capacity :integer
22
+ title :string
23
+ capacity :integer
24
+
25
+ category :string
26
+
27
+ # Questions
28
+ question1 :text
29
+ question2 :text
30
+ question3 :text
22
31
 
23
32
  # Pricing
24
- regular_price :integer
25
- early_bird_price :integer
33
+ early_bird_price :integer
34
+ member_price :integer
35
+ regular_price :integer
26
36
 
27
- qb_item_name :string
28
- tax_exempt :boolean
37
+ qb_item_name :string
38
+ tax_exempt :boolean
29
39
 
30
- position :integer
31
- archived :boolean
40
+ position :integer
41
+ archived :boolean
32
42
 
33
43
  timestamps
34
44
  end
@@ -37,21 +47,26 @@ module Effective
37
47
  scope :deep, -> { with_rich_text_body.includes(:event, :purchased_event_registrants) }
38
48
 
39
49
  before_validation(if: -> { event.present? }) do
50
+ self.category ||= CATEGORIES.first
40
51
  self.position ||= (event.event_tickets.map(&:position).compact.max || -1) + 1
41
52
  end
42
53
 
43
54
  validates :title, presence: true, uniqueness: { scope: [:event_id] }
44
- validates :regular_price, presence: true
55
+ validates :category, presence: true
56
+
57
+ validates :regular_price, presence: true, if: -> { member_or_non_member? || regular? }
58
+ validates :regular_price, numericality: { greater_than_or_equal_to: 0, allow_blank: true }
59
+
60
+ validates :member_price, presence: true, if: -> { member_or_non_member? || member_only? }
61
+ validates :member_price, numericality: { greater_than_or_equal_to: 0, allow_blank: true }
62
+
45
63
  validates :early_bird_price, presence: true, if: -> { event&.early_bird_end_at.present? }
64
+ validates :early_bird_price, numericality: { greater_than_or_equal_to: 0, allow_blank: true }
46
65
 
47
66
  def to_s
48
67
  title.presence || 'New Event Ticket'
49
68
  end
50
69
 
51
- def price
52
- event.early_bird? ? early_bird_price : regular_price
53
- end
54
-
55
70
  def capacity_available
56
71
  return nil if capacity.blank?
57
72
  [(capacity - registered_event_registrants_count), 0].max
@@ -65,5 +80,20 @@ module Effective
65
80
  purchased_event_registrants.length
66
81
  end
67
82
 
83
+ def questions
84
+ [question1.presence, question2.presence, question3.presence].compact
85
+ end
86
+
87
+ def regular?
88
+ category == 'Regular'
89
+ end
90
+
91
+ def member_only?
92
+ category == 'Member Only'
93
+ end
94
+
95
+ def member_or_non_member?
96
+ category == 'Member or Non-Member'
97
+ end
68
98
  end
69
99
  end
@@ -1,16 +1,52 @@
1
1
  = effective_form_with(model: [:admin, event_ticket], engine: true) do |f|
2
2
  = f.hidden_field :event_id
3
3
 
4
- = f.text_field :title
5
- = f.number_field :capacity, hint: 'The number of registrations will be limited to capacity. Leave blank for unlimited capacity.'
4
+ = card(et(Effective::EventTicket)) do
5
+ = f.text_field :title
6
6
 
7
- = f.price_field :regular_price, hint: 'A price of $0 will allow a checkout for free.'
8
- = f.price_field :early_bird_price, hint: 'A price of $0 will allow a checkout for free.'
9
- = f.check_box :tax_exempt
7
+ .row
8
+ .col= f.select :category, Effective::EventTicket::CATEGORIES, hint: "Determines who can purchase this ticket at which price. Also determines if the dropdown of all members is displayed to the purchaser."
9
+ .col= f.number_field :capacity, hint: "The number of registrations will be limited to capacity.<br>Leave blank for unlimited capacity."
10
10
 
11
- - if defined?(EffectiveQbSync)
12
- = f.text_field :qb_item_name, label: 'QuickBooks item name'
11
+ = f.show_if(:category, 'Regular') do
12
+ .alert.alert-info.mb-4
13
+ %strong Regular Ticket:
14
+ Anyone will be able to purchase this ticket. They will be asked for a first name, last name and email. Will not display the dropdown list of all members. Only the regular price applies.
13
15
 
14
- = f.check_box :archived, label: 'Archive this ticket. It will be unavailable for purchase.'
16
+ = f.show_if(:category, 'Member Only') do
17
+ .alert.alert-info.mb-4
18
+ %strong Member Only Ticket:
19
+ Only members will be able to purchase this ticket. They must select a member from the dropdown list of all members. Only the member price applies.
20
+
21
+ = f.show_if(:category, 'Member or Non-Member') do
22
+ .alert.alert-info.mb-4
23
+ %strong Member or Non-Member Ticket:
24
+ Anyone will be able to purchase this ticket. They can select a member from the dropdown list of all members to receive the member pricing.
25
+ Or, they can enter a first name, last name and email to receive the regular pricing.
26
+
27
+ .row
28
+ .col-md-6
29
+ - if f.object.event&.early_bird_end_at.present?
30
+ = f.price_field :early_bird_price, hint: 'A price of $0 will allow a checkout for free. Leave blank for no early bird price.'
31
+
32
+ = f.show_if_any(:category, ['Regular', 'Member or Non-Member']) do
33
+ = f.price_field :regular_price, hint: 'A price of $0 will allow a checkout for free.'
34
+
35
+ = f.show_if_any(:category, ['Member Only', 'Member or Non-Member']) do
36
+ = f.price_field :member_price, hint: 'A price of $0 will allow a checkout for free when selecting a member.'
37
+
38
+ .row
39
+ - if defined?(EffectiveQbSync)
40
+ .col= f.text_field :qb_item_name, label: 'QuickBooks item name'
41
+ .col= f.check_box :tax_exempt
42
+
43
+ = f.check_box :archived, label: 'Archive this ticket. It will be unavailable for purchase.'
44
+
45
+ = card('Questions') do
46
+ %p Add upto 3 questions
47
+
48
+ = f.text_field :question1, label: 'Question 1'
49
+ = f.text_field :question2, label: 'Question 2'
50
+ = f.text_field :question3, label: 'Question 3'
15
51
 
16
52
  = effective_submit(f)
@@ -1,30 +1,36 @@
1
1
  = effective_form_with(model: [:admin, event], engine: true) do |f|
2
2
  = f.text_field :title, label: "Title"
3
3
 
4
- - categories = EffectiveEvents.categories
5
- - if categories.present?
6
- = f.select :category, categories, hint: 'optional category'
7
-
8
- - if event.class.respond_to?(:acts_as_tagged?)
9
- = render 'effective/tags/fields', f: f
10
-
11
- = f.datetime_field :published_at, label: 'Publish date', hint: 'When should this be displayed on the website.'
12
- = f.check_box :draft, hint: 'Save this event as a draft. It will not be accessible on the website.'
13
-
14
4
  - if f.object.persisted? || f.object.errors.include?(:slug)
15
5
  - current_url = (effective_events.event_url(f.object) rescue nil)
16
6
  = f.text_field :slug, hint: "The slug controls this event's internet address. Be careful, changing the slug will break links that other websites may have to the old address.<br>#{('This event is currently reachable via ' + link_to(current_url.gsub(f.object.slug, '<strong>' + f.object.slug + '</strong>').html_safe, current_url)) if current_url }".html_safe
17
7
 
18
- = f.datetime_field :start_at, label: "Event Start"
19
- = f.datetime_field :end_at, label: "Event End"
8
+ .row
9
+ .col
10
+ - categories = EffectiveEvents.categories
11
+ - if categories.present?
12
+ = f.select :category, categories, hint: 'optional category'
13
+ .col
14
+ - if event.class.respond_to?(:acts_as_tagged?)
15
+ = render 'effective/tags/fields', f: f
20
16
 
21
- %h2 Registration Opens
17
+ = f.check_box :draft, hint: 'Save this event as a draft. It will not be accessible on the website.'
18
+ .row
19
+ .col-md-6= f.datetime_field :published_at, label: 'Publish date', hint: 'When should this be displayed on the website.'
20
+
21
+ .row
22
+ .col-md-6= f.datetime_field :start_at, label: "Event Start"
23
+ .col-md-6= f.datetime_field :end_at, label: "Event End"
24
+
25
+ %h2 Registration
22
26
  %p The online registration and purchase for this event will be available between:
23
27
  = f.datetime_field :registration_start_at, label: "Registration Start"
24
28
  = f.datetime_field :registration_end_at, label: "Registration End"
25
29
  = f.datetime_field :early_bird_end_at, label: "Early Bird End",
26
30
  hint: 'Event tickets can be purchased for early bird pricing before this date. Afterwards regular pricing applies.'
27
31
 
32
+ = f.check_box :allow_blank_registrants, label: "Yes, allow blank ticket registrations", hint: "Allow event tickets to be purchased without adding a name. The purchaser may return to add names later."
33
+
28
34
  = f.check_box :external_registration, label: "Yes, the registration for this event is handled by another website"
29
35
 
30
36
  = f.show_if(:external_registration, true) do
@@ -8,13 +8,28 @@
8
8
  - if f.object.purchased? && namespace == :admin
9
9
  = f.select :event_ticket_id, effective_events_event_tickets_collection(event, namespace), label: 'Change Ticket', hint: 'Admin only. Change the purchased ticket. This will not create charges, alter the original order, or consider ticket capacity.'
10
10
 
11
- .row
12
- .col-lg= f.text_field :first_name
13
- .col-lg= f.text_field :last_name
14
- .col-lg= f.text_field :number, label: 'Professional Designations', hint: 'member or registrant number'
11
+ - event.event_tickets.each do |ticket|
12
+ - next if ticket.member_only? && !current_user&.is?(:member)
15
13
 
16
- .row
17
- .col-lg= f.email_field :email
18
- .col-lg= f.text_field :company
14
+ = f.show_if(:event_ticket_id, ticket.id) do
15
+ - if event.allow_blank_registrants? && (f.object.new_record? || f.object.blank_registrant?)
16
+ = f.check_box :blank_registrant, label: "I will return and add this ticket's information later"
17
+ - else
18
+ = f.hidden_field :blank_registrant, value: false
19
+
20
+ - if ticket.member_or_non_member?
21
+ = f.show_if(:blank_registrant, true, nested: true) do
22
+ = f.check_box :member_registrant, label: 'Yes, this ticket will be for a member'
23
+
24
+ = f.show_if(:blank_registrant, false, nested: true) do
25
+ - if ticket.regular?
26
+ = render('effective/event_registrants/fields_regular', f: f)
27
+ - elsif ticket.member_only?
28
+ = render('effective/event_registrants/fields_member_only', f: f)
29
+ - elsif ticket.member_or_non_member?
30
+ = render('effective/event_registrants/fields_member_or_non_member', f: f)
31
+ - else
32
+ - raise("Unexpected ticket category: #{ticket.category || 'nil'}")
33
+
34
+ = render('effective/event_registrants/fields_questions', f: f, ticket: ticket)
19
35
 
20
- = f.text_area :notes, label: 'Dietary restrictions or other notes'
@@ -0,0 +1,6 @@
1
+ - # Choose member
2
+ - klass = (f.object.user || current_user).class
3
+ - ajax_url = (@select2_users_ajax_path || effective_memberships.member_users_membership_select2_ajax_index_path) unless Rails.env.test?
4
+
5
+ = f.hidden_field :user_type, value: klass.name
6
+ = f.select :user_id, klass.all, ajax_url: ajax_url, label: 'Member', required: true, hint: effective_events_event_tickets_user_hint()
@@ -0,0 +1,20 @@
1
+ - # Choose member
2
+ - klass = (f.object.user || current_user).class
3
+ - ajax_url = (@select2_users_ajax_path || effective_memberships.member_users_membership_select2_ajax_index_path) unless Rails.env.test?
4
+
5
+ = f.hidden_field :user_type, value: klass.name
6
+ = f.select :user_id, klass.all, ajax_url: ajax_url, label: 'Member', hint: effective_events_event_tickets_user_hint()
7
+
8
+ = f.show_if(:user_id, '', nested: true) do
9
+ %p.text-center - or -
10
+
11
+ - # Choose non-member
12
+ .row
13
+ .col-md= f.text_field :first_name
14
+ .col-md= f.text_field :last_name
15
+
16
+ .row
17
+ .col-md= f.email_field :email
18
+ .col-md= f.text_field :company
19
+
20
+ %hr
@@ -0,0 +1,11 @@
1
+ - if ticket.questions.present?
2
+ %p Please answer the following questions:
3
+
4
+ - if ticket.question1.present?
5
+ = f.text_field :response1, label: ticket.question1
6
+
7
+ - if ticket.question2.present?
8
+ = f.text_field :response2, label: ticket.question2
9
+
10
+ - if ticket.question3.present?
11
+ = f.text_field :response3, label: ticket.question3
@@ -0,0 +1,8 @@
1
+ - # Choose non-member
2
+ .row
3
+ .col-md= f.text_field :first_name, required: true
4
+ .col-md= f.text_field :last_name, required: true
5
+
6
+ .row
7
+ .col-md= f.email_field :email, required: true
8
+ .col-md= f.text_field :company
@@ -1,4 +1,4 @@
1
- - if event_registration.submit_order&.purchased?
1
+ - if event_registration.submit_order.present?
2
2
  .card.mb-4
3
3
  .card-body
4
4
  = render(event_registration.submit_order)
@@ -4,7 +4,7 @@
4
4
  - if resource.submit_order.deferred?
5
5
  .card
6
6
  .card-body
7
- = render_checkout_step2(resource.submit_order, purchased_url: wizard_path(:submitted), deferred_url: wizard_path(:checkout), declined_url: wizard_path(:checkout))
7
+ = render_checkout_step2(resource.submit_order, purchased_url: wizard_path(:complete), deferred_url: wizard_path(:submitted), declined_url: wizard_path(:checkout))
8
8
  - elsif resource.event.registerable? == false
9
9
  .alert.alert-danger Your selected event is no longer available for registration.
10
10
  - elsif resource.unavailable_event_tickets.present?
@@ -14,5 +14,5 @@
14
14
  - else
15
15
  .card
16
16
  .card-body
17
- = render_checkout_step2(resource.submit_order, purchased_url: wizard_path(:submitted), deferred_url: wizard_path(:checkout), declined_url: wizard_path(:checkout))
17
+ = render_checkout_step2(resource.submit_order, purchased_url: wizard_path(:complete), deferred_url: wizard_path(:submitted), declined_url: wizard_path(:checkout))
18
18
 
@@ -0,0 +1,18 @@
1
+ = render 'layout' do
2
+ = render 'effective/event_registrations/content', resource: resource
3
+
4
+ - raise('expected a completed event_registration') unless resource.completed?
5
+ - raise('expected a purchased event_registration submit_order') unless resource.submit_order&.purchased?
6
+
7
+ .alert.alert-warning.mb-4
8
+ Successfully paid on #{resource.submit_order.purchased_at.strftime('%F')}.
9
+
10
+ .mb-4
11
+ = link_to "Return to Dashboard", return_to_dashboard_path, class: 'btn btn-lg btn-primary btn-block'
12
+
13
+ = render 'effective/event_registrations/summary', event_registration: resource
14
+ = render 'effective/event_registrations/event_registration', event_registration: resource
15
+ = render 'effective/event_registrations/orders', event_registration: resource
16
+
17
+ = link_to "Return to Dashboard", return_to_dashboard_path, class: 'btn btn-lg btn-primary btn-block'
18
+
@@ -1,11 +1,8 @@
1
1
  = render 'layout' do
2
2
  = render 'effective/event_registrations/content', resource: resource
3
3
 
4
- - raise('expected a submitted event_registration') unless resource.was_submitted?
5
- - raise('expected a purchased event_registration submit_order') unless resource.submit_order&.was_purchased?
6
-
7
- .alert.alert-warning.mb-4
8
- Successfully paid on #{resource.submit_order.purchased_at.strftime('%F')}.
4
+ - raise('expected a submitted event_registration') unless resource.submitted?
5
+ - raise('expected a deffered event_registration submit_order') unless resource.submit_order&.deferred?
9
6
 
10
7
  .mb-4
11
8
  = link_to "Return to Dashboard", return_to_dashboard_path, class: 'btn btn-lg btn-primary btn-block'
@@ -48,7 +48,7 @@
48
48
  %br
49
49
  %ul
50
50
  - @event.event_tickets.each do |ticket|
51
- %li= "#{ticket} (#{price_to_currency(ticket.price)})"
51
+ %li= "#{ticket} (#{effective_events_ticket_price(@event, ticket)})"
52
52
 
53
53
  -# - if @event.event_products.present?
54
54
  -# %li
@@ -22,6 +22,8 @@ class CreateEffectiveEvents < ActiveRecord::Migration[6.0]
22
22
  t.boolean :external_registration, default: false
23
23
  t.string :external_registration_url
24
24
 
25
+ t.boolean :allow_blank_registrants, default: false
26
+
25
27
  t.integer :roles_mask
26
28
  t.boolean :authenticate_user, default: false
27
29
 
@@ -36,10 +38,16 @@ class CreateEffectiveEvents < ActiveRecord::Migration[6.0]
36
38
 
37
39
  t.string :title
38
40
  t.integer :capacity
41
+ t.string :category
39
42
 
40
43
  t.integer :regular_price
44
+ t.integer :member_price
41
45
  t.integer :early_bird_price
42
46
 
47
+ t.text :question1
48
+ t.text :question2
49
+ t.text :question3
50
+
43
51
  t.string :qb_item_name
44
52
  t.boolean :tax_exempt, default: false
45
53
 
@@ -59,6 +67,9 @@ class CreateEffectiveEvents < ActiveRecord::Migration[6.0]
59
67
  t.integer :event_registration_id
60
68
  t.string :event_registration_type
61
69
 
70
+ t.integer :user_id
71
+ t.string :user_type
72
+
62
73
  t.string :first_name
63
74
  t.string :last_name
64
75
  t.string :email
@@ -67,6 +78,13 @@ class CreateEffectiveEvents < ActiveRecord::Migration[6.0]
67
78
  t.string :number
68
79
  t.text :notes
69
80
 
81
+ t.boolean :blank_registrant, default: false
82
+ t.boolean :member_registrant, default: false
83
+
84
+ t.text :response1
85
+ t.text :response2
86
+ t.text :response3
87
+
70
88
  t.integer :purchased_order_id
71
89
  t.integer :price
72
90
 
@@ -136,6 +154,7 @@ class CreateEffectiveEvents < ActiveRecord::Migration[6.0]
136
154
 
137
155
  # Dates
138
156
  t.datetime :submitted_at
157
+ t.datetime :completed_at
139
158
 
140
159
  t.datetime :updated_at
141
160
  t.datetime :created_at
data/db/seeds.rb CHANGED
@@ -27,6 +27,7 @@ event.event_tickets.create!(
27
27
  capacity: 10,
28
28
  regular_price: 100_00,
29
29
  early_bird_price: 50_00,
30
+ member_price: 75_00,
30
31
  qb_item_name: 'Tickets'
31
32
  )
32
33
 
@@ -35,6 +36,7 @@ event.event_tickets.create!(
35
36
  capacity: 20,
36
37
  regular_price: 200_00,
37
38
  early_bird_price: 150_00,
39
+ member_price: 75_00,
38
40
  qb_item_name: 'Tickets'
39
41
  )
40
42
 
@@ -43,6 +45,7 @@ event.event_tickets.create!(
43
45
  capacity: nil,
44
46
  regular_price: 200_00,
45
47
  early_bird_price: 150_00,
48
+ member_price: 75_00,
46
49
  qb_item_name: 'Tickets'
47
50
  )
48
51
 
@@ -1,3 +1,3 @@
1
1
  module EffectiveEvents
2
- VERSION = '0.11.3'.freeze
2
+ VERSION = '0.12.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: effective_events
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.3
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Code and Effect
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-02-22 00:00:00.000000000 Z
11
+ date: 2024-05-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -267,6 +267,10 @@ files:
267
267
  - app/views/admin/events/_form_event_registration_content.html.haml
268
268
  - app/views/effective/event_addons/_fields.html.haml
269
269
  - app/views/effective/event_registrants/_fields.html.haml
270
+ - app/views/effective/event_registrants/_fields_member_only.html.haml
271
+ - app/views/effective/event_registrants/_fields_member_or_non_member.html.haml
272
+ - app/views/effective/event_registrants/_fields_questions.html.haml
273
+ - app/views/effective/event_registrants/_fields_regular.html.haml
270
274
  - app/views/effective/event_registrations/_addons.html.haml
271
275
  - app/views/effective/event_registrations/_content.html.haml
272
276
  - app/views/effective/event_registrations/_dashboard.html.haml
@@ -278,6 +282,7 @@ files:
278
282
  - app/views/effective/event_registrations/addons.html.haml
279
283
  - app/views/effective/event_registrations/billing.html.haml
280
284
  - app/views/effective/event_registrations/checkout.html.haml
285
+ - app/views/effective/event_registrations/complete.html.haml
281
286
  - app/views/effective/event_registrations/start.html.haml
282
287
  - app/views/effective/event_registrations/submitted.html.haml
283
288
  - app/views/effective/event_registrations/summary.html.haml