effective_memberships 0.6.13 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/admin/applicant_reviews_controller.rb +19 -0
  3. data/app/controllers/effective/applicant_reviews_controller.rb +13 -0
  4. data/app/datatables/admin/effective_applicant_reviews_datatable.rb +24 -0
  5. data/app/datatables/effective_applicant_reviews_datatable.rb +25 -0
  6. data/app/datatables/effective_available_applicant_reviews_datatable.rb +40 -0
  7. data/app/mailers/effective/memberships_mailer.rb +57 -4
  8. data/app/models/concerns/effective_memberships_applicant.rb +26 -3
  9. data/app/models/concerns/effective_memberships_applicant_review.rb +34 -68
  10. data/app/models/concerns/effective_memberships_owner.rb +19 -0
  11. data/app/models/concerns/effective_memberships_user.rb +6 -0
  12. data/app/models/effective/membership.rb +3 -0
  13. data/app/views/admin/applicant_reviews/_applicant_review.html.haml +1 -0
  14. data/app/views/admin/applicants/_form_applicant.html.haml +4 -0
  15. data/app/views/admin/applicants/_status.html.haml +2 -2
  16. data/app/views/effective/applicant_endorsements/complete.html.haml +1 -1
  17. data/app/views/effective/applicant_references/complete.html.haml +1 -1
  18. data/app/views/effective/applicant_reviews/_applicant_review.html.haml +4 -0
  19. data/app/views/effective/applicant_reviews/_content.html.haml +8 -0
  20. data/app/views/effective/applicant_reviews/_dashboard.html.haml +36 -0
  21. data/app/views/effective/applicant_reviews/_layout.html.haml +3 -0
  22. data/app/views/effective/applicant_reviews/_recommendation.html.haml +23 -0
  23. data/app/views/effective/applicant_reviews/_review.html.haml +1 -0
  24. data/app/views/effective/applicant_reviews/recommendation.html.haml +20 -0
  25. data/app/views/effective/applicant_reviews/review.html.haml +14 -0
  26. data/app/views/effective/applicant_reviews/start.html.haml +18 -0
  27. data/app/views/effective/applicant_reviews/submitted.html.haml +15 -0
  28. data/app/views/effective/applicants/_summary.html.haml +4 -3
  29. data/app/views/effective/fees/_dashboard.html.haml +1 -1
  30. data/app/views/effective/memberships_mailer/applicant_approved.liquid +2 -2
  31. data/app/views/effective/memberships_mailer/applicant_completed.liquid +1 -1
  32. data/app/views/effective/memberships_mailer/applicant_review_submitted.liquid +19 -0
  33. data/app/views/effective/memberships_mailer/applicant_submitted.liquid +15 -0
  34. data/config/routes.rb +6 -0
  35. data/lib/effective_memberships/version.rb +1 -1
  36. metadata +20 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 58da6947ec96b076bd125a190e31ade30e7964b6dc796423dc7387efa156a246
4
- data.tar.gz: 5805aa420a911f2abbb4d38ceaf1d6d743a3932e3b47a14b3b1e4a8796afd0bd
3
+ metadata.gz: a205c16844097a8874a4f40bda0465c96bd75cbda0ca7505cd74222180891323
4
+ data.tar.gz: f0edcff046181395c697a0c1ce386188dd28d419c7f3c5b9c466f6f9c51b972c
5
5
  SHA512:
