effective_events 0.11.3 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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