effective_memberships 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +114 -0
  4. data/Rakefile +18 -0
  5. data/app/assets/config/effective_memberships_manifest.js +3 -0
  6. data/app/assets/javascripts/effective_memberships/applicant_courses.js +29 -0
  7. data/app/assets/javascripts/effective_memberships/applicant_experiences.js +38 -0
  8. data/app/assets/javascripts/effective_memberships/base.js +0 -0
  9. data/app/assets/javascripts/effective_memberships.js +1 -0
  10. data/app/assets/stylesheets/effective_memberships/base.scss +5 -0
  11. data/app/assets/stylesheets/effective_memberships.scss +1 -0
  12. data/app/controllers/admin/applicant_course_areas_controller.rb +15 -0
  13. data/app/controllers/admin/applicant_course_names_controller.rb +15 -0
  14. data/app/controllers/admin/applicants_controller.rb +33 -0
  15. data/app/controllers/admin/categories_controller.rb +19 -0
  16. data/app/controllers/admin/fees_controller.rb +18 -0
  17. data/app/controllers/admin/registrar_actions_controller.rb +49 -0
  18. data/app/controllers/effective/applicant_references_controller.rb +37 -0
  19. data/app/controllers/effective/applicants_controller.rb +34 -0
  20. data/app/controllers/effective/fee_payments_controller.rb +29 -0
  21. data/app/datatables/admin/effective_applicant_course_areas_datatable.rb +17 -0
  22. data/app/datatables/admin/effective_applicant_course_names_datatable.rb +17 -0
  23. data/app/datatables/admin/effective_applicants_datatable.rb +55 -0
  24. data/app/datatables/admin/effective_categories_datatable.rb +23 -0
  25. data/app/datatables/admin/effective_fees_datatable.rb +69 -0
  26. data/app/datatables/admin/effective_membership_histories_datatable.rb +35 -0
  27. data/app/datatables/effective_applicant_courses_datatable.rb +30 -0
  28. data/app/datatables/effective_applicant_educations_datatable.rb +22 -0
  29. data/app/datatables/effective_applicant_experiences_datatable.rb +42 -0
  30. data/app/datatables/effective_applicant_references_datatable.rb +34 -0
  31. data/app/datatables/effective_applicants_datatable.rb +34 -0
  32. data/app/helpers/effective_memberships_helper.rb +15 -0
  33. data/app/mailers/effective/memberships_mailer.rb +84 -0
  34. data/app/models/concerns/effective_memberships_applicant.rb +612 -0
  35. data/app/models/concerns/effective_memberships_applicant_review.rb +149 -0
  36. data/app/models/concerns/effective_memberships_category.rb +196 -0
  37. data/app/models/concerns/effective_memberships_fee_payment.rb +229 -0
  38. data/app/models/concerns/effective_memberships_owner.rb +263 -0
  39. data/app/models/concerns/effective_memberships_registrar.rb +300 -0
  40. data/app/models/effective/applicant.rb +7 -0
  41. data/app/models/effective/applicant_course.rb +40 -0
  42. data/app/models/effective/applicant_course_area.rb +31 -0
  43. data/app/models/effective/applicant_course_name.rb +29 -0
  44. data/app/models/effective/applicant_education.rb +36 -0
  45. data/app/models/effective/applicant_experience.rb +79 -0
  46. data/app/models/effective/applicant_reference.rb +81 -0
  47. data/app/models/effective/applicant_review.rb +7 -0
  48. data/app/models/effective/category.rb +7 -0
  49. data/app/models/effective/fee.rb +142 -0
  50. data/app/models/effective/fee_payment.rb +7 -0
  51. data/app/models/effective/membership.rb +119 -0
  52. data/app/models/effective/membership_category.rb +11 -0
  53. data/app/models/effective/membership_history.rb +40 -0
  54. data/app/models/effective/registrar.rb +19 -0
  55. data/app/models/effective/registrar_action.rb +97 -0
  56. data/app/views/admin/applicant_course_areas/_form.html.haml +8 -0
  57. data/app/views/admin/applicant_course_areas/_form_applicant_course_area.html.haml +8 -0
  58. data/app/views/admin/applicant_course_areas/index.html.haml +8 -0
  59. data/app/views/admin/applicant_course_name/_form.html.haml +9 -0
  60. data/app/views/admin/applicants/_form.html.haml +38 -0
  61. data/app/views/admin/applicants/_form_approve.html.haml +34 -0
  62. data/app/views/admin/applicants/_form_decline.html.haml +11 -0
  63. data/app/views/admin/applicants/_form_process.html.haml +18 -0
  64. data/app/views/admin/applicants/_status.html.haml +133 -0
  65. data/app/views/admin/categories/_form.html.haml +14 -0
  66. data/app/views/admin/categories/_form_applicant.html.haml +12 -0
  67. data/app/views/admin/categories/_form_applicant_content.html.haml +19 -0
  68. data/app/views/admin/categories/_form_applicant_eligibility.html.haml +13 -0
  69. data/app/views/admin/categories/_form_applicant_fees.html.haml +29 -0
  70. data/app/views/admin/categories/_form_applicant_steps.html.haml +42 -0
  71. data/app/views/admin/categories/_form_category.html.haml +10 -0
  72. data/app/views/admin/categories/_form_fee_payment.html.haml +9 -0
  73. data/app/views/admin/categories/_form_fee_payment_content.html.haml +19 -0
  74. data/app/views/admin/categories/_form_fee_payment_steps.html.haml +9 -0
  75. data/app/views/admin/categories/_form_renewals.html.haml +32 -0
  76. data/app/views/admin/fees/_fee.html.haml +1 -0
  77. data/app/views/admin/fees/_form.html.haml +14 -0
  78. data/app/views/admin/memberships/_status.html.haml +6 -0
  79. data/app/views/admin/registrar_actions/_form.html.haml +10 -0
  80. data/app/views/admin/registrar_actions/_form_bad_standing.html.haml +43 -0
  81. data/app/views/admin/registrar_actions/_form_fees_paid.html.haml +30 -0
  82. data/app/views/admin/registrar_actions/_form_reclassify.html.haml +43 -0
  83. data/app/views/admin/registrar_actions/_form_register.html.haml +44 -0
  84. data/app/views/admin/registrar_actions/_form_remove.html.haml +17 -0
  85. data/app/views/effective/applicant_references/_applicant_reference.html.haml +51 -0
  86. data/app/views/effective/applicant_references/_datatable_actions.html.haml +4 -0
  87. data/app/views/effective/applicant_references/_form.html.haml +18 -0
  88. data/app/views/effective/applicant_references/_form_declaration.html.haml +37 -0
  89. data/app/views/effective/applicant_references/complete.html.haml +3 -0
  90. data/app/views/effective/applicant_references/edit.html.haml +8 -0
  91. data/app/views/effective/applicants/_applicant.html.haml +6 -0
  92. data/app/views/effective/applicants/_content.html.haml +10 -0
  93. data/app/views/effective/applicants/_course_amounts.html.haml +19 -0
  94. data/app/views/effective/applicants/_dashboard.html.haml +19 -0
  95. data/app/views/effective/applicants/_declarations.html.haml +16 -0
  96. data/app/views/effective/applicants/_demographics.html.haml +9 -0
  97. data/app/views/effective/applicants/_demographics_fields.html.haml +11 -0
  98. data/app/views/effective/applicants/_demographics_owner.html.haml +20 -0
  99. data/app/views/effective/applicants/_education.html.haml +14 -0
  100. data/app/views/effective/applicants/_experience.html.haml +28 -0
  101. data/app/views/effective/applicants/_files.html.haml +27 -0
  102. data/app/views/effective/applicants/_layout.html.haml +3 -0
  103. data/app/views/effective/applicants/_orders.html.haml +4 -0
  104. data/app/views/effective/applicants/_references.html.haml +15 -0
  105. data/app/views/effective/applicants/_summary.html.haml +40 -0
  106. data/app/views/effective/applicants/billing.html.haml +14 -0
  107. data/app/views/effective/applicants/checkout.html.haml +6 -0
  108. data/app/views/effective/applicants/course_amounts.html.haml +50 -0
  109. data/app/views/effective/applicants/declarations.html.haml +19 -0
  110. data/app/views/effective/applicants/demographics.html.haml +11 -0
  111. data/app/views/effective/applicants/education.html.haml +27 -0
  112. data/app/views/effective/applicants/experience.html.haml +51 -0
  113. data/app/views/effective/applicants/files.html.haml +14 -0
  114. data/app/views/effective/applicants/references.html.haml +24 -0
  115. data/app/views/effective/applicants/select.html.haml +27 -0
  116. data/app/views/effective/applicants/start.html.haml +18 -0
  117. data/app/views/effective/applicants/submitted.html.haml +44 -0
  118. data/app/views/effective/applicants/summary.html.haml +8 -0
  119. data/app/views/effective/fee_payments/_content.html.haml +8 -0
  120. data/app/views/effective/fee_payments/_declarations.html.haml +16 -0
  121. data/app/views/effective/fee_payments/_demographics.html.haml +9 -0
  122. data/app/views/effective/fee_payments/_demographics_fields.html.haml +11 -0
  123. data/app/views/effective/fee_payments/_demographics_owner.html.haml +20 -0
  124. data/app/views/effective/fee_payments/_fee_payment.html.haml +6 -0
  125. data/app/views/effective/fee_payments/_layout.html.haml +3 -0
  126. data/app/views/effective/fee_payments/_orders.html.haml +4 -0
  127. data/app/views/effective/fee_payments/billing.html.haml +14 -0
  128. data/app/views/effective/fee_payments/checkout.html.haml +6 -0
  129. data/app/views/effective/fee_payments/declarations.html.haml +20 -0
  130. data/app/views/effective/fee_payments/demographics.html.haml +12 -0
  131. data/app/views/effective/fee_payments/start.html.haml +20 -0
  132. data/app/views/effective/fee_payments/submitted.html.haml +14 -0
  133. data/app/views/effective/fee_payments/summary.html.haml +8 -0
  134. data/app/views/effective/fees/_dashboard.html.haml +22 -0
  135. data/app/views/effective/fees/_fee.html.haml +37 -0
  136. data/app/views/effective/memberships/_dashboard.html.haml +29 -0
  137. data/app/views/effective/memberships/_membership.html.haml +40 -0
  138. data/app/views/effective/memberships_mailer/applicant_approved.liquid +15 -0
  139. data/app/views/effective/memberships_mailer/applicant_declined.liquid +13 -0
  140. data/app/views/effective/memberships_mailer/applicant_reference_notification.liquid +15 -0
  141. data/app/views/layouts/effective_memberships_mailer_layout.html.haml +7 -0
  142. data/config/effective_memberships.rb +47 -0
  143. data/config/routes.rb +32 -0
  144. data/db/migrate/01_create_effective_memberships.rb.erb +380 -0
  145. data/db/seeds.rb +59 -0
  146. data/lib/effective_memberships/engine.rb +23 -0
  147. data/lib/effective_memberships/version.rb +3 -0
  148. data/lib/effective_memberships.rb +86 -0
  149. data/lib/generators/effective_memberships/install_generator.rb +40 -0
  150. data/lib/generators/templates/effective_memberships_mailer_preview.rb +4 -0
  151. data/lib/tasks/effective_memberships_tasks.rake +17 -0
  152. metadata +377 -0
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ # EffectiveMembershipsApplicantReview
4
+ #
5
+ # Mark your category model with effective_memberships_applicant_review to get all the includes
6
+
7
+ module EffectiveMembershipsApplicantReview
8
+ extend ActiveSupport::Concern
9
+
10
+ module Base
11
+ def effective_memberships_applicant_review
12
+ include ::EffectiveMembershipsApplicantReview
13
+ end
14
+ end
15
+
16
+ module ClassMethods
17
+ def effective_memberships_applicant_review?; 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, :recommendation, :submitted]
26
+ end
27
+
28
+ def recommendations
29
+ ['Accept', 'Reject']
30
+ end
31
+
32
+ end
33
+
34
+ included do
35
+ log_changes if respond_to?(:log_changes)
36
+
37
+ acts_as_tokened
38
+
39
+ acts_as_statused(
40
+ :draft, # Just Started
41
+ :conflicted, # Conflict of Interest
42
+ :accepted, # Accepted
43
+ :rejected # Rejected
44
+ )
45
+
46
+ acts_as_wizard(
47
+ start: 'Start',
48
+ conflict: 'Conflict of Interest',
49
+ education: 'Education',
50
+ course_amounts: 'Courses',
51
+ experience: 'Work Experience',
52
+ references: 'References',
53
+ files: 'Attach Files',
54
+ declarations: 'Declarations',
55
+ recommendation: 'Recommendation',
56
+ submitted: 'Submitted'
57
+ )
58
+
59
+ belongs_to :applicant
60
+ belongs_to :reviewer, polymorphic: true
61
+
62
+ effective_resource do
63
+ submitted_at :datetime
64
+ recommendation :string
65
+
66
+ comments :text # Rolling comments
67
+
68
+ # Conflict Step
69
+ conflict_of_interest :boolean
70
+
71
+ # Education Step
72
+ education_accepted :boolean
73
+
74
+ # Course Amounts
75
+ course_amounts_accepted :boolean
76
+
77
+ # Courses
78
+ courses_accepted :boolean
79
+
80
+ # Experience Step
81
+ experience_accepted :boolean
82
+
83
+ # References Step
84
+ references_accepted :boolean
85
+
86
+ # References Step
87
+ files_accepted :boolean
88
+
89
+ timestamps
90
+ end
91
+
92
+ scope :deep, -> { includes(:reviewer, applicant: :owner) }
93
+
94
+ with_options(if: -> { current_step == :conflict }) do
95
+ validates :conflict_of_interest, inclusion: { in: [true, false] }
96
+ validates :comments, presence: true, if: -> { conflict_of_interest? }
97
+ end
98
+
99
+ validates :recommendation, absence: true, if: -> { done? && conflict_of_interest? }
100
+
101
+ after_commit(on: :create, if: -> { applicant.completed? }) { notify! }
102
+
103
+ def to_s
104
+ 'applicant review'
105
+ end
106
+
107
+ def in_progress?
108
+ draft?
109
+ end
110
+
111
+ def done?
112
+ !draft?
113
+ end
114
+
115
+ def conflict_of_interest!
116
+ after_commit { send_email(:applicant_review_conflict_of_interest) }
117
+
118
+ update!(conflict_of_interest: true, recommendation: nil)
119
+ conflicted!
120
+
121
+ applicant.save!
122
+ end
123
+
124
+ def accept!
125
+ after_commit { send_email(:applicant_review_completed) }
126
+
127
+ assign_attributes(recommendation: 'Accept')
128
+ accepted!
129
+
130
+ applicant.save!
131
+ end
132
+
133
+ def reject!
134
+ after_commit { send_email(:applicant_review_completed) }
135
+
136
+ assign_attributes(recommendation: 'Reject')
137
+ rejected!
138
+
139
+ applicant.save!
140
+ end
141
+
142
+ private
143
+
144
+ def send_email(email)
145
+ EffectiveMemberships.send_email(email, self, email_form_params) unless email_form_skip?
146
+ end
147
+
148
+ end
149
+ end
@@ -0,0 +1,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ # EffectiveMembershipsCategory
4
+ #
5
+ # Mark your category model with effective_memberships_category to get all the includes
6
+
7
+ module EffectiveMembershipsCategory
8
+ extend ActiveSupport::Concern
9
+
10
+ module Base
11
+ def effective_memberships_category
12
+ include ::EffectiveMembershipsCategory
13
+ end
14
+ end
15
+
16
+ module ClassMethods
17
+ def effective_memberships_category?; true; end
18
+
19
+ def categories
20
+ []
21
+ end
22
+ end
23
+
24
+ included do
25
+ log_changes(except: :memberships) if respond_to?(:log_changes)
26
+
27
+ # rich_text_body - Used by the select step
28
+ has_many_rich_texts
29
+
30
+ # rich_text_applicant_all_steps_content
31
+ # rich_text_applicant_start_content
32
+ # rich_text_applicant_select_content
33
+ # rich_text_applicant_select_content
34
+
35
+ has_many :membership_categories, class_name: 'Effective::MembershipCategory', as: :category
36
+
37
+ effective_resource do
38
+ title :string
39
+ category :string
40
+ position :integer
41
+
42
+ # Applicants
43
+ can_apply_new :boolean
44
+ can_apply_existing :boolean
45
+ can_apply_restricted :boolean
46
+ can_apply_restricted_ids :text
47
+
48
+ applicant_fee :integer
49
+ applicant_wizard_steps :text
50
+
51
+ min_applicant_educations :integer
52
+ min_applicant_experiences_months :integer
53
+ min_applicant_references :integer
54
+ min_applicant_courses :integer
55
+ min_applicant_files :integer
56
+
57
+ # Applicant Reviews
58
+ min_applicant_reviews :integer
59
+ applicant_review_wizard_steps :text
60
+
61
+ # Prorated Fees
62
+ prorated_jan :integer
63
+ prorated_feb :integer
64
+ prorated_mar :integer
65
+ prorated_apr :integer
66
+ prorated_may :integer
67
+ prorated_jun :integer
68
+ prorated_jul :integer
69
+ prorated_aug :integer
70
+ prorated_sep :integer
71
+ prorated_oct :integer
72
+ prorated_nov :integer
73
+ prorated_dec :integer
74
+
75
+ # Fee Payments
76
+ fee_payment_wizard_steps :text
77
+
78
+ # Renewals
79
+ create_renewal_fees :boolean
80
+ renewal_fee :integer
81
+
82
+ create_late_fees :boolean
83
+ late_fee :integer
84
+
85
+ create_bad_standing :boolean
86
+
87
+ # Pricing
88
+ qb_item_name :string
89
+ tax_exempt :boolean
90
+
91
+ timestamps
92
+ end
93
+
94
+ serialize :can_apply_restricted_ids, Array
95
+ serialize :applicant_wizard_steps, Array
96
+ serialize :fee_payment_wizard_steps, Array
97
+
98
+ scope :deep, -> { includes(:rich_texts) }
99
+ scope :sorted, -> { order(:position) }
100
+
101
+ scope :can_apply, -> {
102
+ where(can_apply_new: true)
103
+ .or(where(can_apply_existing: true))
104
+ .or(where(can_apply_restricted: true))
105
+ }
106
+
107
+ # For the create_fees rake task
108
+ scope :create_renewal_fees, -> { where(create_renewal_fees: true) }
109
+ scope :create_late_fees, -> { where(create_late_fees: true) }
110
+ scope :create_bad_standing, -> { where(create_bad_standing: true) }
111
+
112
+ validates :title, presence: true, uniqueness: true
113
+ validates :position, presence: true
114
+
115
+ after_initialize(if: -> { new_record? }) do
116
+ self.applicant_wizard_steps = EffectiveMemberships.Applicant.all_wizard_steps
117
+ self.applicant_review_wizard_steps = EffectiveMemberships.ApplicantReview.all_wizard_steps
118
+ self.fee_payment_wizard_steps = EffectiveMemberships.FeePayment.all_wizard_steps
119
+ end
120
+
121
+ before_validation do
122
+ self.position ||= (self.class.pluck(:position).compact.max || -1) + 1
123
+ end
124
+
125
+ with_options(if: -> { can_apply? }) do
126
+ validates :can_apply_restricted_ids, presence: true, if: -> { can_apply_restricted? }
127
+
128
+ validates :applicant_fee, presence: true, numericality: { greater_than_or_equal_to: 0 }
129
+ validates :prorated_jan, presence: true, numericality: { greater_than_or_equal_to: 0 }
130
+ validates :prorated_feb, presence: true, numericality: { greater_than_or_equal_to: 0 }
131
+ validates :prorated_mar, presence: true, numericality: { greater_than_or_equal_to: 0 }
132
+ validates :prorated_apr, presence: true, numericality: { greater_than_or_equal_to: 0 }
133
+ validates :prorated_may, presence: true, numericality: { greater_than_or_equal_to: 0 }
134
+ validates :prorated_jun, presence: true, numericality: { greater_than_or_equal_to: 0 }
135
+ validates :prorated_jul, presence: true, numericality: { greater_than_or_equal_to: 0 }
136
+ validates :prorated_aug, presence: true, numericality: { greater_than_or_equal_to: 0 }
137
+ validates :prorated_sep, presence: true, numericality: { greater_than_or_equal_to: 0 }
138
+ validates :prorated_oct, presence: true, numericality: { greater_than_or_equal_to: 0 }
139
+ validates :prorated_nov, presence: true, numericality: { greater_than_or_equal_to: 0 }
140
+ validates :prorated_dec, presence: true, numericality: { greater_than_or_equal_to: 0 }
141
+ end
142
+ end
143
+
144
+ # Instance Methods
145
+
146
+ def to_s
147
+ title.presence || 'New Membership Category'
148
+ end
149
+
150
+ def can_apply?
151
+ can_apply_new? || can_apply_existing? || can_apply_restricted?
152
+ end
153
+
154
+ def prorated_fee(date:)
155
+ send("prorated_#{date.strftime('%b').downcase}").to_i
156
+ end
157
+
158
+ def discount_fee(date:)
159
+ 0 - prorated_fee(date: date)
160
+ end
161
+
162
+ def can_apply_restricted_ids
163
+ Array(self[:can_apply_restricted_ids]) - [nil, '']
164
+ end
165
+
166
+ def applicant_wizard_steps
167
+ (Array(self[:applicant_wizard_steps]) - [nil, '']).map(&:to_sym)
168
+ end
169
+
170
+ def fee_payment_wizard_steps
171
+ (Array(self[:fee_payment_wizard_steps]) - [nil, '']).map(&:to_sym)
172
+ end
173
+
174
+ def applicant_review_wizard_steps
175
+ (Array(self[:applicant_review_wizard_steps]) - [nil, '']).map(&:to_sym)
176
+ end
177
+
178
+ def applicant_wizard_steps_collection
179
+ wizard_steps = EffectiveMemberships.Applicant.const_get(:WIZARD_STEPS)
180
+ required_steps = EffectiveMemberships.Applicant.required_wizard_steps
181
+
182
+ wizard_steps.map do |step, title|
183
+ [title, step, 'disabled' => required_steps.include?(step)]
184
+ end
185
+ end
186
+
187
+ def fee_payment_wizard_steps_collection
188
+ wizard_steps = EffectiveMemberships.FeePayment.const_get(:WIZARD_STEPS)
189
+ required_steps = EffectiveMemberships.FeePayment.required_wizard_steps
190
+
191
+ wizard_steps.map do |step, title|
192
+ [title, step, 'disabled' => required_steps.include?(step)]
193
+ end
194
+ end
195
+
196
+ end
@@ -0,0 +1,229 @@
1
+ # frozen_string_literal: true
2
+
3
+ # EffectiveMembershipsFeePayment
4
+ #
5
+ # Mark your model with effective_memberships_fee_payment to get all the includes
6
+
7
+ module EffectiveMembershipsFeePayment
8
+ extend ActiveSupport::Concern
9
+
10
+ module Base
11
+ def effective_memberships_fee_payment
12
+ include ::EffectiveMembershipsFeePayment
13
+ end
14
+ end
15
+
16
+ module ClassMethods
17
+ def effective_memberships_fee_payment?; true; end
18
+
19
+ def all_wizard_steps
20
+ const_get(:WIZARD_STEPS).keys
21
+ end
22
+
23
+ def required_wizard_steps
24
+ [:start, :summary, :billing, :checkout, :submitted]
25
+ end
26
+ end
27
+
28
+ included do
29
+ acts_as_purchasable_parent
30
+ acts_as_tokened
31
+
32
+ acts_as_statused(
33
+ :draft, # Just Started
34
+ :submitted # All Done
35
+ )
36
+
37
+ acts_as_wizard(
38
+ start: 'Start',
39
+ demographics: 'Demographics',
40
+ declarations: 'Declarations',
41
+ summary: 'Review',
42
+ billing: 'Billing Address',
43
+ checkout: 'Checkout',
44
+ submitted: 'Submitted'
45
+ )
46
+
47
+ log_changes(except: :wizard_steps) if respond_to?(:log_changes)
48
+
49
+ # Declarations Step
50
+ attr_accessor :declare_code_of_ethics
51
+ attr_accessor :declare_truth
52
+
53
+ # Application Namespace
54
+ belongs_to :owner, polymorphic: true
55
+ accepts_nested_attributes_for :owner
56
+
57
+ # Like maybe optionally it makes sense.
58
+ belongs_to :category, polymorphic: true, optional: true
59
+
60
+ has_many :fees, -> { order(:id) }, as: :parent, class_name: 'Effective::Fee', dependent: :nullify
61
+ accepts_nested_attributes_for :fees, reject_if: :all_blank, allow_destroy: true
62
+
63
+ # Effective Namespace
64
+ has_many :orders, -> { order(:id) }, as: :parent, class_name: 'Effective::Order', dependent: :nullify
65
+ accepts_nested_attributes_for :orders
66
+
67
+ effective_resource do
68
+ # Acts as Statused
69
+ status :string, permitted: false
70
+ status_steps :text, permitted: false
71
+
72
+ # Dates
73
+ period :date
74
+ submitted_at :datetime
75
+
76
+ # Acts as Wizard
77
+ wizard_steps :text, permitted: false
78
+
79
+ timestamps
80
+ end
81
+
82
+ scope :deep, -> { includes(:owner, :category, :orders) }
83
+ scope :sorted, -> { order(:id) }
84
+
85
+ scope :in_progress, -> { where.not(status: [:submitted]) }
86
+ scope :done, -> { where(status: [:submitted]) }
87
+
88
+ before_validation do
89
+ self.period ||= EffectiveMemberships.Registrar.current_period
90
+ end
91
+
92
+ before_validation(if: -> { current_step == :start && owner && owner.membership }) do
93
+ self.category ||= owner.membership.categories.first if owner.membership.categories.length == 1
94
+ end
95
+
96
+ validates :period, presence: true
97
+
98
+ # All Steps validations
99
+ validates :owner, presence: true
100
+
101
+ # Declarations Step
102
+ with_options(if: -> { current_step == :declarations }) do
103
+ validates :declare_code_of_ethics, acceptance: true
104
+ validates :declare_truth, acceptance: true
105
+ end
106
+
107
+ # Clear required steps memoization
108
+ after_save { @_required_steps = nil }
109
+
110
+ # This required_steps is defined inside the included do .. end block so it overrides the acts_as_wizard one.
111
+ def required_steps
112
+ return self.class.test_required_steps if Rails.env.test? && self.class.test_required_steps.present?
113
+
114
+ @_required_steps ||= begin
115
+ wizard_steps = self.class.all_wizard_steps
116
+ required_steps = self.class.required_wizard_steps
117
+
118
+ fee_payment_steps = Array(category&.fee_payment_wizard_steps)
119
+
120
+ wizard_steps.select do |step|
121
+ required_steps.include?(step) || category.blank? || fee_payment_steps.include?(step)
122
+ end
123
+ end
124
+ end
125
+
126
+ after_purchase do |_order|
127
+ raise('expected submit_order to be purchased') unless submit_order&.purchased?
128
+
129
+ submit_purchased!
130
+ after_submit_purchased!
131
+ EffectiveMemberships.Registrar.fee_payment_purchased!(owner)
132
+ end
133
+ end
134
+
135
+ def to_s
136
+ 'Fee Payment'
137
+ end
138
+
139
+ # Instance Methods
140
+ def in_progress?
141
+ draft?
142
+ end
143
+
144
+ def done?
145
+ submitted?
146
+ end
147
+
148
+ def can_visit_step?(step)
149
+ can_revisit_completed_steps(step)
150
+ end
151
+
152
+ def outstanding_fees
153
+ owner&.outstanding_fee_payment_fees
154
+ end
155
+
156
+ # All Fees and Orders
157
+ def submit_fees
158
+ fees
159
+ end
160
+
161
+ def submit_order
162
+ orders.first
163
+ end
164
+
165
+ # We take over the owner's outstanding fees.
166
+ def find_or_build_submit_fees
167
+ Array(outstanding_fees).each { |fee| fees << fee }
168
+ submit_fees
169
+ end
170
+
171
+ def find_or_build_submit_order
172
+ order = submit_order || orders.build(user: owner)
173
+
174
+ submit_fees.each do |fee|
175
+ order.add(fee) unless order.purchasables.include?(fee)
176
+ end
177
+
178
+ order.billing_address = owner.billing_address if owner.billing_address.present?
179
+
180
+ order
181
+ end
182
+
183
+ # Should be indempotent.
184
+ def build_submit_fees_and_order
185
+ return false if was_submitted?
186
+
187
+ fees = find_or_build_submit_fees()
188
+ raise('already has purchased submit fees') if fees.any? { |fee| fee.purchased? }
189
+
190
+ order = find_or_build_submit_order()
191
+ raise('already has purchased submit order') if order.purchased?
192
+
193
+ true
194
+ end
195
+
196
+ # Owner clicks on the Billing Submit. Next step is Checkout
197
+ def billing!
198
+ ready!
199
+ end
200
+
201
+ def ready!
202
+ build_submit_fees_and_order
203
+ save!
204
+ end
205
+
206
+
207
+ # Called automatically via after_purchase hook above
208
+ def submit_purchased!
209
+ return false if was_submitted?
210
+
211
+ wizard_steps[:checkout] = Time.zone.now
212
+ submit!
213
+ end
214
+
215
+ # A hook to extend
216
+ def after_submit_purchased!
217
+ end
218
+
219
+ # Draft -> Submitted
220
+ def submit!
221
+ raise('already submitted') if was_submitted?
222
+ raise('expected a purchased order') unless submit_order&.purchased?
223
+
224
+ wizard_steps[:checkout] ||= Time.zone.now
225
+ wizard_steps[:submitted] = Time.zone.now
226
+ submitted!
227
+ end
228
+
229
+ end