6
- metadata.gz: ea84383f2dbd0763c31e4c8a1c13f16d474d96e749502717dec4825cdbd50eee9fe3dbf3572650dcccedc3f05785bc283c0451fd9ac51fee5f17fd7fa738e7b6
7
- data.tar.gz: f0d0dad74b75782b25fc76f786b6e1d042faa591941ebc747ac85d24301dda54e8d3fc5be080ef5827ac251de0f60d23eafea443106565fb872fd2ccdb9bf1a6
6
+ metadata.gz: d2abc588db28def44fbebc89138443c1de6e4cfba9654b2e0b93d2d399eca177b3ce0da51a0a62ac50dea6585518459609bc3a7749061719f9ea5f2c3572dfd2
7
+ data.tar.gz: 37e0ca136d376fba18e6d84eba56289a81c17c7da9268aaab10c415b78aecc5f83c256dd1a9eb43a4d4c362491b8deaf0223a711fcdf957007ccca463728c1d0
@@ -0,0 +1,19 @@
1
+ module Admin
2
+ class ApplicantReviewsController < ApplicationController
3
+ before_action(:authenticate_user!) if defined?(Devise)
4
+ before_action { EffectiveResources.authorize!(self, :admin, :effective_memberships) }
5
+
6
+ include Effective::CrudController
7
+
8
+ resource_scope -> { EffectiveMemberships.ApplicantReview.deep.all }
9
+ datatable -> { Admin::EffectiveApplicantReviewsDatatable.new }
10
+
11
+ private
12
+
13
+ def permitted_params
14
+ model = (params.key?(:effective_applicant_review) ? :effective_applicant_review: :applicant_review)
15
+ params.require(model).permit!
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ module Effective
2
+ class ApplicantReviewsController < ApplicationController
3
+ before_action(:authenticate_user!) if defined?(Devise)
4
+
5
+ include Effective::WizardController
6
+
7
+ resource_scope -> {
8
+ applicant = EffectiveMemberships.Applicant.find(params[:applicant_id])
9
+ EffectiveMemberships.ApplicantReview.deep.where(reviewer: current_user, applicant: applicant)
10
+ }
11
+
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ module Admin
2
+ class EffectiveApplicantReviewsDatatable < Effective::Datatable
3
+ datatable do
4
+ order :submitted_at
5
+
6
+ col :created_at, visible: false
7
+ col :id, visible: false
8
+
9
+ col :submitted_at, label: 'Reviewed', as: :date
10
+ col :reviewer
11
+ col :applicant
12
+
13
+ col :recommendation
14
+ col :comments
15
+
16
+ actions_col
17
+ end
18
+
19
+ collection do
20
+ EffectiveMemberships.ApplicantReview.deep.submitted
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ # Dashboard ApplicantReviews
2
+ class EffectiveApplicantReviewsDatatable < Effective::Datatable
3
+ datatable do
4
+ order :id, :desc
5
+
6
+ col :created_at, visible: false
7
+ col :id, visible: false
8
+
9
+ col :applicant
10
+ col :reviewer, visible: false
11
+
12
+ col :submitted_at, label: 'Reviewed', as: :date
13
+ col :recommendation
14
+ col :comments
15
+
16
+ actions_col(show: false) do |applicant_review|
17
+ dropdown_link_to('Show', effective_memberships.applicant_applicant_review_path(applicant_review.applicant, applicant_review))
18
+ end
19
+ end
20
+
21
+ collection do
22
+ EffectiveMemberships.ApplicantReview.deep.submitted.where(reviewer: current_user)
23
+ end
24
+
25
+ end
@@ -0,0 +1,40 @@
1
+ # Dashboard Available Applicant Reviews
2
+ class EffectiveAvailableApplicantReviewsDatatable < Effective::Datatable
3
+ datatable do
4
+ order :created_at
5
+
6
+ col :id, visible: false
7
+
8
+ col :created_at, label: 'Created', as: :date, visible: false
9
+ col :updated_at, label: 'Updated', as: :date, visible: false
10
+ col :submitted_at, label: 'Submitted', as: :date, visible: false
11
+
12
+ # This is when they become available for review
13
+ col :completed_at, label: 'Date', as: :date
14
+
15
+ col :user
16
+ col :category, label: 'Category'
17
+ col :to_status, label: 'Status'
18
+
19
+ actions_col(show: false) do |applicant|
20
+ applicant_review = applicant.applicant_review(reviewer: current_user)
21
+
22
+ if applicant_review.blank?
23
+ dropdown_link_to('Start Review', effective_memberships.new_applicant_applicant_review_path(applicant), 'data-turbolinks' => false)
24
+ elsif applicant_review.in_progress?
25
+ dropdown_link_to('Continue', effective_memberships.applicant_applicant_review_build_path(applicant, applicant_review, applicant_review.next_step), 'data-turbolinks' => false)
26
+ dropdown_link_to('Delete', effective_memberships.applicant_applicant_review_path(applicant, applicant_review), 'data-confirm': "Really delete #{applicant_review}?", 'data-method': :delete)
27
+ else
28
+ dropdown_link_to('Show', effective_memberships.applicant_applicant_review_path(applicant, applicant_review), 'data-turbolinks' => false)
29
+ end
30
+ end
31
+ end
32
+
33
+ collection do
34
+ # These ones have already been submitted by this reviewer
35
+ submitted = EffectiveMemberships.ApplicantReview.deep.submitted.where(reviewer: current_user).select('applicant_id')
36
+
37
+ EffectiveMemberships.Applicant.deep.reviewable.where.not(id: submitted)
38
+ end
39
+
40
+ end
@@ -4,11 +4,21 @@ module Effective
4
4
  include EffectiveMailer
5
5
  include EffectiveEmailTemplatesMailer if EffectiveMemberships.use_effective_email_templates
6
6
 
7
+ def applicant_submitted(resource, opts = {})
8
+ @assigns = assigns_for(resource)
9
+ @applicant = resource
10
+
11
+ subject = subject_for(__method__, "Applicant Submitted - #{resource}", resource, opts)
12
+ headers = headers_for(resource, opts)
13
+
14
+ mail(to: resource.user.email, subject: subject, **headers)
15
+ end
16
+
7
17
  def applicant_completed(resource, opts = {})
8
18
  @assigns = assigns_for(resource)
9
19
  @applicant = resource
10
20
 
11
- subject = subject_for(__method__, 'Applicant Completed', resource, opts)
21
+ subject = subject_for(__method__, "Applicant Completed - #{resource}", resource, opts)
12
22
  headers = headers_for(resource, opts)
13
23
 
14
24
  mail(to: resource.user.email, subject: subject, **headers)
@@ -18,7 +28,7 @@ module Effective
18
28
  @assigns = assigns_for(resource)
19
29
  @applicant = resource
20
30
 
21
- subject = subject_for(__method__, 'Applicant Missing Info', resource, opts)
31
+ subject = subject_for(__method__, "Applicant Missing Info - #{resource}", resource, opts)
22
32
  headers = headers_for(resource, opts)
23
33
 
24
34
  mail(to: resource.user.email, subject: subject, **headers)
@@ -28,7 +38,7 @@ module Effective
28
38
  @assigns = assigns_for(resource)
29
39
  @applicant = resource
30
40
 
31
- subject = subject_for(__method__, 'Applicant Approved', resource, opts)
41
+ subject = subject_for(__method__, "Applicant Approved - #{resource}", resource, opts)
32
42
  headers = headers_for(resource, opts)
