effective_events 0.19.2 → 0.20.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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/effective_events/base.js +32 -0
- data/app/controllers/effective/event_registrants_select2_ajax_controller.rb +61 -0
- data/app/controllers/effective/event_registrations_controller.rb +17 -1
- data/app/datatables/effective_event_registrants_datatable.rb +11 -11
- data/app/datatables/effective_event_registrations_datatable.rb +2 -1
- data/app/helpers/effective_events_helper.rb +2 -19
- data/app/mailers/effective/events_mailer.rb +1 -1
- data/app/models/concerns/effective_events_event_registration.rb +141 -51
- data/app/models/effective/event.rb +32 -12
- data/app/models/effective/event_notification.rb +1 -1
- data/app/models/effective/event_registrant.rb +171 -45
- data/app/models/effective/event_ticket.rb +20 -17
- data/app/models/effective/event_ticket_selection.rb +28 -0
- data/app/views/admin/event_registrants/_form.html.haml +16 -3
- data/app/views/admin/event_tickets/_form.html.haml +1 -1
- data/app/views/effective/event_registrants/_fields.html.haml +47 -28
- data/app/views/effective/event_registrations/_details.html.haml +1 -0
- data/app/views/effective/event_registrations/_fields_event_registrants.html.haml +26 -0
- data/app/views/effective/event_registrations/_fields_event_ticket_selections.html.haml +85 -0
- data/app/views/effective/event_registrations/_form_blank_registrants.html.haml +12 -14
- data/app/views/effective/event_registrations/_layout.html.haml +9 -1
- data/app/views/effective/event_registrations/billing.html.haml +1 -1
- data/app/views/effective/event_registrations/details.html.haml +10 -0
- data/app/views/effective/event_registrations/summary.html.haml +2 -2
- data/app/views/effective/event_registrations/tickets.html.haml +3 -13
- data/app/views/effective/events/show.html.haml +34 -34
- data/config/effective_events.rb +11 -4
- data/config/routes.rb +5 -0
- data/db/migrate/101_create_effective_events.rb +16 -1
- data/lib/effective_events/version.rb +1 -1
- data/lib/effective_events.rb +6 -4
- metadata +8 -7
- data/app/views/effective/event_registrants/_fields_member_only.html.haml +0 -10
- data/app/views/effective/event_registrants/_fields_member_or_non_member.html.haml +0 -34
- data/app/views/effective/event_registrants/_fields_questions.html.haml +0 -8
- data/app/views/effective/event_registrants/_fields_regular.html.haml +0 -8
- data/app/views/effective/event_registrations/_event_tickets.html.haml +0 -68
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eaa9c21f0fef7ddb63d6ed3eead8cef3e567d56adca7086a450edd80f17c52d9
|
4
|
+
data.tar.gz: '05839b258bb697ee0320b4137c307ae89cc1c930e5d1a684f12dcf21f686e748'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bbaf326950e23dbf0052418436863a79b87628c7dd6cfd79bf57eeb884fb9b6be4a3088b490620a98568c610068048f25844eb9f866bf255cadf2f1d02761bde
|
7
|
+
data.tar.gz: 82199ab2e8c26b8f6fbfc4ad3b27f2865b1a64d3ffca1a565d7866c02447a7f41b508a83177ff7fbab6e063774dc732dedd7624e014bc193b7d96a11f3c24658
|
@@ -0,0 +1,32 @@
|
|
1
|
+
// For the Ticket Details screen
|
2
|
+
$(document).on('select2:select', '[data-event-registrant-user-search]', function(event) {
|
3
|
+
var data = event.params.data['data'];
|
4
|
+
var $form = $(event.currentTarget).closest('.event-registrant-user-fields')
|
5
|
+
|
6
|
+
// Set the organization_id
|
7
|
+
$form.find('input[type="hidden"][name$="[organization_id]"]').val(data.organization_id || '')
|
8
|
+
$form.find('input[type="hidden"][name$="[organization_type]"]').val(data.organization_type || '')
|
9
|
+
|
10
|
+
// Disable everything else
|
11
|
+
$form.find('input[name$="[first_name]"]').val(data.first_name || '').prop('disabled', true)
|
12
|
+
$form.find('input[name$="[last_name]"]').val(data.last_name || '').prop('disabled', true)
|
13
|
+
$form.find('input[name$="[email]"]').val(data.email || '').prop('disabled', true)
|
14
|
+
|
15
|
+
$form.find('select[name$="[organization_id]"]').val(data.organization_id || '').trigger('change').prop('disabled', true)
|
16
|
+
$form.find('input[name$="[company]"]').val(data.company || '').prop('disabled', true)
|
17
|
+
});
|
18
|
+
|
19
|
+
$(document).on('select2:unselect', '[data-event-registrant-user-search]', function(event) {
|
20
|
+
var $form = $(event.currentTarget).closest('.event-registrant-user-fields')
|
21
|
+
|
22
|
+
// Unset the organization_id
|
23
|
+
$form.find('input[type="hidden"][name$="[organization_id]"]').val('')
|
24
|
+
|
25
|
+
// Enable everything else
|
26
|
+
$form.find('input[name$="[first_name]"]').val('').prop('disabled', false)
|
27
|
+
$form.find('input[name$="[last_name]"]').val('').prop('disabled', false)
|
28
|
+
$form.find('input[name$="[email]"]').val('').prop('disabled', false)
|
29
|
+
|
30
|
+
$form.find('select[name$="[organization_id]"]').val('').trigger('change').prop('disabled', false)
|
31
|
+
$form.find('input[name$="[company]"]').val('').prop('disabled', false)
|
32
|
+
});
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Effective
|
2
|
+
class EventRegistrantsSelect2AjaxController < ApplicationController
|
3
|
+
before_action(:authenticate_user!) if defined?(Devise)
|
4
|
+
|
5
|
+
include Effective::Select2AjaxController
|
6
|
+
|
7
|
+
def users
|
8
|
+
authorize! :users, Effective::EventRegistrant
|
9
|
+
|
10
|
+
with_organizations = current_user.class.try(:effective_memberships_organization_user?)
|
11
|
+
|
12
|
+
collection = current_user.class.all
|
13
|
+
collection = collection.includes(:organizations) if with_organizations
|
14
|
+
|
15
|
+
respond_with_select2_ajax(collection, skip_authorize: true) do |user|
|
16
|
+
data = { first_name: user.first_name, last_name: user.last_name, email: user.email }
|
17
|
+
|
18
|
+
if with_organizations
|
19
|
+
data[:company] = user.organizations.first.try(:to_s)
|
20
|
+
data[:organization_id] = user.organizations.first.try(:id)
|
21
|
+
data[:organization_type] = user.organizations.first.try(:class).try(:name)
|
22
|
+
end
|
23
|
+
|
24
|
+
{
|
25
|
+
id: user.to_param,
|
26
|
+
text: to_select2(user, with_organizations),
|
27
|
+
data: data
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def organizations
|
33
|
+
raise('expected EffectiveEvents.organization_enabled?') unless EffectiveEvents.organization_enabled?
|
34
|
+
|
35
|
+
klass = EffectiveMemberships.Organization
|
36
|
+
raise('an EffectiveMemberships.Organization is required') unless klass.try(:effective_memberships_organization?)
|
37
|
+
|
38
|
+
collection = klass.all
|
39
|
+
|
40
|
+
# Authorize
|
41
|
+
EffectiveResources.authorize!(self, :member_organizations, collection.klass)
|
42
|
+
|
43
|
+
respond_with_select2_ajax(collection, skip_authorize: true) do |organization|
|
44
|
+
{ id: organization.to_param, text: organization.to_s }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def to_select2(resource, with_organizations)
|
51
|
+
organizations = Array(resource.try(:organizations)).join(', ') if with_organizations
|
52
|
+
|
53
|
+
[
|
54
|
+
"<span>#{resource}</span>",
|
55
|
+
"<small><#{resource.try(:public_email).presence || resource.email}></small>",
|
56
|
+
("<small>#{organizations}</small>" if organizations.present?)
|
57
|
+
].compact.join(' ')
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -7,6 +7,7 @@ module Effective
|
|
7
7
|
include Effective::WizardController
|
8
8
|
|
9
9
|
before_action :redirect_unless_registerable, only: [:new, :show]
|
10
|
+
before_action :expire_ticket_selection_window, only: [:show]
|
10
11
|
|
11
12
|
resource_scope -> {
|
12
13
|
event = Effective::Event.find(params[:event_id])
|
@@ -18,12 +19,27 @@ module Effective
|
|
18
19
|
return if resource.blank?
|
19
20
|
return if resource.was_submitted?
|
20
21
|
return if resource.event.blank?
|
21
|
-
return if resource.event.registerable?
|
22
22
|
return if resource.submit_order&.deferred?
|
23
|
+
return if resource.event.registerable? && !resource.event.sold_out?(except: resource)
|
23
24
|
|
24
25
|
flash[:danger] = "Your selected event is no longer available for registration. This event registration is no longer available."
|
25
26
|
return redirect_to('/dashboard')
|
26
27
|
end
|
27
28
|
|
29
|
+
def expire_ticket_selection_window
|
30
|
+
return if resource.blank?
|
31
|
+
return if resource.was_submitted?
|
32
|
+
return if resource.event.blank?
|
33
|
+
return if resource.selection_not_expired?
|
34
|
+
|
35
|
+
resource.ticket_selection_expired!
|
36
|
+
|
37
|
+
flash[:danger] = "Your ticket reservation window has expired. Your tickets are no longer reserved. Please start over."
|
38
|
+
|
39
|
+
return redirect_to(wizard_path(:start))
|
40
|
+
end
|
41
|
+
|
42
|
+
# TODO: Add better permitted params
|
43
|
+
|
28
44
|
end
|
29
45
|
end
|
@@ -6,8 +6,11 @@ class EffectiveEventRegistrantsDatatable < Effective::Datatable
|
|
6
6
|
|
7
7
|
col :name do |er|
|
8
8
|
if er.first_name.present?
|
9
|
-
|
10
|
-
|
9
|
+
[
|
10
|
+
"#{er.first_name} #{er.last_name}",
|
11
|
+
("<small>#{er.organization || er.company}</small>" if er.organization || er.company.present?),
|
12
|
+
("<small>#{er.email}</small>" if er.email.present?)
|
13
|
+
].compact.join('<br>').html_safe
|
11
14
|
elsif er.owner.present?
|
12
15
|
er.owner.to_s + ' - GUEST'
|
13
16
|
else
|
@@ -18,14 +21,12 @@ class EffectiveEventRegistrantsDatatable < Effective::Datatable
|
|
18
21
|
col :id, visible: false
|
19
22
|
|
20
23
|
col :event_ticket, search: :string, label: 'Ticket' do |er|
|
21
|
-
[
|
22
|
-
er.event_ticket.to_s,
|
23
|
-
(content_tag(:span, 'Waitlist', class: 'badge badge-warning') if er.waitlisted_not_promoted?),
|
24
|
-
(content_tag(:span, 'Archived', class: 'badge badge-warning') if er.event_ticket&.archived?)
|
25
|
-
].compact.join('<br>').html_safe
|
24
|
+
[er.event_ticket.to_s, er.details.presence].compact.join('<br>').html_safe
|
26
25
|
end
|
27
26
|
|
28
27
|
col :user, label: 'Member', visible: false
|
28
|
+
col :organization, visible: false
|
29
|
+
|
29
30
|
col :first_name, visible: false
|
30
31
|
col :last_name, visible: false
|
31
32
|
col :email, visible: false
|
@@ -35,14 +36,13 @@ class EffectiveEventRegistrantsDatatable < Effective::Datatable
|
|
35
36
|
col :response2, visible: false
|
36
37
|
col :response3, visible: false
|
37
38
|
|
38
|
-
col :
|
39
|
+
col :responses, label: 'Details' do |registrant|
|
39
40
|
[registrant.response1.presence, registrant.response2.presence, registrant.response3.presence].compact.map do |response|
|
40
41
|
content_tag(:div, response)
|
41
42
|
end.join.html_safe
|
42
43
|
end
|
43
44
|
|
44
|
-
|
45
|
-
col :event_ticket_price, as: :price, label: 'Price'
|
45
|
+
col :price, as: :price
|
46
46
|
col :archived, visible: false
|
47
47
|
|
48
48
|
# no actions_col
|
@@ -56,7 +56,7 @@ class EffectiveEventRegistrantsDatatable < Effective::Datatable
|
|
56
56
|
end
|
57
57
|
|
58
58
|
if event_registration.present?
|
59
|
-
scope = scope.where(event_registration_id: event_registration)
|
59
|
+
scope = scope.where(event_registration_id: event_registration).sorted
|
60
60
|
end
|
61
61
|
|
62
62
|
scope
|
@@ -17,6 +17,7 @@ class EffectiveEventRegistrationsDatatable < Effective::Datatable
|
|
17
17
|
col :owner, visible: false, search: :string
|
18
18
|
col :status, visible: false
|
19
19
|
col :event_registrants, label: 'Registrants', search: :string
|
20
|
+
|
20
21
|
col :event_addons, label: 'Add-ons', search: :string
|
21
22
|
col :orders, action: :show, visible: false, search: :string
|
22
23
|
|
@@ -31,7 +32,7 @@ class EffectiveEventRegistrationsDatatable < Effective::Datatable
|
|
31
32
|
end
|
32
33
|
|
33
34
|
# Register Again
|
34
|
-
if er.event.registerable?
|
35
|
+
if er.event.registerable? && !er.event.sold_out?
|
35
36
|
url = er.event.external_registration_url.presence || effective_events.new_event_event_registration_path(er.event)
|
36
37
|
dropdown_link_to('Register Again', url)
|
37
38
|
end
|
@@ -52,6 +52,8 @@ module EffectiveEventsHelper
|
|
52
52
|
|
53
53
|
tickets.map do |ticket|
|
54
54
|
title = ticket.to_s
|
55
|
+
title = "#{title} (archived)" if ticket.archived?
|
56
|
+
|
55
57
|
price = effective_events_ticket_price(event, ticket)
|
56
58
|
|
57
59
|
label = [title, price].compact.join(' - ')
|
@@ -81,23 +83,4 @@ module EffectiveEventsHelper
|
|
81
83
|
end
|
82
84
|
end
|
83
85
|
|
84
|
-
def effective_events_event_registrant_user_collection(event_registrant)
|
85
|
-
raise("expected an Effective::EventRegistrant") unless event_registrant.kind_of?(Effective::EventRegistrant)
|
86
|
-
|
87
|
-
Array(event_registrant.event_registration&.event_ticket_member_users).map do |user|
|
88
|
-
["<span>#{user}</span> <small>#{user.email}</small>", user.to_param]
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def effective_events_event_registrant_user_hint
|
93
|
-
url = if current_user.class.try(:effective_memberships_organization_user?)
|
94
|
-
organization = current_user.membership_organizations.first || current_user.organizations.first
|
95
|
-
effective_memberships.edit_organization_path(organization, anchor: 'tab-representatives') if organization
|
96
|
-
end
|
97
|
-
|
98
|
-
return if url.blank?
|
99
|
-
|
100
|
-
"Can't find the member you need? <a href='#{url}' target='blank'>Click here</a> to add them to your #{EffectiveResources.etd(EffectiveMemberships.Organization)}."
|
101
|
-
end
|
102
|
-
|
103
86
|
end
|
@@ -8,7 +8,7 @@ module Effective
|
|
8
8
|
raise('expected an Effective::EventRegistrant') unless resource.kind_of?(Effective::EventRegistrant)
|
9
9
|
|
10
10
|
@assigns = assigns_for(resource)
|
11
|
-
mail(to: resource.
|
11
|
+
mail(to: resource.email, **headers_for(resource, opts))
|
12
12
|
end
|
13
13
|
|
14
14
|
protected
|
@@ -15,12 +15,13 @@ module EffectiveEventsEventRegistration
|
|
15
15
|
|
16
16
|
module ClassMethods
|
17
17
|
def effective_events_event_registration?; true; end
|
18
|
+
|
19
|
+
def selection_window
|
20
|
+
30.minutes
|
21
|
+
end
|
18
22
|
end
|
19
23
|
|
20
24
|
included do
|
21
|
-
# Needs to be first up here. Before the acts_as_purchasable_parent one voids the order
|
22
|
-
around_destroy :around_destroy_deferred_event_registration, if: -> { submit_order&.deferred? }
|
23
|
-
|
24
25
|
acts_as_purchasable_parent
|
25
26
|
acts_as_tokened
|
26
27
|
|
@@ -33,6 +34,7 @@ module EffectiveEventsEventRegistration
|
|
33
34
|
acts_as_wizard(
|
34
35
|
start: 'Start',
|
35
36
|
tickets: 'Tickets',
|
37
|
+
details: 'Ticket Details',
|
36
38
|
addons: 'Add-ons',
|
37
39
|
summary: 'Review',
|
38
40
|
billing: 'Billing Address',
|
@@ -52,10 +54,13 @@ module EffectiveEventsEventRegistration
|
|
52
54
|
# Effective Namespace
|
53
55
|
belongs_to :event, class_name: 'Effective::Event'
|
54
56
|
|
55
|
-
has_many :
|
57
|
+
has_many :event_ticket_selections, -> { order(:id) }, class_name: 'Effective::EventTicketSelection', inverse_of: :event_registration, dependent: :destroy
|
58
|
+
accepts_nested_attributes_for :event_ticket_selections, reject_if: :all_blank, allow_destroy: true
|
59
|
+
|
60
|
+
has_many :event_registrants, -> { order(:event_ticket_id, :id) }, class_name: 'Effective::EventRegistrant', inverse_of: :event_registration, dependent: :destroy
|
56
61
|
accepts_nested_attributes_for :event_registrants, reject_if: :all_blank, allow_destroy: true
|
57
62
|
|
58
|
-
has_many :event_addons, -> { order(:id) }, class_name: 'Effective::EventAddon', inverse_of: :event_registration, dependent: :destroy
|
63
|
+
has_many :event_addons, -> { order(:event_product_id, :id) }, class_name: 'Effective::EventAddon', inverse_of: :event_registration, dependent: :destroy
|
59
64
|
accepts_nested_attributes_for :event_addons, reject_if: :all_blank, allow_destroy: true
|
60
65
|
|
61
66
|
has_many :orders, -> { order(:id) }, as: :parent, class_name: 'Effective::Order', dependent: :nullify
|
@@ -83,9 +88,10 @@ module EffectiveEventsEventRegistration
|
|
83
88
|
timestamps
|
84
89
|
end
|
85
90
|
|
86
|
-
scope :deep, -> {
|
91
|
+
scope :deep, -> {
|
87
92
|
includes(:owner)
|
88
93
|
.includes(event: [:rich_texts, event_products: :purchased_event_addons, event_tickets: :purchased_event_registrants])
|
94
|
+
.includes(event_ticket_selections: [:event_ticket])
|
89
95
|
.includes(event_registrants: [event_ticket: :purchased_event_registrants])
|
90
96
|
.includes(event_addons: [event_product: :purchased_event_addons])
|
91
97
|
}
|
@@ -106,18 +112,32 @@ module EffectiveEventsEventRegistration
|
|
106
112
|
|
107
113
|
# All Steps
|
108
114
|
validate(if: -> { event.present? }) do
|
109
|
-
|
115
|
+
errors.add(:base, "cannot register for an external registration event") if event.external_registration?
|
110
116
|
end
|
111
117
|
|
112
118
|
# Tickets Step
|
113
119
|
validate(if: -> { current_step == :tickets }) do
|
114
|
-
|
120
|
+
if event_ticket_selections.all? { |selection| selection.quantity.to_i == 0 }
|
121
|
+
errors.add(:event_ticket_selections, "Please select one or more tickets")
|
122
|
+
event_ticket_selections.each { |ets| ets.errors.add(:quantity, "can't be blank") }
|
123
|
+
end
|
115
124
|
end
|
116
125
|
|
117
126
|
# Validate all tickets are available for registration
|
118
127
|
validate(if: -> { current_step == :tickets }) do
|
119
128
|
unavailable_event_tickets.each do |event_ticket|
|
120
129
|
errors.add(:base, "The requested number of #{event_ticket} tickets are not available")
|
130
|
+
event_ticket_selections.find { |ets| ets.event_ticket == event_ticket }.errors.add(:quantity, "not available")
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Validate the same registrant user isn't being registered twice
|
135
|
+
validate(if: -> { current_step == :details }) do
|
136
|
+
present_event_registrants.group_by { |er| er.user }.each do |user, event_registrants|
|
137
|
+
if user.present? && event_registrants.length > 1
|
138
|
+
errors.add(:base, "Unable to register #{user} more than once")
|
139
|
+
event_registrants.each { |er| er.errors.add(:user_id, "cannot be registered more than once") }
|
140
|
+
end
|
121
141
|
end
|
122
142
|
end
|
123
143
|
|
@@ -131,19 +151,6 @@ module EffectiveEventsEventRegistration
|
|
131
151
|
# If we're submitted. Try to move to completed.
|
132
152
|
before_save(if: -> { submitted? }) { try_completed! }
|
133
153
|
|
134
|
-
def around_destroy_deferred_event_registration
|
135
|
-
raise('expecting a deferred submit order') unless submit_order&.deferred?
|
136
|
-
|
137
|
-
waitlisted_event_tickets_was = event_tickets().select(&:waitlist?)
|
138
|
-
yield
|
139
|
-
waitlisted_event_tickets_was.each { |event_ticket| event_ticket.update_waitlist! }
|
140
|
-
true
|
141
|
-
end
|
142
|
-
|
143
|
-
def delayed_payment_date_upcoming?
|
144
|
-
event&.delayed_payment_date_upcoming?
|
145
|
-
end
|
146
|
-
|
147
154
|
def can_visit_step?(step)
|
148
155
|
return false if step == :complete && !completed?
|
149
156
|
return true if step == :complete && completed?
|
@@ -164,17 +171,24 @@ module EffectiveEventsEventRegistration
|
|
164
171
|
|
165
172
|
def required_steps
|
166
173
|
return self.class.test_required_steps if Rails.env.test? && self.class.test_required_steps.present?
|
167
|
-
event&.event_products.unarchived.present? ? wizard_step_keys : (wizard_step_keys - [:addons])
|
168
|
-
end
|
169
174
|
|
170
|
-
|
171
|
-
|
175
|
+
with_addons = event.event_products.any? { |event_product| event_product.archived? == false }
|
176
|
+
|
177
|
+
with_addons ? wizard_step_keys : (wizard_step_keys - [:addons])
|
172
178
|
end
|
173
179
|
|
174
180
|
def delayed_payment_attributes
|
175
181
|
{ delayed_payment: event&.delayed_payment, delayed_payment_date: event&.delayed_payment_date }
|
176
182
|
end
|
177
183
|
|
184
|
+
def delayed_payment_date_upcoming?
|
185
|
+
event&.delayed_payment_date_upcoming?
|
186
|
+
end
|
187
|
+
|
188
|
+
def find_or_build_submit_fees
|
189
|
+
with_outstanding_coupon_fees(submit_fees)
|
190
|
+
end
|
191
|
+
|
178
192
|
# All Fees and Orders
|
179
193
|
def submit_fees
|
180
194
|
if defined?(EffectiveMemberships)
|
@@ -217,7 +231,95 @@ module EffectiveEventsEventRegistration
|
|
217
231
|
completed?
|
218
232
|
end
|
219
233
|
|
234
|
+
def display_countdown?
|
235
|
+
return false if done?
|
236
|
+
return false unless selected_at.present?
|
237
|
+
return false unless current_step.present?
|
238
|
+
|
239
|
+
[:start, :tickets, :submitted, :complete].exclude?(current_step)
|
240
|
+
end
|
241
|
+
|
242
|
+
# When we make a ticket selection, we assign the selected_at to all tickets
|
243
|
+
# So the max or the min should be the same here.
|
244
|
+
def selected_at
|
245
|
+
event_registrants.map(&:selected_at).compact.max
|
246
|
+
end
|
247
|
+
|
248
|
+
def selected_expires_at
|
249
|
+
selected_at + EffectiveEvents.EventRegistration.selection_window
|
250
|
+
end
|
251
|
+
|
252
|
+
def selected_expired?
|
253
|
+
return false if selected_at.blank?
|
254
|
+
Time.zone.now >= selected_expires_at
|
255
|
+
end
|
256
|
+
|
257
|
+
def selection_not_expired?
|
258
|
+
return true if selected_at.blank?
|
259
|
+
Time.zone.now < selected_expires_at
|
260
|
+
end
|
261
|
+
|
262
|
+
# Called by a before_action on the event registration show action
|
263
|
+
def ticket_selection_expired!
|
264
|
+
raise("unexpected submitted registration") if was_submitted?
|
265
|
+
raise("unexpected purchased order") if submit_order&.purchased?
|
266
|
+
raise("unexpected deferred order") if submit_order&.deferred?
|
267
|
+
|
268
|
+
event_registrants.each { |er| er.assign_attributes(selected_at: nil) }
|
269
|
+
event_ticket_selections.each { |ets| ets.assign_attributes(quantity: 0) }
|
270
|
+
assign_attributes(current_step: nil, wizard_steps: {}) # Reset all steps
|
271
|
+
|
272
|
+
save!
|
273
|
+
end
|
274
|
+
|
275
|
+
# This considers the event_ticket_selection and builds the appropriate event_registrants
|
276
|
+
def update_event_registrants
|
277
|
+
event_ticket_selections.each do |event_ticket_selection|
|
278
|
+
event_ticket = event_ticket_selection.event_ticket
|
279
|
+
quantity = event_ticket_selection.quantity.to_i
|
280
|
+
|
281
|
+
# All the registrants for this event ticket
|
282
|
+
registrants = event_registrants.select { |er| er.event_ticket == event_ticket }
|
283
|
+
|
284
|
+
# Delete over quantity
|
285
|
+
if (diff = registrants.length - quantity) > 0
|
286
|
+
registrants.last(diff).each { |er| er.mark_for_destruction }
|
287
|
+
end
|
288
|
+
|
289
|
+
# Create upto quantity
|
290
|
+
if (diff = quantity - registrants.length) > 0
|
291
|
+
diff.times { build_event_registrant(event_ticket: event_ticket) }
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
event_registrants
|
296
|
+
end
|
297
|
+
|
298
|
+
# Assigns the selected at time to start the reservation window
|
299
|
+
def select_event_registrants
|
300
|
+
now = Time.zone.now
|
301
|
+
present_event_registrants.each { |er| er.assign_attributes(selected_at: now) }
|
302
|
+
end
|
303
|
+
|
304
|
+
# Looks at any unselected event registrants and assigns a waitlist value
|
305
|
+
def waitlist_event_registrants
|
306
|
+
present_event_registrants.group_by { |er| er.event_ticket }.each do |event_ticket, event_registrants|
|
307
|
+
if event_ticket.waitlist?
|
308
|
+
capacity = event.capacity_available(event_ticket: event_ticket, event_registration: self)
|
309
|
+
event_registrants.each_with_index { |er, index| er.assign_attributes(waitlisted: index >= capacity) }
|
310
|
+
else
|
311
|
+
event_registrants.each { |er| er.assign_attributes(waitlisted: false) }
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
220
316
|
def tickets!
|
317
|
+
assign_attributes(current_step: :tickets) # Ensure the unavailable tickets validations are run
|
318
|
+
|
319
|
+
update_event_registrants
|
320
|
+
select_event_registrants
|
321
|
+
waitlist_event_registrants
|
322
|
+
|
221
323
|
after_commit do
|
222
324
|
update_submit_fees_and_order! if submit_order.present?
|
223
325
|
update_deferred_event_registration! if submit_order&.deferred?
|
@@ -252,27 +354,26 @@ module EffectiveEventsEventRegistration
|
|
252
354
|
true
|
253
355
|
end
|
254
356
|
|
255
|
-
|
256
|
-
|
257
|
-
registrant = event_registrants.find { |er| er.event_ticket == event_ticket && er.first_name == first_name && er.last_name == last_name && er.email == email }
|
258
|
-
registrant || event_registrants.build(event: event, event_ticket: event_ticket, owner: owner, first_name: first_name, last_name: last_name, email: email)
|
357
|
+
def build_event_registrant(event_ticket:)
|
358
|
+
event_registrants.build(event: event, event_ticket: event_ticket, owner: owner)
|
259
359
|
end
|
260
360
|
|
361
|
+
# Find or build - # Used for testing
|
362
|
+
# def event_registrant(event_ticket:, first_name:, last_name:, email:)
|
363
|
+
# registrant = event_registrants.find { |er| er.event_ticket == event_ticket && er.first_name == first_name && er.last_name == last_name && er.email == email }
|
364
|
+
# registrant || event_registrants.build(event: event, event_ticket: event_ticket, owner: owner, first_name: first_name, last_name: last_name, email: email)
|
365
|
+
# end
|
366
|
+
|
261
367
|
# Find or build. But it's not gonna work with more than 1. This is for testing only really.
|
262
368
|
def event_addon(event_product:, first_name:, last_name:, email:)
|
263
369
|
addon = event_addons.find { |er| er.event_product == event_product && er.first_name == first_name && er.last_name == last_name && er.email == email }
|
264
370
|
addon || event_addons.build(event: event, event_product: event_product, owner: owner, first_name: first_name, last_name: last_name, email: email)
|
265
371
|
end
|
266
372
|
|
267
|
-
#
|
268
|
-
def
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
event_registrants.build()
|
273
|
-
end
|
274
|
-
|
275
|
-
event_registrants
|
373
|
+
# Find or build
|
374
|
+
def event_ticket_selection(event_ticket:, quantity: 0)
|
375
|
+
selection = event_ticket_selections.find { |ets| ets.event_ticket == event_ticket }
|
376
|
+
selection || event_ticket_selections.build(event_ticket: event_ticket, quantity: quantity)
|
276
377
|
end
|
277
378
|
|
278
379
|
# This builds the default event addons used by the wizard form
|
@@ -298,9 +399,7 @@ module EffectiveEventsEventRegistration
|
|
298
399
|
unavailable = []
|
299
400
|
|
300
401
|
present_event_registrants.map(&:event_ticket).group_by { |t| t }.each do |event_ticket, event_tickets|
|
301
|
-
|
302
|
-
unavailable << event_ticket
|
303
|
-
end
|
402
|
+
unavailable << event_ticket unless event.event_ticket_available?(event_ticket, except: self, quantity: event_tickets.length)
|
304
403
|
end
|
305
404
|
|
306
405
|
unavailable
|
@@ -316,13 +415,6 @@ module EffectiveEventsEventRegistration
|
|
316
415
|
unavailable
|
317
416
|
end
|
318
417
|
|
319
|
-
def event_ticket_member_users
|
320
|
-
raise("expected owner to be a user") if owner.class.try(:effective_memberships_organization?)
|
321
|
-
users = [owner] + (owner.try(:organizations).try(:flat_map, &:users) || [])
|
322
|
-
|
323
|
-
users.select { |user| user.is_any?(:member) }.uniq
|
324
|
-
end
|
325
|
-
|
326
418
|
def update_blank_registrants!
|
327
419
|
# This method is called by the user on a submitted or completed event registration.
|
328
420
|
# Allow them to update blank registrants
|
@@ -343,6 +435,7 @@ module EffectiveEventsEventRegistration
|
|
343
435
|
raise('unable to make changes to event addons while updating blank registrants')
|
344
436
|
end
|
345
437
|
|
438
|
+
assign_attributes(current_step: :details) if current_step.blank? # Enables validations
|
346
439
|
save!
|
347
440
|
|
348
441
|
update_submit_fees_and_order! if submit_order.present? && !submit_order.purchased?
|
@@ -358,9 +451,6 @@ module EffectiveEventsEventRegistration
|
|
358
451
|
# Mark registered anyone who hasn't been registered yet. They are now!
|
359
452
|
event_registrants.reject(&:registered?).each { |event_registrant| event_registrant.registered! }
|
360
453
|
|
361
|
-
# Update the waitlist for any event tickets
|
362
|
-
event_tickets.select(&:waitlist?).each { |event_ticket| event_ticket.update_waitlist! }
|
363
|
-
|
364
454
|
true
|
365
455
|
end
|
366
456
|
|
@@ -15,7 +15,7 @@ module Effective
|
|
15
15
|
has_many :event_products, -> { EventProduct.sorted }, inverse_of: :event, dependent: :destroy
|
16
16
|
accepts_nested_attributes_for :event_products, allow_destroy: true
|
17
17
|
|
18
|
-
has_many :event_registrants, -> { order(:event_ticket_id
|
18
|
+
has_many :event_registrants, -> { order(:event_ticket_id, :id) }, inverse_of: :event
|
19
19
|
accepts_nested_attributes_for :event_registrants, allow_destroy: true
|
20
20
|
|
21
21
|
has_many :event_addons, -> { order(:event_product_id).order(:id) }, inverse_of: :event
|
@@ -190,11 +190,10 @@ module Effective
|
|
190
190
|
event_tickets.any? { |et| et.waitlist? }
|
191
191
|
end
|
192
192
|
|
193
|
+
# No longer includes sold_out? we check that separately
|
193
194
|
def registerable?
|
194
195
|
return false unless published?
|
195
196
|
return false if closed?
|
196
|
-
return false if sold_out?
|
197
|
-
|
198
197
|
(external_registration? && external_registration_url.present?) || event_tickets.present?
|
199
198
|
end
|
200
199
|
|
@@ -203,10 +202,13 @@ module Effective
|
|
203
202
|
registration_end_at < Time.zone.now
|
204
203
|
end
|
205
204
|
|
206
|
-
def sold_out?
|
205
|
+
def sold_out?(except: nil)
|
206
|
+
raise('expected except to be an EventRegistration') if except && !except.class.try(:effective_events_event_registration?)
|
207
|
+
|
207
208
|
return false unless event_tickets.present?
|
208
209
|
return false if any_waitlist?
|
209
|
-
|
210
|
+
|
211
|
+
event_tickets.none? { |event_ticket| event_ticket_available?(event_ticket, except: except, quantity: 1) }
|
210
212
|
end
|
211
213
|
|
212
214
|
def early_bird?
|
@@ -250,19 +252,37 @@ module Effective
|
|
250
252
|
start_at
|
251
253
|
end
|
252
254
|
|
255
|
+
# The amount of tickets that can be purchased except ones from an event registration
|
256
|
+
def capacity_selectable(event_ticket:, event_registration: nil)
|
257
|
+
return 0 if event_ticket.archived?
|
258
|
+
return 100 if event_ticket.capacity.blank?
|
259
|
+
return 100 if event_ticket.waitlist?
|
260
|
+
|
261
|
+
event_ticket.capacity_selectable(except: event_registration)
|
262
|
+
end
|
263
|
+
|
264
|
+
# The amount of tickets that can be purchased except ones from an event registration
|
265
|
+
def capacity_available(event_ticket:, event_registration: nil)
|
266
|
+
event_ticket.capacity_available(except: event_registration)
|
267
|
+
end
|
268
|
+
|
269
|
+
# Just used in tests so far
|
270
|
+
def capacity_taken(event_ticket:, event_registration: nil)
|
271
|
+
event_ticket.capacity_taken(except: event_registration)
|
272
|
+
end
|
273
|
+
|
253
274
|
# Can I register/purchase this many new event tickets?
|
254
|
-
def event_ticket_available?(event_ticket, quantity:)
|
275
|
+
def event_ticket_available?(event_ticket, except: nil, quantity: 0)
|
255
276
|
raise('expected an EventTicket') unless event_ticket.kind_of?(Effective::EventTicket)
|
277
|
+
raise('expected except to be an EventRegistration') if except && !except.class.try(:effective_events_event_registration?)
|
256
278
|
raise('expected quantity to be greater than 0') unless quantity.to_i > 0
|
257
279
|
|
258
280
|
return false if event_ticket.archived?
|
259
|
-
return true if event_ticket.capacity.blank?
|
281
|
+
return true if event_ticket.capacity.blank? # No capacity enforced
|
282
|
+
return true if event_ticket.waitlist? # Always available for waitlist
|
260
283
|
|
261
|
-
#
|
262
|
-
|
263
|
-
|
264
|
-
# If there's capacity for this many more
|
265
|
-
(registered + quantity) <= event_ticket.capacity
|
284
|
+
# Do we have any tickets available left?
|
285
|
+
event_ticket.capacity_available(except: except) >= quantity.to_i
|
266
286
|
end
|
267
287
|
|
268
288
|
# Can I register/purchase this many new event products?
|
@@ -150,7 +150,7 @@ module Effective
|
|
150
150
|
update_column(:started_at, Time.zone.now)
|
151
151
|
|
152
152
|
event_registrants.each do |event_registrant|
|
153
|
-
next if event_registrant.
|
153
|
+
next if event_registrant.email.blank?
|
154
154
|
|
155
155
|
begin
|
156
156
|
EffectiveEvents.send_email(email_template, event_registrant, email_notification_params)
|