effective_memberships 0.1.2
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +114 -0
- data/Rakefile +18 -0
- data/app/assets/config/effective_memberships_manifest.js +3 -0
- data/app/assets/javascripts/effective_memberships/applicant_courses.js +29 -0
- data/app/assets/javascripts/effective_memberships/applicant_experiences.js +38 -0
- data/app/assets/javascripts/effective_memberships/base.js +0 -0
- data/app/assets/javascripts/effective_memberships.js +1 -0
- data/app/assets/stylesheets/effective_memberships/base.scss +5 -0
- data/app/assets/stylesheets/effective_memberships.scss +1 -0
- data/app/controllers/admin/applicant_course_areas_controller.rb +15 -0
- data/app/controllers/admin/applicant_course_names_controller.rb +15 -0
- data/app/controllers/admin/applicants_controller.rb +33 -0
- data/app/controllers/admin/categories_controller.rb +19 -0
- data/app/controllers/admin/fees_controller.rb +18 -0
- data/app/controllers/admin/registrar_actions_controller.rb +49 -0
- data/app/controllers/effective/applicant_references_controller.rb +37 -0
- data/app/controllers/effective/applicants_controller.rb +34 -0
- data/app/controllers/effective/fee_payments_controller.rb +29 -0
- data/app/datatables/admin/effective_applicant_course_areas_datatable.rb +17 -0
- data/app/datatables/admin/effective_applicant_course_names_datatable.rb +17 -0
- data/app/datatables/admin/effective_applicants_datatable.rb +55 -0
- data/app/datatables/admin/effective_categories_datatable.rb +23 -0
- data/app/datatables/admin/effective_fees_datatable.rb +69 -0
- data/app/datatables/admin/effective_membership_histories_datatable.rb +35 -0
- data/app/datatables/effective_applicant_courses_datatable.rb +30 -0
- data/app/datatables/effective_applicant_educations_datatable.rb +22 -0
- data/app/datatables/effective_applicant_experiences_datatable.rb +42 -0
- data/app/datatables/effective_applicant_references_datatable.rb +34 -0
- data/app/datatables/effective_applicants_datatable.rb +34 -0
- data/app/helpers/effective_memberships_helper.rb +15 -0
- data/app/mailers/effective/memberships_mailer.rb +84 -0
- data/app/models/concerns/effective_memberships_applicant.rb +612 -0
- data/app/models/concerns/effective_memberships_applicant_review.rb +149 -0
- data/app/models/concerns/effective_memberships_category.rb +196 -0
- data/app/models/concerns/effective_memberships_fee_payment.rb +229 -0
- data/app/models/concerns/effective_memberships_owner.rb +263 -0
- data/app/models/concerns/effective_memberships_registrar.rb +300 -0
- data/app/models/effective/applicant.rb +7 -0
- data/app/models/effective/applicant_course.rb +40 -0
- data/app/models/effective/applicant_course_area.rb +31 -0
- data/app/models/effective/applicant_course_name.rb +29 -0
- data/app/models/effective/applicant_education.rb +36 -0
- data/app/models/effective/applicant_experience.rb +79 -0
- data/app/models/effective/applicant_reference.rb +81 -0
- data/app/models/effective/applicant_review.rb +7 -0
- data/app/models/effective/category.rb +7 -0
- data/app/models/effective/fee.rb +142 -0
- data/app/models/effective/fee_payment.rb +7 -0
- data/app/models/effective/membership.rb +119 -0
- data/app/models/effective/membership_category.rb +11 -0
- data/app/models/effective/membership_history.rb +40 -0
- data/app/models/effective/registrar.rb +19 -0
- data/app/models/effective/registrar_action.rb +97 -0
- data/app/views/admin/applicant_course_areas/_form.html.haml +8 -0
- data/app/views/admin/applicant_course_areas/_form_applicant_course_area.html.haml +8 -0
- data/app/views/admin/applicant_course_areas/index.html.haml +8 -0
- data/app/views/admin/applicant_course_name/_form.html.haml +9 -0
- data/app/views/admin/applicants/_form.html.haml +38 -0
- data/app/views/admin/applicants/_form_approve.html.haml +34 -0
- data/app/views/admin/applicants/_form_decline.html.haml +11 -0
- data/app/views/admin/applicants/_form_process.html.haml +18 -0
- data/app/views/admin/applicants/_status.html.haml +133 -0
- data/app/views/admin/categories/_form.html.haml +14 -0
- data/app/views/admin/categories/_form_applicant.html.haml +12 -0
- data/app/views/admin/categories/_form_applicant_content.html.haml +19 -0
- data/app/views/admin/categories/_form_applicant_eligibility.html.haml +13 -0
- data/app/views/admin/categories/_form_applicant_fees.html.haml +29 -0
- data/app/views/admin/categories/_form_applicant_steps.html.haml +42 -0
- data/app/views/admin/categories/_form_category.html.haml +10 -0
- data/app/views/admin/categories/_form_fee_payment.html.haml +9 -0
- data/app/views/admin/categories/_form_fee_payment_content.html.haml +19 -0
- data/app/views/admin/categories/_form_fee_payment_steps.html.haml +9 -0
- data/app/views/admin/categories/_form_renewals.html.haml +32 -0
- data/app/views/admin/fees/_fee.html.haml +1 -0
- data/app/views/admin/fees/_form.html.haml +14 -0
- data/app/views/admin/memberships/_status.html.haml +6 -0
- data/app/views/admin/registrar_actions/_form.html.haml +10 -0
- data/app/views/admin/registrar_actions/_form_bad_standing.html.haml +43 -0
- data/app/views/admin/registrar_actions/_form_fees_paid.html.haml +30 -0
- data/app/views/admin/registrar_actions/_form_reclassify.html.haml +43 -0
- data/app/views/admin/registrar_actions/_form_register.html.haml +44 -0
- data/app/views/admin/registrar_actions/_form_remove.html.haml +17 -0
- data/app/views/effective/applicant_references/_applicant_reference.html.haml +51 -0
- data/app/views/effective/applicant_references/_datatable_actions.html.haml +4 -0
- data/app/views/effective/applicant_references/_form.html.haml +18 -0
- data/app/views/effective/applicant_references/_form_declaration.html.haml +37 -0
- data/app/views/effective/applicant_references/complete.html.haml +3 -0
- data/app/views/effective/applicant_references/edit.html.haml +8 -0
- data/app/views/effective/applicants/_applicant.html.haml +6 -0
- data/app/views/effective/applicants/_content.html.haml +10 -0
- data/app/views/effective/applicants/_course_amounts.html.haml +19 -0
- data/app/views/effective/applicants/_dashboard.html.haml +19 -0
- data/app/views/effective/applicants/_declarations.html.haml +16 -0
- data/app/views/effective/applicants/_demographics.html.haml +9 -0
- data/app/views/effective/applicants/_demographics_fields.html.haml +11 -0
- data/app/views/effective/applicants/_demographics_owner.html.haml +20 -0
- data/app/views/effective/applicants/_education.html.haml +14 -0
- data/app/views/effective/applicants/_experience.html.haml +28 -0
- data/app/views/effective/applicants/_files.html.haml +27 -0
- data/app/views/effective/applicants/_layout.html.haml +3 -0
- data/app/views/effective/applicants/_orders.html.haml +4 -0
- data/app/views/effective/applicants/_references.html.haml +15 -0
- data/app/views/effective/applicants/_summary.html.haml +40 -0
- data/app/views/effective/applicants/billing.html.haml +14 -0
- data/app/views/effective/applicants/checkout.html.haml +6 -0
- data/app/views/effective/applicants/course_amounts.html.haml +50 -0
- data/app/views/effective/applicants/declarations.html.haml +19 -0
- data/app/views/effective/applicants/demographics.html.haml +11 -0
- data/app/views/effective/applicants/education.html.haml +27 -0
- data/app/views/effective/applicants/experience.html.haml +51 -0
- data/app/views/effective/applicants/files.html.haml +14 -0
- data/app/views/effective/applicants/references.html.haml +24 -0
- data/app/views/effective/applicants/select.html.haml +27 -0
- data/app/views/effective/applicants/start.html.haml +18 -0
- data/app/views/effective/applicants/submitted.html.haml +44 -0
- data/app/views/effective/applicants/summary.html.haml +8 -0
- data/app/views/effective/fee_payments/_content.html.haml +8 -0
- data/app/views/effective/fee_payments/_declarations.html.haml +16 -0
- data/app/views/effective/fee_payments/_demographics.html.haml +9 -0
- data/app/views/effective/fee_payments/_demographics_fields.html.haml +11 -0
- data/app/views/effective/fee_payments/_demographics_owner.html.haml +20 -0
- data/app/views/effective/fee_payments/_fee_payment.html.haml +6 -0
- data/app/views/effective/fee_payments/_layout.html.haml +3 -0
- data/app/views/effective/fee_payments/_orders.html.haml +4 -0
- data/app/views/effective/fee_payments/billing.html.haml +14 -0
- data/app/views/effective/fee_payments/checkout.html.haml +6 -0
- data/app/views/effective/fee_payments/declarations.html.haml +20 -0
- data/app/views/effective/fee_payments/demographics.html.haml +12 -0
- data/app/views/effective/fee_payments/start.html.haml +20 -0
- data/app/views/effective/fee_payments/submitted.html.haml +14 -0
- data/app/views/effective/fee_payments/summary.html.haml +8 -0
- data/app/views/effective/fees/_dashboard.html.haml +22 -0
- data/app/views/effective/fees/_fee.html.haml +37 -0
- data/app/views/effective/memberships/_dashboard.html.haml +29 -0
- data/app/views/effective/memberships/_membership.html.haml +40 -0
- data/app/views/effective/memberships_mailer/applicant_approved.liquid +15 -0
- data/app/views/effective/memberships_mailer/applicant_declined.liquid +13 -0
- data/app/views/effective/memberships_mailer/applicant_reference_notification.liquid +15 -0
- data/app/views/layouts/effective_memberships_mailer_layout.html.haml +7 -0
- data/config/effective_memberships.rb +47 -0
- data/config/routes.rb +32 -0
- data/db/migrate/01_create_effective_memberships.rb.erb +380 -0
- data/db/seeds.rb +59 -0
- data/lib/effective_memberships/engine.rb +23 -0
- data/lib/effective_memberships/version.rb +3 -0
- data/lib/effective_memberships.rb +86 -0
- data/lib/generators/effective_memberships/install_generator.rb +40 -0
- data/lib/generators/templates/effective_memberships_mailer_preview.rb +4 -0
- data/lib/tasks/effective_memberships_tasks.rake +17 -0
- metadata +377 -0
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# EffectiveMembershipsApplicant
|
|
4
|
+
#
|
|
5
|
+
# Mark your owner model with effective_memberships_applicant to get all the includes
|
|
6
|
+
|
|
7
|
+
module EffectiveMembershipsApplicant
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
module Base
|
|
11
|
+
def effective_memberships_applicant
|
|
12
|
+
include ::EffectiveMembershipsApplicant
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module ClassMethods
|
|
17
|
+
def effective_memberships_applicant?; true; end
|
|
18
|
+
|
|
19
|
+
def all_wizard_steps
|
|
20
|
+
const_get(:WIZARD_STEPS).keys
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# For effective_category_applicant_wizard_steps_collection
|
|
24
|
+
def required_wizard_steps
|
|
25
|
+
[:start, :select, :summary, :billing, :checkout, :submitted]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def categories
|
|
29
|
+
['Apply to Join', 'Apply to Reclassify']
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
included do
|
|
34
|
+
acts_as_email_form
|
|
35
|
+
acts_as_purchasable_parent
|
|
36
|
+
acts_as_tokened
|
|
37
|
+
|
|
38
|
+
acts_as_statused(
|
|
39
|
+
:draft, # Just Started
|
|
40
|
+
:submitted, # Completed wizard. Paid applicant fee.
|
|
41
|
+
:completed, # Admin has received all deliverables. The application is complete and ready for review.
|
|
42
|
+
:reviewed, # All applicant reviews completed
|
|
43
|
+
:declined, # Exit state. Application was declined.
|
|
44
|
+
:approved # Exit state. Application was approved.
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
acts_as_wizard(
|
|
48
|
+
start: 'Start',
|
|
49
|
+
select: 'Select Application Type',
|
|
50
|
+
demographics: 'Demographics',
|
|
51
|
+
education: 'Education',
|
|
52
|
+
course_amounts: 'Courses',
|
|
53
|
+
experience: 'Work Experience',
|
|
54
|
+
references: 'References',
|
|
55
|
+
files: 'Attach Files',
|
|
56
|
+
declarations: 'Declarations',
|
|
57
|
+
summary: 'Review',
|
|
58
|
+
billing: 'Billing Address',
|
|
59
|
+
checkout: 'Checkout',
|
|
60
|
+
submitted: 'Submitted'
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
log_changes(except: :wizard_steps) if respond_to?(:log_changes)
|
|
64
|
+
|
|
65
|
+
has_many_attached :applicant_files
|
|
66
|
+
|
|
67
|
+
# Declarations Step
|
|
68
|
+
attr_accessor :declare_code_of_ethics
|
|
69
|
+
attr_accessor :declare_truth
|
|
70
|
+
|
|
71
|
+
# Admin Approve Step
|
|
72
|
+
attr_accessor :approved_membership_number
|
|
73
|
+
attr_accessor :approved_membership_date
|
|
74
|
+
|
|
75
|
+
# Application Namespace
|
|
76
|
+
belongs_to :owner, polymorphic: true
|
|
77
|
+
accepts_nested_attributes_for :owner
|
|
78
|
+
|
|
79
|
+
belongs_to :category, polymorphic: true, optional: true
|
|
80
|
+
belongs_to :from_category, polymorphic: true, optional: true
|
|
81
|
+
|
|
82
|
+
has_many :applicant_reviews, -> { order(:id) }, inverse_of: :applicant, dependent: :destroy
|
|
83
|
+
accepts_nested_attributes_for :applicant_reviews, reject_if: :all_blank, allow_destroy: true
|
|
84
|
+
|
|
85
|
+
# Effective Namespace
|
|
86
|
+
has_many :applicant_courses, -> { order(:id) }, class_name: 'Effective::ApplicantCourse', inverse_of: :applicant, dependent: :destroy
|
|
87
|
+
accepts_nested_attributes_for :applicant_courses, reject_if: :all_blank, allow_destroy: true
|
|
88
|
+
|
|
89
|
+
has_many :applicant_educations, -> { order(:id) }, class_name: 'Effective::ApplicantEducation', inverse_of: :applicant, dependent: :destroy
|
|
90
|
+
accepts_nested_attributes_for :applicant_educations, reject_if: :all_blank, allow_destroy: true
|
|
91
|
+
|
|
92
|
+
has_many :applicant_experiences, -> { order(:id) }, class_name: 'Effective::ApplicantExperience', inverse_of: :applicant, dependent: :destroy
|
|
93
|
+
accepts_nested_attributes_for :applicant_experiences, reject_if: :all_blank, allow_destroy: true
|
|
94
|
+
|
|
95
|
+
has_many :applicant_references, -> { order(:id) }, class_name: 'Effective::ApplicantReference', inverse_of: :applicant, dependent: :destroy
|
|
96
|
+
accepts_nested_attributes_for :applicant_references, reject_if: :all_blank, allow_destroy: true
|
|
97
|
+
|
|
98
|
+
has_many :fees, -> { order(:id) }, as: :parent, class_name: 'Effective::Fee', dependent: :nullify
|
|
99
|
+
accepts_nested_attributes_for :fees, reject_if: :all_blank, allow_destroy: true
|
|
100
|
+
|
|
101
|
+
has_many :orders, -> { order(:id) }, as: :parent, class_name: 'Effective::Order', dependent: :nullify
|
|
102
|
+
accepts_nested_attributes_for :orders
|
|
103
|
+
|
|
104
|
+
effective_resource do
|
|
105
|
+
applicant_type :string
|
|
106
|
+
|
|
107
|
+
# Acts as Statused
|
|
108
|
+
status :string, permitted: false
|
|
109
|
+
status_steps :text, permitted: false
|
|
110
|
+
|
|
111
|
+
# Dates
|
|
112
|
+
submitted_at :datetime
|
|
113
|
+
completed_at :datetime
|
|
114
|
+
reviewed_at :datetime
|
|
115
|
+
approved_at :datetime
|
|
116
|
+
|
|
117
|
+
# Declined
|
|
118
|
+
declined_at :datetime
|
|
119
|
+
declined_reason :text
|
|
120
|
+
|
|
121
|
+
# Applicant Educations
|
|
122
|
+
applicant_educations_details :text
|
|
123
|
+
|
|
124
|
+
# Applicant Experiences
|
|
125
|
+
applicant_experiences_months :integer
|
|
126
|
+
applicant_experiences_details :text
|
|
127
|
+
|
|
128
|
+
# Additional Information
|
|
129
|
+
additional_information :text
|
|
130
|
+
|
|
131
|
+
# Acts as Wizard
|
|
132
|
+
wizard_steps :text, permitted: false
|
|
133
|
+
|
|
134
|
+
timestamps
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
scope :deep, -> { includes(:owner, :category, :from_category, :orders) }
|
|
138
|
+
scope :sorted, -> { order(:id) }
|
|
139
|
+
|
|
140
|
+
scope :in_progress, -> { where.not(status: [:approved, :declined]) }
|
|
141
|
+
scope :done, -> { where(status: [:approved, :declined]) }
|
|
142
|
+
|
|
143
|
+
# Set Apply to Join or Reclassification
|
|
144
|
+
before_validation(if: -> { new_record? && owner.present? }) do
|
|
145
|
+
self.applicant_type ||= (owner.membership.blank? ? 'Apply to Join' : 'Apply to Reclassify')
|
|
146
|
+
self.from_category ||= owner.membership&.category
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
before_validation(if: -> { current_step == :select && category_id.present? }) do
|
|
150
|
+
self.category_type ||= EffectiveMemberships.Category.name
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
before_validation(if: -> { current_step == :experience }) do
|
|
154
|
+
assign_applicant_experiences_months!
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# All Steps validations
|
|
158
|
+
validates :owner, presence: true
|
|
159
|
+
validates :from_category, presence: true, if: -> { reclassification? }
|
|
160
|
+
|
|
161
|
+
validate(if: -> { reclassification? }) do
|
|
162
|
+
errors.add(:category_id, "can't reclassify to existing category") if category_id == from_category_id
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Start Step
|
|
166
|
+
with_options(if: -> { current_step == :start && owner.present? }) do
|
|
167
|
+
validate do
|
|
168
|
+
errors.add(:base, 'may not have outstanding fees') if owner.outstanding_fee_payment_fees.present?
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Select Step
|
|
173
|
+
with_options(if: -> { current_step == :select || has_completed_step?(:select) }) do
|
|
174
|
+
validates :applicant_type, presence: true
|
|
175
|
+
validates :category, presence: true
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Applicant Educations Step
|
|
179
|
+
with_options(if: -> { current_step == :education }) do
|
|
180
|
+
validate do
|
|
181
|
+
required = min_applicant_educations()
|
|
182
|
+
existing = applicant_educations().reject(&:marked_for_destruction?).length
|
|
183
|
+
|
|
184
|
+
self.errors.add(:applicant_educations, "please include #{required} or more educations") if existing < required
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Applicant Experiences Step
|
|
189
|
+
with_options(if: -> { current_step == :experience }) do
|
|
190
|
+
validates :applicant_experiences_months, presence: true
|
|
191
|
+
|
|
192
|
+
validate do
|
|
193
|
+
if (min = min_applicant_experiences_months) > applicant_experiences_months.to_i
|
|
194
|
+
self.errors.add(:applicant_experiences_months, "must be at least #{min} months, or #{min / 12} years")
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Make sure none of the full time applicant_experience dates overlap
|
|
199
|
+
validate do
|
|
200
|
+
experiences = applicant_experiences.reject(&:marked_for_destruction?).select(&:full_time?)
|
|
201
|
+
|
|
202
|
+
experiences.find do |x|
|
|
203
|
+
(experiences - [x]).find do |y|
|
|
204
|
+
next unless (x.start_on..x.end_on).overlaps?(y.start_on..y.end_on)
|
|
205
|
+
x.errors.add(:start_on, "can't overlap when full time")
|
|
206
|
+
x.errors.add(:end_on, "can't overlap when full time")
|
|
207
|
+
y.errors.add(:start_on, "can't overlap when full time")
|
|
208
|
+
y.errors.add(:end_on, "can't overlap when full time")
|
|
209
|
+
self.errors.add(:applicant_experiences, "can't have overlaping dates for full time experiences")
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
with_options(if: -> { current_step == :course_amounts }) do
|
|
216
|
+
validate do
|
|
217
|
+
required = min_applicant_courses()
|
|
218
|
+
existing = applicant_courses().reject(&:marked_for_destruction?).length
|
|
219
|
+
|
|
220
|
+
self.errors.add(:applicant_courses, "please include #{required} or more courses") if existing < required
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Applicant References Step
|
|
225
|
+
with_options(if: -> { current_step == :references }) do
|
|
226
|
+
validate do
|
|
227
|
+
required = min_applicant_references()
|
|
228
|
+
existing = applicant_references().reject(&:marked_for_destruction?).length
|
|
229
|
+
|
|
230
|
+
self.errors.add(:applicant_references, "please include #{required} or more references") if existing < required
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Applicant Files Step
|
|
235
|
+
with_options(if: -> { current_step == :files }) do
|
|
236
|
+
validate do
|
|
237
|
+
required = min_applicant_files()
|
|
238
|
+
existing = applicant_files().length
|
|
239
|
+
|
|
240
|
+
self.errors.add(:applicant_files, "please include #{required} or more files") if existing < required
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Declarations Step
|
|
245
|
+
with_options(if: -> { current_step == :declarations }) do
|
|
246
|
+
validates :declare_code_of_ethics, acceptance: true
|
|
247
|
+
validates :declare_truth, acceptance: true
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Admin Approve
|
|
251
|
+
validate(if: -> { approved_membership_date.present? }) do
|
|
252
|
+
if approved_membership_date.to_date > Time.zone.now.to_date
|
|
253
|
+
errors.add(:approved_membership_date, "can't be in the future")
|
|
254
|
+
elsif approved_membership_date.to_date < (Time.zone.now - 1.year).to_date
|
|
255
|
+
errors.add(:approved_membership_date, "can't be more than 1 year in the past")
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Admin Decline
|
|
260
|
+
validates :declined_reason, presence: true, if: -> { declined? }
|
|
261
|
+
|
|
262
|
+
# These two try completed and try reviewed
|
|
263
|
+
before_save(if: -> { submitted? }) { complete! }
|
|
264
|
+
before_save(if: -> { completed? }) { review! }
|
|
265
|
+
|
|
266
|
+
# Clear required steps memoization
|
|
267
|
+
after_save { @_required_steps = nil }
|
|
268
|
+
|
|
269
|
+
# This required_steps is defined inside the included do .. end block so it overrides the acts_as_wizard one.
|
|
270
|
+
def required_steps
|
|
271
|
+
return self.class.test_required_steps if Rails.env.test? && self.class.test_required_steps.present?
|
|
272
|
+
|
|
273
|
+
@_required_steps ||= begin
|
|
274
|
+
wizard_steps = self.class.all_wizard_steps
|
|
275
|
+
required_steps = self.class.required_wizard_steps
|
|
276
|
+
|
|
277
|
+
applicant_steps = Array(category&.applicant_wizard_steps)
|
|
278
|
+
|
|
279
|
+
wizard_steps.select do |step|
|
|
280
|
+
required_steps.include?(step) || category.blank? || applicant_steps.include?(step)
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
after_purchase do |_|
|
|
286
|
+
raise('expected submit_order to be purchased') unless submit_order&.purchased?
|
|
287
|
+
submit_purchased!
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Instance Methods
|
|
292
|
+
def to_s
|
|
293
|
+
if category.present? && category.present?
|
|
294
|
+
[
|
|
295
|
+
owner.to_s,
|
|
296
|
+
'-',
|
|
297
|
+
category,
|
|
298
|
+
'for',
|
|
299
|
+
category,
|
|
300
|
+
("from #{from_category}" if reclassification?)
|
|
301
|
+
].compact.join(' ')
|
|
302
|
+
else
|
|
303
|
+
'New Applicant'
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def apply_to_join?
|
|
308
|
+
applicant_type == 'Apply to Join'
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def reclassification?
|
|
312
|
+
applicant_type == 'Apply to Reclassify'
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def owner_label
|
|
316
|
+
owner_type.to_s.split('::').last
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def in_progress?
|
|
320
|
+
!approved? && !declined?
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def done?
|
|
324
|
+
approved? || declined?
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def can_visit_step?(step)
|
|
328
|
+
can_revisit_completed_steps(step)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def summary
|
|
332
|
+
case status_was
|
|
333
|
+
when 'draft'
|
|
334
|
+
"Applicant has not yet completed the #{category} wizard steps or paid to submit this application. This application will transition to 'submitted' after payment has been collected."
|
|
335
|
+
when 'submitted'
|
|
336
|
+
summary = "Application has been purchased and submitted. The following tasks must be done before this application will transition to 'completed':"
|
|
337
|
+
items = completed_requirements.map { |item, done| "<li>#{item}: #{done ? 'Complete' : 'Incomplete'}</li>" }.join
|
|
338
|
+
"<p>#{summary}</p><ul>#{items}</ul>"
|
|
339
|
+
when 'completed'
|
|
340
|
+
if applicant_reviews_required?
|
|
341
|
+
"All required materials have been provided. This application will transition to 'reviewed' after all reviewers have voted."
|
|
342
|
+
else
|
|
343
|
+
"This application has been completed and is now ready for an admin to approve or decline it. If approved, prorated fees will be generated."
|
|
344
|
+
end
|
|
345
|
+
when 'reviewed'
|
|
346
|
+
"This application has been reviewed and is now ready for an admin to approve or decline it. If approved, prorated fees will be generated."
|
|
347
|
+
when 'approved'
|
|
348
|
+
"The application has been approved! All done!"
|
|
349
|
+
when 'declined'
|
|
350
|
+
"This application has been declined."
|
|
351
|
+
else
|
|
352
|
+
raise("unexpected status #{status}")
|
|
353
|
+
end.html_safe
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
# Used by the select step
|
|
357
|
+
def can_apply_categories_collection
|
|
358
|
+
categories = EffectiveMemberships.Category.sorted.can_apply
|
|
359
|
+
|
|
360
|
+
if owner.blank? || owner.membership.blank?
|
|
361
|
+
return categories.where(can_apply_new: true)
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
category_ids = owner.membership.category_ids
|
|
365
|
+
|
|
366
|
+
categories.select do |cat|
|
|
367
|
+
cat.can_apply_existing? ||
|
|
368
|
+
(cat.can_apply_restricted? && (category_ids & cat.can_apply_restricted_ids).present?)
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def select!
|
|
373
|
+
raise('cannot select a submitted applicant') if was_submitted?
|
|
374
|
+
raise('cannot select a purchased applicant') if orders.any? { |order| order.purchased? }
|
|
375
|
+
|
|
376
|
+
# Reset the progress so far. They have to click through screens again.
|
|
377
|
+
assign_attributes(wizard_steps: wizard_steps.slice(:start, :select))
|
|
378
|
+
|
|
379
|
+
# Delete any fees and orders. Keep all other data.
|
|
380
|
+
submit_fees.each { |fee| fee.mark_for_destruction }
|
|
381
|
+
submit_order.mark_for_destruction if submit_order
|
|
382
|
+
|
|
383
|
+
save!
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# Educations Step
|
|
387
|
+
def min_applicant_educations
|
|
388
|
+
category&.min_applicant_educations.to_i
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
# Courses Amounts step
|
|
392
|
+
def min_applicant_courses
|
|
393
|
+
category&.min_applicant_courses.to_i
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def applicant_course_areas_collection
|
|
397
|
+
Effective::ApplicantCourseArea.deep.sorted
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
def applicant_course_names_collection(applicant_course_area:)
|
|
401
|
+
applicant_course_area.applicant_course_names
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def applicant_course(applicant_course_name: nil)
|
|
405
|
+
applicant_courses.find { |ac| ac.applicant_course_name_id == applicant_course_name.id } ||
|
|
406
|
+
applicant_courses.build(applicant_course_name: applicant_course_name, applicant_course_area: applicant_course_name.applicant_course_area)
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def applicant_course_area_sum(applicant_course_area:)
|
|
410
|
+
applicant_courses.select { |ac| ac.applicant_course_area_id == applicant_course_area.id }.sum { |ac| ac.amount.to_i }
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
def applicant_courses_sum
|
|
414
|
+
applicant_courses.sum { |ac| ac.amount.to_i }
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
# Work Experiences Step
|
|
418
|
+
def min_applicant_experiences_months
|
|
419
|
+
category&.min_applicant_experiences_months.to_i
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
# References Step
|
|
423
|
+
def min_applicant_references
|
|
424
|
+
category&.min_applicant_references.to_i
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
# Files Step
|
|
428
|
+
def min_applicant_files
|
|
429
|
+
category&.min_applicant_files.to_i
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
# All Fees and Orders
|
|
433
|
+
def submit_fees
|
|
434
|
+
fees.select { |fee| fee.applicant_submit_fee? }
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
def submit_order
|
|
438
|
+
orders.find { |order| order.purchasables.any?(&:applicant_submit_fee?) }
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
def find_or_build_submit_fees
|
|
442
|
+
return submit_fees if submit_fees.present?
|
|
443
|
+
|
|
444
|
+
fees.build(
|
|
445
|
+
owner: owner,
|
|
446
|
+
fee_type: 'Applicant',
|
|
447
|
+
category: category,
|
|
448
|
+
price: category.applicant_fee
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
submit_fees
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
def find_or_build_submit_order
|
|
455
|
+
order = submit_order || orders.build(user: owner)
|
|
456
|
+
|
|
457
|
+
# Adds fees, but does not overwrite any existing price.
|
|
458
|
+
submit_fees.each do |fee|
|
|
459
|
+
order.add(fee) unless order.purchasables.include?(fee)
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
# From Billing Step
|
|
463
|
+
order.billing_address = owner.billing_address if owner.billing_address.present?
|
|
464
|
+
|
|
465
|
+
order
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
# Should be indempotent.
|
|
469
|
+
def build_submit_fees_and_order
|
|
470
|
+
return false if was_submitted?
|
|
471
|
+
|
|
472
|
+
fees = find_or_build_submit_fees()
|
|
473
|
+
raise('already has purchased submit fees') if fees.any? { |fee| fee.purchased? }
|
|
474
|
+
|
|
475
|
+
order = find_or_build_submit_order()
|
|
476
|
+
raise('already has purchased submit order') if order.purchased?
|
|
477
|
+
|
|
478
|
+
true
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
# Owner clicks on the Billing step. Next step is Checkout
|
|
482
|
+
def billing!
|
|
483
|
+
ready!
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
# Ready to check out
|
|
487
|
+
def ready!
|
|
488
|
+
build_submit_fees_and_order
|
|
489
|
+
save!
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
# Called automatically via after_purchase hook above
|
|
493
|
+
def submit_purchased!
|
|
494
|
+
return false if was_submitted?
|
|
495
|
+
|
|
496
|
+
wizard_steps[:checkout] = Time.zone.now
|
|
497
|
+
submit!
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
# Draft -> Submitted requirements
|
|
501
|
+
def submit!
|
|
502
|
+
raise('already submitted') if was_submitted?
|
|
503
|
+
raise('expected a purchased order') unless submit_order&.purchased?
|
|
504
|
+
|
|
505
|
+
after_commit do
|
|
506
|
+
applicant_references.each { |reference| reference.notify! if reference.submitted? }
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
wizard_steps[:checkout] ||= Time.zone.now
|
|
510
|
+
wizard_steps[:submitted] = Time.zone.now
|
|
511
|
+
submitted!
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
# Submitted -> Completed requirements
|
|
515
|
+
|
|
516
|
+
def applicant_references_required?
|
|
517
|
+
min_applicant_references > 0
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
# When an application is submitted, these must be done to go to completed
|
|
521
|
+
def completed_requirements
|
|
522
|
+
{
|
|
523
|
+
'Applicant References' => (!applicant_references_required? || applicant_references.count(&:completed?) >= min_applicant_references)
|
|
524
|
+
}
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
def complete!
|
|
528
|
+
return false unless submitted? && completed_requirements.values.all?
|
|
529
|
+
# Could send registrar an email here saying this applicant is ready to review
|
|
530
|
+
completed!
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
# Completed -> Reviewed requirements
|
|
534
|
+
def applicant_reviews_required?
|
|
535
|
+
(min_applicant_reviews > 0 || applicant_reviews.present?)
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
def min_applicant_reviews
|
|
539
|
+
category&.min_applicant_reviews.to_i
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
# When an application is completed, these must be done to go to reviewed
|
|
543
|
+
def reviewed_requirements
|
|
544
|
+
{
|
|
545
|
+
'Applicant Reviews' => (!applicant_reviews_required? || applicant_reviews.count(&:completed?) >= min_applicant_reviews)
|
|
546
|
+
}
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
def review!
|
|
550
|
+
return false unless completed? && reviewed_requirements.values.all?
|
|
551
|
+
# Could send registrar an email here saying this applicant is ready to approve
|
|
552
|
+
reviewed!
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
# Admin approves an applicant. Registers the owner. Sends an email.
|
|
556
|
+
def approve!
|
|
557
|
+
raise('already approved') if was_approved?
|
|
558
|
+
raise('applicant must have been submitted to approve!') unless was_submitted?
|
|
559
|
+
|
|
560
|
+
# Complete the wizard step. Just incase this is run out of order.
|
|
561
|
+
wizard_steps[:checkout] ||= Time.zone.now
|
|
562
|
+
wizard_steps[:submitted] ||= Time.zone.now
|
|
563
|
+
approved!
|
|
564
|
+
|
|
565
|
+
if apply_to_join?
|
|
566
|
+
EffectiveMemberships.Registrar.register!(
|
|
567
|
+
owner,
|
|
568
|
+
to: category,
|
|
569
|
+
date: approved_membership_date.presence, # Set by the Admin Process form, or nil
|
|
570
|
+
number: approved_membership_number.presence # Set by the Admin Process form, or nil
|
|
571
|
+
)
|
|
572
|
+
elsif reclassification?
|
|
573
|
+
EffectiveMemberships.Registrar.reclassify!(owner, to: category)
|
|
574
|
+
else
|
|
575
|
+
raise('unsupported approval applicant_type')
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
after_commit { send_email(:applicant_approved) }
|
|
579
|
+
|
|
580
|
+
save!
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
# Admin approves an applicant. Registers the owner. Sends an email.
|
|
584
|
+
def decline!
|
|
585
|
+
raise('already declined') if was_declined?
|
|
586
|
+
raise('previously approved') if was_approved?
|
|
587
|
+
raise('applicant must have been submitted to decline!') unless was_submitted?
|
|
588
|
+
|
|
589
|
+
# Complete the wizard step. Just incase this is run out of order.
|
|
590
|
+
wizard_steps[:checkout] ||= Time.zone.now
|
|
591
|
+
wizard_steps[:submitted] ||= Time.zone.now
|
|
592
|
+
declined!
|
|
593
|
+
|
|
594
|
+
after_commit { send_email(:applicant_declined) }
|
|
595
|
+
|
|
596
|
+
save!
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
private
|
|
600
|
+
|
|
601
|
+
def assign_applicant_experiences_months!
|
|
602
|
+
existing = applicant_experiences.reject(&:marked_for_destruction?)
|
|
603
|
+
existing.each { |ae| ae.assign_months! }
|
|
604
|
+
|
|
605
|
+
self.applicant_experiences_months = existing.sum { |ae| ae.months.to_i }
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
def send_email(email)
|
|
609
|
+
EffectiveMemberships.send_email(email, self, email_form_params) unless email_form_skip?
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
end
|