33
43
 
34
44
  mail(to: resource.user.email, subject: subject, **headers)
@@ -38,7 +48,7 @@ module Effective
38
48
  @assigns = assigns_for(resource)
39
49
  @applicant = resource
40
50
 
41
- subject = subject_for(__method__, 'Applicant Declined', resource, opts)
51
+ subject = subject_for(__method__, "Applicant Declined - #{resource}", resource, opts)
42
52
  headers = headers_for(resource, opts)
43
53
 
44
54
  mail(to: resource.user.email, subject: subject, **headers)
@@ -64,6 +74,17 @@ module Effective
64
74
  mail(to: resource.email, subject: subject, **headers)
65
75
  end
66
76
 
77
+ def applicant_review_submitted(resource, opts = {})
78
+ @assigns = assigns_for(resource)
79
+ @applicant_review = resource
80
+ @applicant = resource.applicant
81
+
82
+ subject = subject_for(__method__, "Applicant Review Submitted - #{resource.applicant}", resource, opts)
83
+ headers = headers_for(resource, opts)
84
+
85
+ mail(to: mailer_admin, subject: subject, **headers)
86
+ end
87
+
67
88
  protected
68
89
 
69
90
  def assigns_for(resource)
@@ -71,6 +92,10 @@ module Effective
71
92
  return applicant_assigns(resource).merge(owner_assigns(resource.owner)).merge(membership_assigns(resource.owner.membership))
72
93
  end
73
94
 
95
+ if resource.class.respond_to?(:effective_memberships_applicant_review?)
96
+ return applicant_review_assigns(resource).merge(applicant_assigns(resource.applicant)).merge(owner_assigns(resource.applicant.owner)).merge(membership_assigns(resource.applicant.owner.membership))
97
+ end
98
+
74
99
  if resource.kind_of?(Effective::ApplicantEndorsement)
75
100
  return endorsement_assigns(resource).merge(owner_assigns(resource.applicant.owner))
76
101
  end
@@ -103,6 +128,23 @@ module Effective
103
128
  { applicant: values }
104
129
  end
105
130
 
131
+ def applicant_review_assigns(applicant_review)
132
+ raise('expected an applicant review') unless applicant_review.class.respond_to?(:effective_memberships_applicant_review?)
133
+
134
+ values = {
135
+ date: (applicant_review.submitted_at || Time.zone.now).strftime('%F'),
136
+ submitted_at: (applicant_review.submitted_at&.strftime('%F') || 'not submitted'),
137
+
138
+ url: effective_memberships.applicant_applicant_review_url(applicant_review.applicant, applicant_review),
139
+ admin_url: effective_memberships.admin_applicant_review_url(applicant_review),
140
+
141
+ recommendation: applicant_review.recommendation.presence,
142
+ comments: applicant_review.comments.presence
143
+ }.compact
144
+
145
+ { applicant_review: values }
146
+ end
147
+
106
148
  def membership_assigns(membership)
107
149
  return {} if membership.blank?
108
150
  raise('expected a membership') unless membership.kind_of?(Effective::Membership)
@@ -149,5 +191,16 @@ module Effective
149
191
  { user: values }
150
192
  end
151
193
 
194
+ def reviewer_assigns(owner)
195
+ raise('expected a owner') unless owner.class.respond_to?(:effective_memberships_owner?)
196
+
197
+ values = {
198
+ name: owner.to_s,
199
+ email: owner.email
200
+ }
201
+
202
+ { reviewer: values }
203
+ end
204
+
152
205
  end
153
206
  end
@@ -187,6 +187,8 @@ module EffectiveMembershipsApplicant
187
187
 
188
188
  scope :not_draft, -> { where.not(status: :draft) }
189
189
 
