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,79 @@
1
+ module Effective
2
+ class ApplicantExperience < ActiveRecord::Base
3
+ belongs_to :applicant
4
+
5
+ log_changes(to: :applicant) if respond_to?(:log_changes)
6
+
7
+ LEVELS = ['Full Time', 'Part Time', 'Volunteer']
8
+ ONE_HUNDRED_PERCENT = 100_000
9
+
10
+ effective_resource do
11
+ position :string
12
+ employer :string
13
+
14
+ start_on :date
15
+ end_on :date
16
+
17
+ percent_worked :integer # 3 digits of precision. 50_000 == 50%
18
+ still_work_here :boolean
19
+
20
+ level :string
21
+ tasks_performed :text
22
+
23
+ months :integer
24
+
25
+ timestamps
26
+ end
27
+
28
+ scope :deep, -> { includes(:applicant) }
29
+
30
+ before_validation(if: -> { start_on.present? && end_on.present? }) do
31
+ self.months ||= ((end_on - start_on) / 1.month).to_i
32
+ end
33
+
34
+ validates :position, presence: true
35
+ validates :employer, presence: true
36
+ validates :start_on, presence: true
37
+ validates :end_on, presence: true
38
+ validates :level, presence: true, inclusion: { in: LEVELS }
39
+ validates :months, presence: true
40
+
41
+ validates :percent_worked, presence: true,
42
+ inclusion: { in: 0..ONE_HUNDRED_PERCENT, message: 'must be between 0 and 100%' }
43
+
44
+ validate(if: -> { start_on.present? && end_on.present? }) do
45
+ errors.add(:end_on, 'must be after start date') unless start_on < end_on
46
+ end
47
+
48
+ def to_s
49
+ position.presence || 'work experience'
50
+ end
51
+
52
+ def assign_months!
53
+ return 0 unless end_on.present? && start_on.present?
54
+
55
+ self.percent_worked ||= (part_time? ? 0 : ONE_HUNDRED_PERCENT)
56
+
57
+ months = ((end_on.end_of_day.to_time - start_on.to_time) / 1.month.second).ceil
58
+ months *= (percent_worked / ONE_HUNDRED_PERCENT.to_f)
59
+
60
+ self.months = months
61
+ end
62
+
63
+ def percent_worked_to_s
64
+ (percent_worked.to_i / 1000.to_f).to_i.to_s + '%'
65
+ end
66
+
67
+ def full_time?
68
+ level == 'Full Time'
69
+ end
70
+
71
+ def part_time?
72
+ level == 'Part Time'
73
+ end
74
+
75
+ def volunteer?
76
+ level == 'Volunteer'
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,81 @@
1
+ module Effective
2
+ class ApplicantReference < ActiveRecord::Base
3
+ acts_as_tokened
4
+ acts_as_addressable :reference
5
+ acts_as_statused :submitted, :completed
6
+ log_changes(to: :applicant) if respond_to?(:log_changes)
7
+
8
+ belongs_to :applicant
9
+
10
+ KNOWNS = ['6 months - 1 year', '1 year - 2 years', '2 - 5 years', '5 - 10 years', 'More than 10 years']
11
+ RELATIONSHIPS = ['Supervisor', 'Employer', 'Colleague', 'Other']
12
+
13
+ effective_resource do
14
+ # These two fields are submitted on the Applicant wizard step
15
+ name :string
16
+ email :string
17
+ phone :string
18
+
19
+ # As per acts_as_statused. For tracking the state machine.
20
+ status :string
21
+ status_steps :text
22
+
23
+ # Reference Declaration
24
+ known :string
25
+ relationship :string
26
+
27
+ reservations :boolean
28
+ reservations_reason :text
29
+
30
+ work_history :text
31
+
32
+ accept_declaration :boolean
33
+
34
+ # Tracking the submission
35
+ token :string
36
+ last_notified_at :datetime
37
+
38
+ timestamps
39
+ end
40
+
41
+ scope :deep, -> { includes(:applicant) }
42
+
43
+ # All step validations
44
+ validates :applicant, presence: true
45
+
46
+ validates :name, presence: true
47
+ validates :email, presence: true, email: true
48
+ validates :phone, presence: true
49
+
50
+ validates :relationship, presence: true
51
+ validates :known, presence: true
52
+
53
+ # When being submit by the reference
54
+ with_options(if: -> { completed? }) do
55
+ validates :reference_address, presence: true
56
+ validates :reservations_reason, presence: true, if: -> { completed? && reservations? }
57
+ validates :work_history, presence: true
58
+ validates :accept_declaration, acceptance: true
59
+ end
60
+
61
+ after_commit(on: :create, if: -> { applicant.submitted? }) { notify! }
62
+
63
+ def to_s
64
+ 'reference'
65
+ end
66
+
67
+ def notify!
68
+ raise('expected reference email') unless email.present?
69
+
70
+ EffectiveMemberships.send_email(:applicant_reference_notification, self)
71
+ update!(last_notified_at: Time.zone.now)
72
+ end
73
+
74
+ def complete!
75
+ completed!
76
+ applicant.save!
77
+ end
78
+
79
+ end
80
+
81
+ end
@@ -0,0 +1,7 @@
1
+ module Effective
2
+ class ApplicantReview < ActiveRecord::Base
3
+ self.table_name = EffectiveMemberships.applicant_reviews_table_name.to_s
4
+
5
+ effective_memberships_applicant_review
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Effective
2
+ class Category < ActiveRecord::Base
3
+ self.table_name = EffectiveMemberships.categories_table_name.to_s
4
+
5
+ effective_memberships_category
6
+ end
7
+ end
@@ -0,0 +1,142 @@
1
+ module Effective
2
+ class Fee < ActiveRecord::Base
3
+ acts_as_purchasable
4
+
5
+ log_changes(to: :owner) if respond_to?(:log_changes)
6
+
7
+ # Every fee is charged to a owner
8
+ belongs_to :owner, polymorphic: true
9
+
10
+ # This fee may belong to an application, fee payment, or other parent model
11
+ belongs_to :parent, polymorphic: true, optional: true
12
+
13
+ # The membership category for this fee, if there's only 1 membership.categories
14
+ belongs_to :category, polymorphic: true, optional: true
15
+
16
+ effective_resource do
17
+ fee_type :string
18
+
19
+ title :string
20
+
21
+ period :date
22
+
23
+ late_on :date
24
+ bad_standing_on :date
25
+
26
+ price :integer
27
+ qb_item_name :string
28
+ tax_exempt :boolean
29
+
30
+ timestamps
31
+ end
32
+
33
+ scope :sorted, -> { order(:id) }
34
+ scope :deep, -> { includes(:owner, :parent, :category) }
35
+
36
+ before_validation(if: -> { owner.present? }) do
37
+ additional = owner.additional_fee_attributes(self)
38
+ raise('expected a Hash of attributes') unless additional.kind_of?(Hash)
39
+ assign_attributes(additional)
40
+ end
41
+
42
+ before_validation(if: -> { owner && owner.membership }) do
43
+ self.category ||= owner.membership.categories.first if owner.membership.categories.length == 1
44
+ end
45
+
46
+ before_validation do
47
+ self.period ||= default_period()
48
+ self.late_on ||= default_late_on()
49
+ self.bad_standing_on ||= default_bad_standing_on()
50
+
51
+ self.qb_item_name ||= default_qb_item_name()
52
+ self.tax_exempt = default_tax_exempt() if tax_exempt.nil?
53
+
54
+ self.title ||= default_title()
55
+ end
56
+
57
+ validates :fee_type, presence: true
58
+ validates :price, presence: true
59
+
60
+ validates :title, presence: true
61
+ validates :period, presence: true
62
+ validates :qb_item_name, presence: true
63
+
64
+ validate(if: -> { fee_type.present? }) do
65
+ self.errors.add(:fee_type, 'is not included') unless EffectiveMemberships.fee_types.include?(fee_type)
66
+ end
67
+
68
+ validates :late_on, presence: true,
69
+ if: -> { fee_type == 'Renewal' && category&.create_late_fees? }
70
+
71
+ validates :bad_standing_on, presence: true,
72
+ if: -> { fee_type == 'Renewal' && category&.create_bad_standing? }
73
+
74
+ def to_s
75
+ title.presence || default_title()
76
+ end
77
+
78
+ def late?
79
+ return false if late_on.blank?
80
+ return false if purchased?
81
+
82
+ late_on <= Time.zone.now.to_date
83
+ end
84
+
85
+ def bad_standing?
86
+ return false if bad_standing_on.blank?
87
+ return false if purchased?
88
+
89
+ bad_standing_on <= Time.zone.now.to_date
90
+ end
91
+
92
+ # Used by applicant.applicant_submit_fees
93
+ def applicant_submit_fee?
94
+ fee_type == 'Applicant'
95
+ end
96
+
97
+ def fee_payment_fee?
98
+ fee_type != 'Applicant'
99
+ end
100
+
101
+ # Will advance a membership.fees_paid_through_year value when purchased
102
+ def membership_period_fee?
103
+ fee_type == 'Prorated' || fee_type == 'Renewal'
104
+ end
105
+
106
+ def custom_fee?
107
+ EffectiveMemberships.custom_fee_types.include?(fee_type)
108
+ end
109
+
110
+ private
111
+
112
+ def default_period
113
+ EffectiveMemberships.Registrar.current_period
114
+ end
115
+
116
+ def default_late_on
117
+ nil
118
+ end
119
+
120
+ def default_bad_standing_on
121
+ nil
122
+ end
123
+
124
+ def default_title
125
+ [
126
+ period&.strftime('%Y').presence,
127
+ category.to_s.presence,
128
+ fee_type.presence,
129
+ 'Fee'
130
+ ].join(' ')
131
+ end
132
+
133
+ def default_qb_item_name
134
+ "#{fee_type} Fee"
135
+ end
136
+
137
+ def default_tax_exempt
138
+ false
139
+ end
140
+
141
+ end
142
+ end
@@ -0,0 +1,7 @@
1
+ module Effective
2
+ class FeePayment < ActiveRecord::Base
3
+ self.table_name = EffectiveMemberships.fee_payments_table_name.to_s
4
+
5
+ effective_memberships_fee_payment
6
+ end
7
+ end
@@ -0,0 +1,119 @@
1
+ module Effective
2
+ class Membership < ActiveRecord::Base
3
+ belongs_to :owner, polymorphic: true
4
+
5
+ has_many :membership_categories, -> { order(:id) }, inverse_of: :membership, dependent: :delete_all
6
+ accepts_nested_attributes_for :membership_categories
7
+
8
+ log_changes(to: :owner) if respond_to?(:log_changes)
9
+
10
+ effective_resource do
11
+ # Membership Info
12
+ number :string # A unique value
13
+ number_as_integer :integer # A unique integer
14
+
15
+ joined_on :date # When they first receive a membership category
16
+ registration_on :date # When the membership category last changed. Applied or reclassified.
17
+
18
+ # Membership Status
19
+ fees_paid_period :date # The most recent period they have paid in. Start date of period.
20
+ fees_paid_through_period :date # The most recent period they have paid in. End date of period. Kind of an expires.
21
+
22
+ bad_standing :boolean # Calculated value. Is this owner in bad standing? (fees due)
23
+ bad_standing_admin :boolean # Admin set this
24
+ bad_standing_reason :text # Reason for bad standing
25
+
26
+ timestamps
27
+ end
28
+
29
+ scope :deep, -> { includes(owner: [:fees, :membership]) }
30
+ scope :sorted, -> { order(:id) }
31
+
32
+ scope :with_paid_fees_through, -> (period = nil) {
33
+ where(arel_table[:fees_paid_period].gteq(period || EffectiveMemberships.Registrar.current_period))
34
+ }
35
+
36
+ scope :with_unpaid_fees_through, -> (period = nil) {
37
+ where(arel_table[:fees_paid_period].lt(period || EffectiveMemberships.Registrar.current_period))
38
+ .or(where(fees_paid_period: nil))
39
+ }
40
+
41
+ scope :create_renewal_fees, -> (period = nil) {
42
+ deep.with_unpaid_fees_through(period)
43
+ .where.not(fees_paid_period: nil) # Must have purchased a Prorated or Renewal Fee before
44
+ #.where(category_id: EffectiveMemberships.Category.create_renewal_fees)
45
+ }
46
+
47
+ scope :create_late_fees, -> (period = nil) {
48
+ deep.with_unpaid_fees_through(period)
49
+ .where.not(fees_paid_period: nil) # Must have purchased a Prorated or Renewal Fee before
50
+ #.where(category_id: EffectiveMemberships.Category.create_late_fees)
51
+ }
52
+
53
+ scope :create_bad_standing, -> (period = nil) {
54
+ deep.with_unpaid_fees_through(period)
55
+ .where.not(fees_paid_period: nil) # Must have purchased a Prorated or Renewal Fee before
56
+ #.where(category_id: EffectiveMemberships.Category.create_bad_standing)
57
+ }
58
+
59
+ before_validation do
60
+ self.registration_on ||= joined_on
61
+ self.number_as_integer ||= (Integer(number) rescue nil)
62
+ end
63
+
64
+ validates :number, presence: true, uniqueness: true
65
+ validates :joined_on, presence: true
66
+ validates :registration_on, presence: true
67
+
68
+ validate(if: -> { owner.present? }) do
69
+ self.errors.add(:owner_id, 'must be a memberships owner') unless owner.class.effective_memberships_owner?
70
+ end
71
+
72
+ def self.max_number
73
+ maximum('number_as_integer') || 0
74
+ end
75
+
76
+ def to_s
77
+ 'membership'
78
+ end
79
+
80
+ # We can't use the polymorphic has_many. So this is a helper.
81
+ def categories
82
+ membership_categories.reject(&:marked_for_destruction?).map(&:category)
83
+ end
84
+
85
+ def category_ids
86
+ membership_categories.reject(&:marked_for_destruction?).map(&:category_id)
87
+ end
88
+
89
+ # We might want to use singular memberships.
90
+ def category
91
+ raise('expected singular usage but there are more than one membership') if categories.length > 1
92
+ categories.first
93
+ end
94
+
95
+ def category_id
96
+ raise('expected singular usage but there are more than one membership') if categories.length > 1
97
+ categories.first.id
98
+ end
99
+
100
+ def membership_category(category:)
101
+ raise('expected a category') unless category.class.respond_to?(:effective_memberships_category?)
102
+ membership_categories.find { |mc| mc.category_id == category.id && mc.category_type == category.class.name }
103
+ end
104
+
105
+ # find or build
106
+ def build_membership_category(category:)
107
+ membership_category(category: category) || membership_categories.build(category: category)
108
+ end
109
+
110
+ def good_standing?
111
+ !bad_standing?
112
+ end
113
+
114
+ def fees_paid?
115
+ fees_paid_period == EffectiveMemberships.Registrar.current_period
116
+ end
117
+
118
+ end
119
+ end
@@ -0,0 +1,11 @@
1
+ module Effective
2
+ class MembershipCategory < ActiveRecord::Base
3
+ belongs_to :category, polymorphic: true
4
+ belongs_to :membership, polymorphic: true
5
+
6
+ def to_s
7
+ 'membership category'
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,40 @@
1
+ module Effective
2
+ class MembershipHistory < ActiveRecord::Base
3
+ belongs_to :owner, polymorphic: true
4
+
5
+ effective_resource do
6
+ start_on :date
7
+ end_on :date
8
+
9
+ number :string
10
+
11
+ bad_standing :boolean
12
+ removed :boolean
13
+
14
+ categories :text
15
+ category_ids :text
16
+
17
+ notes :text
18
+
19
+ timestamps
20
+ end
21
+
22
+ serialize :categories, Array
23
+ serialize :category_ids, Array
24
+
25
+ scope :deep, -> { includes(:owner) }
26
+ scope :sorted, -> { order(:start_on) }
27
+
28
+ validates :owner, presence: true
29
+
30
+ validates :categories, presence: true, unless: -> { removed? }
31
+ validates :category_ids, presence: true, unless: -> { removed? }
32
+
33
+ validates :start_on, presence: true
34
+
35
+ def to_s
36
+ 'membership history'
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,19 @@
1
+ module Effective
2
+ class Registrar
3
+
4
+ include EffectiveMembershipsRegistrar
5
+
6
+ def renewal_fee_date(date:)
7
+ Date.new(date.year, 12, 1) # Fees roll over every December 1st
8
+ end
9
+
10
+ def late_fee_date(period:)
11
+ Date.new(period.year, 2, 1) # Fees are late after February 1st
12
+ end
13
+
14
+ def bad_standing_date(period:)
15
+ Date.new(period.year, 3, 1) # Membership in bad standing after March 1st
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,97 @@
1
+ # Form Object for the Admin Change Status screens
2
+
3
+ module Effective
4
+ class RegistrarAction
5
+ include ActiveModel::Model
6
+
7
+ # All Actions
8
+ attr_accessor :current_user
9
+ attr_accessor :current_action
10
+ attr_accessor :owner, :owner_id, :owner_type
11
+
12
+ # Bad Standing
13
+ attr_accessor :bad_standing_reason
14
+
15
+ # Reclassify & Register
16
+ attr_accessor :category_id
17
+ attr_accessor :membership_number
18
+ attr_accessor :skip_fees
19
+
20
+ # All Action Validations
21
+ validates :current_action, presence: true
22
+ validates :current_user, presence: true
23
+ validates :owner, presence: true
24
+
25
+ # Bad Standing
26
+ validates :bad_standing_reason, presence: true, if: -> { current_action == :bad_standing }
27
+
28
+ # Reclassification & Register
29
+ validates :category_id, presence: true,
30
+ if: -> { current_action == :reclassify || current_action == :register }
31
+
32
+ def to_s
33
+ 'action'
34
+ end
35
+
36
+ def good_standing!
37
+ update!(current_action: :good_standing)
38
+ EffectiveMemberships.Registrar.good_standing!(owner)
39
+ end
40
+
41
+ def bad_standing!
42
+ update!(current_action: :bad_standing)
43
+ EffectiveMemberships.Registrar.bad_standing!(owner, reason: bad_standing_reason)
44
+ end
45
+
46
+ def reclassify!
47
+ update!(current_action: :reclassify)
48
+ EffectiveMemberships.Registrar.reclassify!(owner, to: category, skip_fees: skip_fees?)
49
+ end
50
+
51
+ def fees_paid!
52
+ update!(current_action: :fees_paid)
53
+ EffectiveMemberships.Registrar.fees_paid!(owner)
54
+ end
55
+
56
+ def remove!
57
+ update!(current_action: :remove)
58
+ EffectiveMemberships.Registrar.remove!(owner)
59
+ end
60
+
61
+ def register!
62
+ update!(current_action: :register)
63
+ EffectiveMemberships.Registrar.register!(owner, to: category, number: membership_number.presence, skip_fees: skip_fees?)
64
+ end
65
+
66
+ def update!(atts)
67
+ assign_attributes(atts); save!
68
+ end
69
+
70
+ def save!
71
+ valid? ? true : raise('invalid')
72
+ end
73
+
74
+ def owner
75
+ @owner ||= (@owner_type.constantize.find(@owner_id) if @owner_type && @owner_id)
76
+ end
77
+
78
+ def owner_type
79
+ @owner_type || (@owner.class.name if @owner)
80
+ end
81
+
82
+ def owner_id
83
+ @owner_id || (@owner.id if @owner)
84
+ end
85
+
86
+ private
87
+
88
+ def category
89
+ EffectiveMemberships.Category.find(@category_id) if @category_id
90
+ end
91
+
92
+ def skip_fees?
93
+ EffectiveResources.truthy?(@skip_fees)
94
+ end
95
+
96
+ end
97
+ end
@@ -0,0 +1,8 @@
1
+ = tabs do
2
+ = tab 'Course Area' do
3
+ = render 'admin/applicant_course_areas/form_applicant_course_area', applicant_course_area: applicant_course_area
4
+
5
+ - if applicant_course_area.persisted?
6
+ - if applicant_course_area.respond_to?(:log_changes_datatable)
7
+ = tab 'Logs' do
8
+ = render_inline_datatable(applicant_course_area.log_changes_datatable)
@@ -0,0 +1,8 @@
1
+ = effective_form_with(model: [:admin, applicant_course_area], engine: true) do |f|
2
+ = f.text_field :title
3
+ = effective_submit(f)
4
+
5
+ - if applicant_course_area.persisted?
6
+ %h2 Course Names
7
+ - datatable = Admin::EffectiveApplicantCourseNamesDatatable.new(applicant_course_area_id: applicant_course_area.id)
8
+ = render_datatable(datatable, inline: true)
@@ -0,0 +1,8 @@
1
+ .row
2
+ .col-8
3
+ %h1.effective-admin-heading= @page_title
4
+ .col-4.text-right
5
+ = render_resource_buttons(Effective::ApplicantCourseArea, :index => false)
6
+
7
+ %p Click Reorder to drag & drop reorder the position of these items.
8
+ = render_datatable(@datatable)
@@ -0,0 +1,9 @@
1
+ = effective_form_with(model: [:admin, applicant_course_name], engine: true) do |f|
2
+ - if inline_datatable?
3
+ = f.hidden_field :applicant_course_area_id
4
+ - else
5
+ = f.select :applicant_course_area_id, Effective::ApplicantCourseArea.all
6
+
7
+ = f.text_field :title
8
+
9
+ = f.submit
@@ -0,0 +1,38 @@
1
+ = tabs do
2
+ - # Done applicants have the Applicant tab first. In progress ones the Status tab
3
+ - if applicant.done?
4
+ = tab 'Applicant' do
5
+ .mb-4= render 'effective/applicants/summary', applicant: applicant, namespace: :admin
6
+ = render 'effective/applicants/applicant', applicant: applicant, namespace: :admin
7
+
8
+ = tab 'Status' do
9
+ = render 'admin/applicants/status', applicant: applicant, namespace: :admin
10
+
11
+ - if applicant.in_progress?
12
+ = tab 'Status' do
13
+ = render 'admin/applicants/status', applicant: applicant, namespace: :admin
14
+
15
+ = tab 'Applicant' do
16
+ .mb-4= render 'effective/applicants/summary', applicant: applicant, namespace: :admin
17
+ = render 'effective/applicants/applicant', applicant: applicant, namespace: :admin
18
+
19
+ - # Just normal tabs now
20
+ - if applicant.was_submitted? && !applicant.was_approved?
21
+ = tab 'Process' do
22
+ = render 'admin/applicants/form_process', applicant: applicant
23
+
24
+ - if applicant.fees.present?
25
+ = tab 'Fees' do
26
+ .mb-4= render_inline_datatable(Admin::EffectiveFeesDatatable.new(applicant_id: applicant.to_param))
27
+
28
+ - if applicant.orders.present?
29
+ = tab 'Orders' do
30
+ .mb-4= render_datatable(Admin::EffectiveOrdersDatatable.new(parent_id: applicant.id, parent_type: EffectiveMemberships.Applicant.name, owner_id: applicant.owner_id, user_type: applicant.owner_type))
31
+
32
+ - if applicant.owner.applicants.any? { |other| other.was_submitted? && other.id != applicant.id }
33
+ = tab 'Other Applications' do
34
+ = render_datatable(Admin::EffectiveApplicantsDatatable.new(owner_id: applicant.owner_id, except_id: applicant.id))
35
+
36
+ - if applicant.persisted? && applicant.respond_to?(:log_changes_datatable)
37
+ = tab 'Logs' do
38
+ = render_inline_datatable(applicant.log_changes_datatable)