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.
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