190
+ scope :reviewable, -> { where(status: :completed) }
191
+
190
192
  scope :for, -> (user) {
191
193
  raise('expected a effective memberships user') unless user.class.try(:effective_memberships_user?)
192
194
  where(user: user).or(where(organization: user.organizations))
@@ -488,6 +490,8 @@ module EffectiveMembershipsApplicant
488
490
  stamps.each { |stamp| stamp.submit! }
489
491
 
490
492
  after_commit do
493
+ send_email(:applicant_submitted)
494
+
491
495
  applicant_endorsements.each { |endorsement| endorsement.notify! if endorsement.submitted? }
492
496
  applicant_references.each { |reference| reference.notify! if reference.submitted? }
493
497
  end
@@ -569,7 +573,7 @@ module EffectiveMembershipsApplicant
569
573
  when 'reviewed'
570
574
  "This application has been reviewed and is now ready for an admin to approve or decline it. If approved, prorated fees will be generated."
571
575
  when 'approved'
572
- "The application has been approved! All done!"
576
+ "The application has been approved. All done."
573
577
  when 'declined'
574
578
  "This application has been declined."
575
579
  else
@@ -750,6 +754,10 @@ module EffectiveMembershipsApplicant
750
754
  end
751
755
 
752
756
  # Completed -> Reviewed requirements
757
+ def reviewable?
758
+ completed?
759
+ end
760
+
753
761
  def applicant_reviews_required?
754
762
  (min_applicant_reviews > 0 || applicant_reviews.present?)
755
763
  end
@@ -758,6 +766,16 @@ module EffectiveMembershipsApplicant
758
766
  category&.min_applicant_reviews.to_i
759
767
  end
760
768
 
769
+ # Find
770
+ def applicant_review(reviewer:)
771
+ applicant_reviews.find { |ar| ar.reviewer_id == reviewer.id && ar.reviewer_type == reviewer.class.name }
772
+ end
773
+
774
+ # Find or build
775
+ def build_applicant_review(reviewer:)
776
+ applicant_review(reviewer: reviewer) || applicant_reviews.build(reviewer: reviewer)
777
+ end
778
+
761
779
  # When an application is completed, these must be done to go to reviewed
762
780
  # An Admin can override this and just set them to reviewed.
763
781
  def reviewed_requirements
@@ -765,17 +783,22 @@ module EffectiveMembershipsApplicant
765
783
  return requirements unless category.present?
766
784
 
767
785
  if applicant_reviews_required?
768
- requirements['Applicant Reviews'] = (applicant_reviews.count(&:completed?) >= min_applicant_reviews)
786
+ requirements['Applicant Reviews'] = (applicant_reviews.count(&:done?) >= min_applicant_reviews)
769
787
  end
770
788
 
771
789
  requirements
772
790
  end
773
791
 
792
+ # Called when an applicant_review was submitted
793
+ def try_reviewed!
794
+ return false unless completed? && reviewed_requirements.values.all?
795
+ reviewed!
796
+ end
797
+
774
798
  def review!
775
799
  raise('applicant must have been submitted to review!') unless was_submitted?
776
800
 
777
801
  # Let an admin ignore these requirements if need be
778
- # return false unless completed? && reviewed_requirements.values.all?
779
802
  reviewed!
780
803
  end
781
804
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  # EffectiveMembershipsApplicantReview
4
4
  #
5
- # Mark your category model with effective_memberships_applicant_review to get all the includes
5
+ # Mark your owner model with effective_memberships_applicant_review to get all the includes
6
6
 
7
7
  module EffectiveMembershipsApplicantReview
8
8
  extend ActiveSupport::Concern
@@ -22,82 +22,60 @@ module EffectiveMembershipsApplicantReview
22
22
  end
23
23
 
24
24
  def recommendations
25
- ['Accept', 'Reject']
25
+ ['Recommend Approve', 'Recommend Decline', 'Missing Information']
26
26
  end
27
27
 
28
28
  end
29
29
 
30
30
  included do
31
- log_changes if respond_to?(:log_changes)
31
+ log_changes(to: :applicant) if respond_to?(:log_changes)
32
32
 
33
33
  acts_as_tokened
34
34
 
35
35
  acts_as_statused(
36
- :draft, # Just Started
37
- :conflicted, # Conflict of Interest
38
- :accepted, # Accepted
39
- :rejected # Rejected
36
+ :draft, # Just Started
37
+ :submitted # All Done
40
38
  )
41
39
 
42
40
  acts_as_wizard(
43
41
  start: 'Start',
44
- conflict: 'Conflict of Interest',
45
- education: 'Education',
46
- course_amounts: 'Courses',
47
- experience: 'Work Experience',
48
- references: 'References',
49
- files: 'Attach Files',
50
- declarations: 'Declarations',
42
+ review: 'Review Applicant',
51
43
  recommendation: 'Recommendation',
52
44
  submitted: 'Submitted'
53
45
  )
54
46
 
47
+ scope :in_progress, -> { where(status: :draft) }
48
+ scope :done, -> { where(status: :submitted) }
49
+
55
50
  belongs_to :applicant
56
51
  belongs_to :reviewer, polymorphic: true
57
52
 
58
53
  effective_resource do
59
54
  submitted_at :datetime
60
- recommendation :string
61
55
 
56
+ recommendation :string
62
57
  comments :text # Rolling comments
63
58
 
64
- # Conflict Step
65
- conflict_of_interest :boolean
66
-
67
- # Education Step
68
- education_accepted :boolean
69
-
70
- # Course Amounts
71
- course_amounts_accepted :boolean
72
-
73
- # Courses
74
- courses_accepted :boolean
75
-
76
- # Experience Step
77
- experience_accepted :boolean
78
-
79
- # References Step
80
- references_accepted :boolean
81
-
82
- # References Step
83
- files_accepted :boolean
84
-
85
59
  timestamps
86
60
  end
87
61
 
88
- scope :deep, -> { includes(:reviewer, applicant: :owner) }
62
+ scope :deep, -> { includes(:reviewer, applicant: :user) }
89
63
 
90
- with_options(if: -> { current_step == :conflict }) do
91
- validates :conflict_of_interest, inclusion: { in: [true, false] }
92
- validates :comments, presence: true, if: -> { conflict_of_interest? }
93
- end
64
+ scope :for, -> (user) {
65
+ raise('expected a effective memberships user') unless user.class.try(:effective_memberships_user?)
66
+ where(reviewer: user)
67
+ }
94
68
 
95
- validates :recommendation, absence: true, if: -> { done? && conflict_of_interest? }
69
+ with_options(if: -> { submitted? }) do
70
+ validates :recommendation, presence: true
96
71
 
97
- after_commit(on: :create, if: -> { applicant.completed? }) { notify! }
72
+ validate do
73
+ self.errors.add(:recommendation, 'is invalid') unless EffectiveMemberships.ApplicantReview.recommendations.include?(recommendation)
74
+ end
75
+ end
98
76
 
99
77
  def to_s
100
- 'applicant review'
78
+ "Review of #{applicant}"
101
79
  end
102
80
 
103
81
  def in_progress?
@@ -105,43 +83,31 @@ module EffectiveMembershipsApplicantReview
105
83
  end
106
84
 
107
85
  def done?
108
- !draft?
86
+ submitted?
109
87
  end
110
88
 
111
- def conflict_of_interest!
112
- update!(conflict_of_interest: true, recommendation: nil)
113
- conflicted!
114
-
115
- applicant.save!
116
-
117
- after_commit { send_email(:applicant_review_conflict_of_interest) }
118
- true
89
+ def can_visit_step?(step)
90
+ return (step == :submitted) if was_submitted?
91
+ can_revisit_completed_steps(step)
119
92
  end
120
93
 
121
- def accept!
122
- assign_attributes(recommendation: 'Accept')
123
- accepted!
124
-
125
- applicant.save!
126
-
127
- after_commit { send_email(:applicant_review_completed) }
128
- true
94
+ # Called by the recommendation wizard step
95
+ def recommendation!
96
+ submit!
129
97
  end
130
98
 
131
- def reject!
132
- assign_attributes(recommendation: 'Reject')
133
- rejected!
134
-
135
- applicant.save!
99
+ def submit!
100
+ submitted!
101
+ applicant.try_reviewed!
136
102
 
137
- after_commit { send_email(:applicant_review_completed) }
103
+ after_commit { send_email(:applicant_review_submitted) }
138
104
  true
139
105
  end
140
106
 
141
107
  private
142
108
 
143
109
  def send_email(email)
144
- EffectiveMemberships.send_email(email, self, email_form_params) unless email_form_skip?
110
+ EffectiveMemberships.send_email(email, self)
145
111
  end
146
112
 
147
113
  end
@@ -43,6 +43,11 @@ module EffectiveMembershipsOwner
43
43
  without_role(:member).where(id: removed.select(:owner_id))
44
44
  }
45
45
 
46
+ scope :membership_good_standing, -> {
47
+ bad_standing = Effective::Membership.where(bad_standing: false).where(owner_type: name)
48
+ where(id: bad_standing.select(:owner_id))
49
+ }
50
+
46
51
  scope :membership_bad_standing, -> {
47
52
  bad_standing = Effective::Membership.where(bad_standing: true).where(owner_type: name)
48
53
  where(id: bad_standing.select(:owner_id))
@@ -71,6 +76,20 @@ module EffectiveMembershipsOwner
71
76
  where(id: memberships.where(owner_type: name).select(:owner_id))
72
77
  }
73
78
 
79
+ scope :membership_joined_before, -> (date) {
80
+ raise('expected a date') unless date.respond_to?(:strftime)
81
+
82
+ joined_before = Effective::Membership.joined_before(date).where(owner_type: name)
83
+ where(id: joined_before.select(:owner_id))
84
+ }
85
+
86
+ scope :membership_joined_after, -> (date) {
87
+ raise('expected a date') unless date.respond_to?(:strftime)
88
+
89
+ joined_after = Effective::Membership.joined_after(date).where(owner_type: name)
90
+ where(id: joined_after.select(:owner_id))
91
+ }
92
+
74
93
  end
75
94
 
76
95
  def assign_member_role
@@ -21,6 +21,7 @@ module EffectiveMembershipsUser
21
21
  # App scoped
22
22
  has_many :applicants, -> { order(:id) }, inverse_of: :user, as: :user
23
23
  has_many :fee_payments, -> { order(:id) }, inverse_of: :user, as: :user
24
+ has_many :applicant_reviews, -> { order(:id) }, inverse_of: :reviewer, as: :reviewer
24
25
 
25
26
  # Effective Scoped
26
27
  has_many :representatives, -> { Effective::Representative.sorted },
@@ -85,4 +86,9 @@ module EffectiveMembershipsUser
85
86
  membership_history_on(date).try(:membership_categories)
86
87
  end
87
88
 
89
+ # Roles
90
+ def applicant_reviewer?
91
+ false
92
+ end
93
+
88
94
  end
@@ -36,6 +36,9 @@ module Effective
36
36
 
37
37
  scope :good_standing, -> { where(bad_standing: [nil, false]) }
38
38
 
39
+ scope :joined_before, -> (date) { where(arel_table[:joined_on].lt(date)) }
40
+ scope :joined_after, -> (date) { where(arel_table[:joined_on].gteq(date)) }
41
+
39
42
  scope :with_paid_fees_through, -> (period = nil) {
40
43
  where(arel_table[:fees_paid_period].gteq(period || EffectiveMemberships.Registrar.current_period))
41
44
  }
@@ -0,0 +1 @@
1
+ = render 'effective/applicant_reviews/applicant_review', applicant_review: applicant_review
@@ -17,6 +17,10 @@
17
17
  = render 'effective/applicants/applicant', applicant: applicant, namespace: :admin
18
18
 
19
19
  - # Just normal tabs now
20
+ - if applicant.applicant_reviews.present?
21
+ = tab 'Reviews' do
22
+ .mb-4= render_inline_datatable(Admin::EffectiveApplicantReviewsDatatable.new(applicant: applicant))
23
+
20
24
  - if applicant.was_submitted? && !applicant.was_approved?
21
25
  = tab 'Process' do
22
26
  = render 'admin/applicants/form_process', applicant: applicant
@@ -107,10 +107,10 @@
107
107
  - if applicant.reviewed_requirements['Applicant Reviews']
108
108
  = icon('check', class: 'small-1')
109
109
 
110
- = applicant.applicant_reviews.count(&:completed?)
110
+ = applicant.applicant_reviews.count(&:submitted?)
111
111
  = '/'
112
112
  = applicant.min_applicant_reviews
113
- Reviews Completed
113
+ Reviews Submitted
114
114
  - else
115
115
  %p
116
116
  = icon('check', class: 'small-1')
@@ -1,2 +1,2 @@
1
1
  = card do
2
- %p Thank you! The confidential endorsement has been completed.
2
+ %p Thank you. The confidential endorsement has been completed.
@@ -1,2 +1,2 @@
1
1
  = card do
2
- %p Thank you! The confidential reference has been completed.
2
+ %p Thank you. The confidential reference has been completed.
@@ -0,0 +1,4 @@
1
+ .effective-applicant-review
2
+ - applicant_review.render_steps.each do |partial|
3
+ - applicant_review.render_step = partial
4
+ = render "effective/applicant_reviews/#{partial}", applicant_review: applicant_review
@@ -0,0 +1,8 @@
1
+ - all_steps_content = resource.applicant&.category&.rich_text_applicant_review_all_steps_content
2
+ - step_content = resource.applicant&.category&.send("rich_text_applicant_review_#{step}_content")
3
+
4
+ - if all_steps_content.present?
5
+ .mb-2= all_steps_content
6
+
7
+ - if step_content.present?
8
+ .mb-2= step_content
@@ -0,0 +1,36 @@
1
+ - authorized = EffectiveResources.authorized?(self, :new, EffectiveMemberships.ApplicantReview)
2
+
3
+ - available = EffectiveResources.best('EffectiveAvailableApplicantReviewsDatatable').new(self)
4
+ - datatable = EffectiveResources.best('EffectiveApplicantReviewsDatatable').new(self)
5
+
6
+ - # In progress
7
+ - applicant_review = current_user.applicant_reviews.in_progress.first
8
+
9
+ - if applicant_review.present? && applicant_review.draft?
10
+ %h2 In-Progress Applicant Review
11
+
12
+ %p
13
+ Your review of #{applicant_review} is incomplete.
14
+
15
+ %p
16
+ Please
17
+ = link_to("Continue review", effective_memberships.applicant_applicant_review_build_path(applicant_review.applicant, applicant_review, applicant_review.next_step), 'data-turbolinks' => false, class: 'btn btn-primary')
18
+ or you can
19
+ = link_to('Abandon review', effective_memberships.applicant_applicant_review_path(applicant_review.applicant, applicant_review), 'data-confirm': "Really delete #{applicant_review}?", 'data-method': :delete, class: 'btn btn-danger')
20
+ to review again.
21
+
22
+ %hr
23
+
24
+ %h2 Applicant Reviews
25
+
26
+ - if available.present?
27
+ %p The following applicants are available for your review:
28
+
29
+ = render_simple_datatable(available)
30
+ - else
31
+ %p There are no applicants available for your review. When there are, we'll show them here.
32
+
33
+ - if datatable.present?
34
+ .mt-4
35
+ %h2 Review History
36
+ = render_datatable(datatable, simple: true)
@@ -0,0 +1,3 @@
1
+ .row
2
+ .col-lg-3.mb-3= render_wizard_sidebar(resource)
3
+ .col-lg-9= yield
@@ -0,0 +1,23 @@
1
+ = wizard_card(applicant_review) do
2
+ %table.table
3
+ - if request.path.start_with?('/admin')
4
+ %tr
5
+ %th Reviewer
6
+ %td
7
+ - url = (polymorphic_admin_path(applicant_review.reviewer) rescue "/admin/users/#{applicant_review.reviewer.to_param}/edit")
8
+ = link_to(applicant_review.reviewer, url)
9
+ - else
10
+ %tr
11
+ %th Reviewer
12
+ %td
13
+ = applicant_review.reviewer.to_s
14
+ %br
15
+ = mail_to(applicant_review.reviewer.email)
16
+
17
+ %tr
18
+ %th Recommendation
19
+ %td= applicant_review.recommendation
20
+
21
+ %tr
22
+ %th Comments
23
+ %td= simple_format(applicant_review.comments.presence || 'none')
@@ -0,0 +1 @@
1
+ - # Intentionally blank
@@ -0,0 +1,20 @@
1
+ = render 'layout' do
2
+ = render 'effective/applicant_reviews/content', resource: resource
3
+
4
+ = card('Summary') do
5
+ = render 'effective/applicants/summary', applicant: resource.applicant
6
+
7
+ .mb-2
8
+ = collapse('Show application...', card_class: 'my-2') do
9
+ = render 'effective/applicants/applicant', applicant: resource.applicant
10
+
11
+ = card('Recommendation') do
12
+
13
+ = effective_form_with(model: resource, url: wizard_path(step), method: :put) do |f|
14
+ = f.hidden_field :id
15
+
16
+ %p My recommendation is
17
+ = f.radios :recommendation, EffectiveMemberships.ApplicantReview.recommendations, label: false
18
+ = f.text_area :comments, label: 'Reviewer comments'
19
+
20
+ = f.save 'Submit Recommendation'
@@ -0,0 +1,14 @@
1
+ = render 'layout' do
2
+ = render 'effective/applicant_reviews/content', resource: resource
3
+
4
+ = card do
5
+ = render 'effective/applicants/summary', applicant: resource.applicant
6
+ = render 'effective/applicants/applicant', applicant: resource.applicant
7
+
8
+ = card do
9
+ = effective_form_with(model: resource, url: wizard_path(step), method: :put) do |f|
10
+ = f.hidden_field :id
11
+
12
+ = f.text_area :comments, label: 'Reviewer comments'
13
+
14
+ = f.save 'Save and Continue'
@@ -0,0 +1,18 @@
1
+ = render 'layout' do
2
+ = render 'effective/applicant_reviews/content', resource: resource
3
+
4
+ = card do
5
+ %p Welcome #{current_user}
6
+
7
+ %p You are starting an applicant review for #{resource.applicant}.
8
+
9
+ = effective_form_with(model: resource, url: wizard_path(step), method: :put) do |f|
10
+ = f.hidden_field :id
11
+
12
+ = f.hidden_field :applicant_type, value: f.object.applicant.class.name
13
+ = f.hidden_field :applicant_id
14
+
15
+ = f.hidden_field :reviewer_type
16
+ = f.hidden_field :reviewer_id
17
+
18
+ = f.save 'Save and Continue'
@@ -0,0 +1,15 @@
1
+ = render 'layout' do
2
+ = render 'effective/applicant_reviews/content', resource: resource
3
+
4
+ - raise('expected a submitted applicant review') unless resource.was_submitted?
5
+
6
+ .alert.alert-success.mb-4
7
+ This review was submitted on #{resource.submitted_at.strftime('%F')}
8
+
9
+ .mb-4
10
+ = collapse('Show application...', card_class: 'my-2') do
11
+ = render 'effective/applicants/applicant', applicant: resource.applicant
12
+
13
+ = render 'effective/applicant_reviews/applicant_review', applicant_review: resource
14
+
15
+ = link_to "Return to Dashboard", root_path, class: 'btn btn-lg btn-primary btn-block'
@@ -54,11 +54,12 @@
54
54
  %th Stream
55
55
  %td= applicant.stream
56
56
 
57
- - if applicant.orders.present?
57
+ - orders = applicant.orders.select { |order| EffectiveResources.authorized?(self, :show, order) }
58
+ - if orders.present?
58
59
  %tr
59
- %th Order#{'s' if applicant.orders.length > 1}
60
+ %th Order#{'s' if orders.length > 1}
60
61
  %td
61
- - applicant.orders.each do |order|
62
+ - orders.each do |order|
62
63
  - if request.path.start_with?('/admin')
63
64
  = link_to(order, effective_orders.edit_admin_order_path(order))
64
65
  - else
@@ -31,4 +31,4 @@
31
31
  %p= link_to 'Pay Fees', effective_memberships.new_fee_payment_path, class: 'btn btn-primary'
32
32
 
33
33
  - else
34
- %p You have no outstanding fees at this time. Thank you!
34
+ %p You have no outstanding fees at this time. Thank you.
@@ -1,10 +1,10 @@
1
1
  ---
2
- subject: 'Your application was approved!'
2
+ subject: 'Your application was approved'
3
3
  from: 'admin@example.com'
4
4
  ---
5
5
  Hello {{ user.name }},
6
6
 
7
- Your application for {{ applicant.to_category }} was approved!
7
+ Your application for {{ applicant.to_category }} was approved.
8
8
 
9
9
  Please log in and purchase any outstanding fees.
10
10
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- subject: 'Your application was marked as completed!'
2
+ subject: 'Your application was marked as completed'
3
3
  from: 'admin@example.com'
4
4
  ---
5
5
  Hello {{ user.name }},
@@ -0,0 +1,19 @@
1
+ ---
2
+ subject: 'An applicant review has been submitted'
3
+ from: 'admin@example.com'
4
+ ---
5
+ Hello admin,
6
+
7
+ The application for {{ user.name }} for {{ applicant.to_category }} has been reviewed by #{{ reviewer.name }}
8
+
9
+ They recommended {{ applicant_review.recommendation }} with the following comments:
10
+
11
+ {{ applicant_review.comments }}
12
+
13
+ For more details please visit
14
+
15
+ {{ applicant.admin_url }}
16
+
17
+ Thanks,
18
+
19
+ Have a great day
@@ -0,0 +1,15 @@
1
+ ---
2
+ subject: 'Your application was successfully submitted'
3
+ from: 'admin@example.com'
4
+ ---
5
+ Hello {{ user.name }},
6
+
7
+ We have received your application for {{ applicant.to_category }} #{{ applicant.to_status }}
8
+
9
+ For more details please visit
10
+
11
+ {{ applicant.url }}
12
+
13
+ Thank you.
14
+
15
+ Please contact us for assistance.
data/config/routes.rb CHANGED
@@ -7,6 +7,10 @@ EffectiveMemberships::Engine.routes.draw do
7
7
  scope module: 'effective' do
8
8
  resources :applicants, only: [:new, :show, :destroy] do
9
9
  resources :build, controller: :applicants, only: [:show, :update]
10
+
11
+ resources :applicant_reviews, only: [:new, :show, :destroy] do
12
+ resources :build, controller: :applicant_reviews, only: [:show, :update]
13
+ end
10
14
  end
11
15
 
12
16
  resources :applicant_endorsements, only: [:new, :create, :show, :update] do
@@ -45,6 +49,8 @@ EffectiveMemberships::Engine.routes.draw do
45
49
  post :notify, on: :member
46
50
  end
47
51
 
52
+ resources :applicant_reviews, only: [:index, :show]
53
+
48
54
  resources :fees
49
55
  resources :categories, only: [:index, :edit, :update]
50
56
 
@@ -1,3 +1,3 @@
1
1
  module EffectiveMemberships
2
- VERSION = '0.6.13'
2
+ VERSION = '0.7.1'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: effective_memberships
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.13
4
+ version: 0.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Code and Effect
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-09-14 00:00:00.000000000 Z
11
+ date: 2022-09-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -258,6 +258,7 @@ files:
258
258
  - app/controllers/admin/applicant_course_names_controller.rb
259
259
  - app/controllers/admin/applicant_endorsements_controller.rb
260
260
  - app/controllers/admin/applicant_references_controller.rb
261
+ - app/controllers/admin/applicant_reviews_controller.rb
261
262
  - app/controllers/admin/applicants_controller.rb
262
263
  - app/controllers/admin/categories_controller.rb
263
264
  - app/controllers/admin/fee_payments_controller.rb
@@ -270,6 +271,7 @@ files:
270
271
  - app/controllers/admin/statuses_controller.rb
271
272
  - app/controllers/effective/applicant_endorsements_controller.rb
272
273
  - app/controllers/effective/applicant_references_controller.rb
274
+ - app/controllers/effective/applicant_reviews_controller.rb
273
275
  - app/controllers/effective/applicants_controller.rb
274
276
  - app/controllers/effective/fee_payments_controller.rb
275
277
  - app/controllers/effective/membership_cards_controller.rb
@@ -280,6 +282,7 @@ files:
280
282
  - app/datatables/admin/effective_applicant_course_names_datatable.rb
281
283
  - app/datatables/admin/effective_applicant_endorsements_datatable.rb
282
284
  - app/datatables/admin/effective_applicant_references_datatable.rb
285
+ - app/datatables/admin/effective_applicant_reviews_datatable.rb
283
286
  - app/datatables/admin/effective_applicants_datatable.rb
284
287
  - app/datatables/admin/effective_categories_datatable.rb
285
288
  - app/datatables/admin/effective_fee_payments_datatable.rb
@@ -296,7 +299,9 @@ files:
296
299
  - app/datatables/effective_applicant_equivalences_datatable.rb
297
300
  - app/datatables/effective_applicant_experiences_datatable.rb
298
301
  - app/datatables/effective_applicant_references_datatable.rb
302
+ - app/datatables/effective_applicant_reviews_datatable.rb
299
303
  - app/datatables/effective_applicants_datatable.rb
304
+ - app/datatables/effective_available_applicant_reviews_datatable.rb
300
305
  - app/datatables/effective_fee_payments_datatable.rb
301
306
  - app/datatables/effective_memberships_directory_datatable.rb
302
307
  - app/datatables/effective_organizations_datatable.rb
@@ -342,6 +347,7 @@ files:
342
347
  - app/views/admin/applicant_course_name/_form.html.haml
343
348
  - app/views/admin/applicant_endorsements/_applicant_endorsement.html.haml
344
349
  - app/views/admin/applicant_references/_applicant_reference.html.haml
350
+ - app/views/admin/applicant_reviews/_applicant_review.html.haml
345
351
  - app/views/admin/applicants/_form.html.haml
346
352
  - app/views/admin/applicants/_form_applicant.html.haml
347
353
  - app/views/admin/applicants/_form_approve.html.haml
@@ -395,6 +401,16 @@ files:
395
401
  - app/views/effective/applicant_references/_form_declaration.html.haml
396
402
  - app/views/effective/applicant_references/complete.html.haml
397
403
  - app/views/effective/applicant_references/edit.html.haml
404
+ - app/views/effective/applicant_reviews/_applicant_review.html.haml
405
+ - app/views/effective/applicant_reviews/_content.html.haml
406
+ - app/views/effective/applicant_reviews/_dashboard.html.haml
407
+ - app/views/effective/applicant_reviews/_layout.html.haml
408
+ - app/views/effective/applicant_reviews/_recommendation.html.haml
409
+ - app/views/effective/applicant_reviews/_review.html.haml
410
+ - app/views/effective/applicant_reviews/recommendation.html.haml
411
+ - app/views/effective/applicant_reviews/review.html.haml
412
+ - app/views/effective/applicant_reviews/start.html.haml
413
+ - app/views/effective/applicant_reviews/submitted.html.haml
398
414
  - app/views/effective/applicants/_applicant.html.haml
399
415
  - app/views/effective/applicants/_content.html.haml
400
416
  - app/views/effective/applicants/_course_amounts.html.haml
@@ -470,6 +486,8 @@ files:
470
486
  - app/views/effective/memberships_mailer/applicant_endorsement_notification.liquid
471
487
  - app/views/effective/memberships_mailer/applicant_missing_info.liquid
472
488
  - app/views/effective/memberships_mailer/applicant_reference_notification.liquid
489
+ - app/views/effective/memberships_mailer/applicant_review_submitted.liquid
490
+ - app/views/effective/memberships_mailer/applicant_submitted.liquid
473
491
  - app/views/effective/organizations/_dashboard.html.haml
474
492
  - app/views/effective/organizations/_form.html.haml
475
493
  - app/views/effective/organizations/_form_organization.html.haml