effective_cpd 0.0.1 → 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +89 -10
- data/app/controllers/admin/cpd_audit_level_questions_controller.rb +13 -0
- data/app/controllers/admin/cpd_audit_levels_controller.rb +13 -0
- data/app/controllers/admin/cpd_audit_reviews_controller.rb +13 -0
- data/app/controllers/admin/cpd_audits_controller.rb +41 -0
- data/app/controllers/effective/cpd_audit_reviews_controller.rb +60 -0
- data/app/controllers/effective/cpd_audits_controller.rb +55 -0
- data/app/controllers/effective/cpd_cycles_controller.rb +1 -0
- data/app/controllers/effective/cpd_statements_controller.rb +1 -3
- data/app/datatables/admin/effective_cpd_audit_level_questions_datatable.rb +30 -0
- data/app/datatables/admin/effective_cpd_audit_levels_datatable.rb +34 -0
- data/app/datatables/admin/effective_cpd_audit_reviews_datatable.rb +29 -0
- data/app/datatables/admin/effective_cpd_audits_datatable.rb +67 -0
- data/app/datatables/admin/effective_cpd_statements_datatable.rb +3 -3
- data/app/datatables/effective_cpd_available_audit_reviews_datatable.rb +37 -0
- data/app/datatables/effective_cpd_available_audits_datatable.rb +30 -0
- data/app/datatables/{effective_cpd_datatable.rb → effective_cpd_available_cycles_datatable.rb} +3 -4
- data/app/datatables/effective_cpd_completed_audit_reviews_datatable.rb +32 -0
- data/app/datatables/effective_cpd_completed_audits_datatable.rb +24 -0
- data/app/datatables/effective_cpd_completed_statements_datatable.rb +28 -0
- data/app/helpers/effective_cpd_audits_helper.rb +48 -0
- data/app/mailers/effective/cpd_mailer.rb +155 -3
- data/app/models/effective/cpd_activity.rb +16 -3
- data/app/models/effective/cpd_audit.rb +380 -0
- data/app/models/effective/cpd_audit_level.rb +87 -0
- data/app/models/effective/cpd_audit_level_question.rb +91 -0
- data/app/models/effective/cpd_audit_level_question_option.rb +34 -0
- data/app/models/effective/cpd_audit_level_section.rb +50 -0
- data/app/models/effective/cpd_audit_response.rb +86 -0
- data/app/models/effective/cpd_audit_response_option.rb +6 -0
- data/app/models/effective/cpd_audit_review.rb +222 -0
- data/app/models/effective/cpd_audit_review_item.rb +34 -0
- data/app/models/effective/cpd_category.rb +12 -3
- data/app/models/effective/cpd_cycle.rb +7 -0
- data/app/models/effective/cpd_rule.rb +3 -1
- data/app/models/effective/cpd_statement.rb +15 -8
- data/app/views/admin/cpd_activities/_form.html.haml +1 -1
- data/app/views/admin/cpd_audit_level_questions/_form.html.haml +100 -0
- data/app/views/admin/cpd_audit_levels/_form.html.haml +24 -0
- data/app/views/admin/cpd_audit_levels/_form_content_audit.html.haml +15 -0
- data/app/views/admin/cpd_audit_levels/_form_content_audit_review.html.haml +15 -0
- data/app/views/admin/cpd_audit_levels/_form_cpd_audit_level.html.haml +52 -0
- data/app/views/admin/cpd_audit_levels/_form_cpd_audit_level_section.html.haml +10 -0
- data/app/views/admin/cpd_audit_reviews/_cpd_audit_review.html.haml +1 -0
- data/app/views/admin/cpd_audit_reviews/_form.html.haml +13 -0
- data/app/views/admin/cpd_audits/_audit_reviewer_fields.html.haml +2 -0
- data/app/views/admin/cpd_audits/_auditee_fields.html.haml +2 -0
- data/app/views/admin/cpd_audits/_form.html.haml +46 -0
- data/app/views/admin/cpd_audits/_form_conflict.html.haml +24 -0
- data/app/views/admin/cpd_audits/_form_determination.html.haml +10 -0
- data/app/views/admin/cpd_audits/_form_exemption.html.haml +24 -0
- data/app/views/admin/cpd_audits/_form_extension.html.haml +24 -0
- data/app/views/admin/cpd_audits/_form_new.html.haml +28 -0
- data/app/views/admin/cpd_audits/_status.html.haml +121 -0
- data/app/views/admin/cpd_categories/_form.html.haml +1 -1
- data/app/views/admin/cpd_cycles/_form.html.haml +4 -4
- data/app/views/admin/cpd_cycles/_form_cpd_cycle.html.haml +3 -0
- data/app/views/admin/cpd_statements/_cpd_statement.html.haml +7 -0
- data/app/views/effective/cpd_audit_level_questions/_cpd_audit_level_question.html.haml +5 -0
- data/app/views/effective/cpd_audit_responses/_cpd_audit_response.html.haml +4 -0
- data/app/views/effective/cpd_audit_responses/_fields.html.haml +13 -0
- data/app/views/effective/cpd_audit_responses/fields/_choose_one.html.haml +1 -0
- data/app/views/effective/cpd_audit_responses/fields/_date.html.haml +2 -0
- data/app/views/effective/cpd_audit_responses/fields/_email.html.haml +2 -0
- data/app/views/effective/cpd_audit_responses/fields/_long_answer.html.haml +1 -0
- data/app/views/effective/cpd_audit_responses/fields/_number.html.haml +2 -0
- data/app/views/effective/cpd_audit_responses/fields/_select_all_that_apply.html.haml +1 -0
- data/app/views/effective/cpd_audit_responses/fields/_select_up_to_1.html.haml +1 -0
- data/app/views/effective/cpd_audit_responses/fields/_select_up_to_2.html.haml +1 -0
- data/app/views/effective/cpd_audit_responses/fields/_select_up_to_3.html.haml +1 -0
- data/app/views/effective/cpd_audit_responses/fields/_select_up_to_4.html.haml +1 -0
- data/app/views/effective/cpd_audit_responses/fields/_select_up_to_5.html.haml +1 -0
- data/app/views/effective/cpd_audit_responses/fields/_short_answer.html.haml +1 -0
- data/app/views/effective/cpd_audit_responses/fields/_upload_file.html.haml +1 -0
- data/app/views/effective/cpd_audit_responses/responses/_choose_one.html.haml +1 -0
- data/app/views/effective/cpd_audit_responses/responses/_date.html.haml +1 -0
- data/app/views/effective/cpd_audit_responses/responses/_email.html.haml +1 -0
- data/app/views/effective/cpd_audit_responses/responses/_long_answer.html.haml +1 -0
- data/app/views/effective/cpd_audit_responses/responses/_number.html.haml +1 -0
- data/app/views/effective/cpd_audit_responses/responses/_select_all_that_apply.html.haml +5 -0
- data/app/views/effective/cpd_audit_responses/responses/_select_up_to_1.html.haml +1 -0
- data/app/views/effective/cpd_audit_responses/responses/_select_up_to_2.html.haml +5 -0
- data/app/views/effective/cpd_audit_responses/responses/_select_up_to_3.html.haml +5 -0
- data/app/views/effective/cpd_audit_responses/responses/_select_up_to_4.html.haml +5 -0
- data/app/views/effective/cpd_audit_responses/responses/_select_up_to_5.html.haml +5 -0
- data/app/views/effective/cpd_audit_responses/responses/_short_answer.html.haml +1 -0
- data/app/views/effective/cpd_audit_responses/responses/_upload_file.html.haml +4 -0
- data/app/views/effective/cpd_audit_review_items/_cpd_audit_review_item.html.haml +6 -0
- data/app/views/effective/cpd_audit_review_items/_fields.html.haml +11 -0
- data/app/views/effective/cpd_audit_reviews/_conflict.html.haml +15 -0
- data/app/views/effective/cpd_audit_reviews/_cpd_audit_level_section.html.haml +23 -0
- data/app/views/effective/cpd_audit_reviews/_cpd_audit_review.html.haml +17 -0
- data/app/views/effective/cpd_audit_reviews/_cpd_statement.html.haml +15 -0
- data/app/views/effective/cpd_audit_reviews/_layout.html.haml +24 -0
- data/app/views/effective/cpd_audit_reviews/_recommendation.html.haml +9 -0
- data/app/views/effective/cpd_audit_reviews/_summary.html.haml +47 -0
- data/app/views/effective/cpd_audit_reviews/complete.html.haml +20 -0
- data/app/views/effective/cpd_audit_reviews/conflict.html.haml +21 -0
- data/app/views/effective/cpd_audit_reviews/cpd_audit_level_section.html.haml +43 -0
- data/app/views/effective/cpd_audit_reviews/cpd_statement.html.haml +35 -0
- data/app/views/effective/cpd_audit_reviews/information.html.haml +5 -0
- data/app/views/effective/cpd_audit_reviews/instructions.html.haml +10 -0
- data/app/views/effective/cpd_audit_reviews/questionnaire.html.haml +8 -0
- data/app/views/effective/cpd_audit_reviews/recommendation.html.haml +21 -0
- data/app/views/effective/cpd_audit_reviews/start.html.haml +10 -0
- data/app/views/effective/cpd_audit_reviews/statements.html.haml +29 -0
- data/app/views/effective/cpd_audit_reviews/submit.html.haml +14 -0
- data/app/views/effective/cpd_audit_reviews/waiting.html.haml +9 -0
- data/app/views/effective/cpd_audits/_conflict.html.haml +15 -0
- data/app/views/effective/cpd_audits/_cpd_audit.html.haml +12 -0
- data/app/views/effective/cpd_audits/_cpd_audit_level_section.html.haml +14 -0
- data/app/views/effective/cpd_audits/_exemption.html.haml +26 -0
- data/app/views/effective/cpd_audits/_extension.html.haml +30 -0
- data/app/views/effective/cpd_audits/_files.html.haml +12 -0
- data/app/views/effective/cpd_audits/_layout.html.haml +33 -0
- data/app/views/effective/cpd_audits/_summary.html.haml +54 -0
- data/app/views/effective/cpd_audits/_waiting.html.haml +6 -0
- data/app/views/effective/cpd_audits/complete.html.haml +24 -0
- data/app/views/effective/cpd_audits/conflict.html.haml +22 -0
- data/app/views/effective/cpd_audits/cpd_audit_level_section.html.haml +20 -0
- data/app/views/effective/cpd_audits/exemption.html.haml +19 -0
- data/app/views/effective/cpd_audits/extension.html.haml +21 -0
- data/app/views/effective/cpd_audits/files.html.haml +7 -0
- data/app/views/effective/cpd_audits/information.html.haml +5 -0
- data/app/views/effective/cpd_audits/instructions.html.haml +5 -0
- data/app/views/effective/cpd_audits/questionnaire.html.haml +5 -0
- data/app/views/effective/cpd_audits/start.html.haml +11 -0
- data/app/views/effective/cpd_audits/submit.html.haml +15 -0
- data/app/views/effective/cpd_audits/waiting.html.haml +24 -0
- data/app/views/effective/cpd_mailer/README.md +1 -0
- data/app/views/effective/cpd_mailer/cpd_audit_closed.liquid +15 -0
- data/app/views/effective/cpd_mailer/cpd_audit_conflict_resolved.liquid +15 -0
- data/app/views/effective/cpd_mailer/cpd_audit_conflicted.liquid +13 -0
- data/app/views/effective/cpd_mailer/cpd_audit_exemption_denied.liquid +13 -0
- data/app/views/effective/cpd_mailer/cpd_audit_exemption_granted.liquid +13 -0
- data/app/views/effective/cpd_mailer/cpd_audit_exemption_request.liquid +13 -0
- data/app/views/effective/cpd_mailer/cpd_audit_extension_denied.liquid +13 -0
- data/app/views/effective/cpd_mailer/cpd_audit_extension_granted.liquid +13 -0
- data/app/views/effective/cpd_mailer/cpd_audit_extension_request.liquid +13 -0
- data/app/views/effective/cpd_mailer/cpd_audit_opened.liquid +13 -0
- data/app/views/effective/cpd_mailer/cpd_audit_review_opened.liquid +15 -0
- data/app/views/effective/cpd_mailer/cpd_audit_review_ready.liquid +13 -0
- data/app/views/effective/cpd_mailer/cpd_audit_review_submitted.liquid +11 -0
- data/app/views/effective/cpd_mailer/cpd_audit_reviewed.liquid +13 -0
- data/app/views/effective/cpd_mailer/cpd_audit_submitted.liquid +13 -0
- data/app/views/effective/cpd_statement_activities/_cpd_statement_activity.html.haml +65 -0
- data/app/views/effective/cpd_statement_activities/_form.html.haml +9 -10
- data/app/views/effective/cpd_statements/_activities.html.haml +3 -64
- data/app/views/effective/cpd_statements/_activities_edit.html.haml +2 -0
- data/app/views/effective/cpd_statements/_activities_table.html.haml +64 -0
- data/app/views/effective/cpd_statements/_agreements.html.haml +19 -5
- data/app/views/effective/cpd_statements/_cpd_statement.html.haml +6 -3
- data/app/views/effective/cpd_statements/_summary.html.haml +26 -31
- data/app/views/effective/cpd_statements/activities.html.haml +1 -1
- data/app/views/effective/cpd_statements/agreements.html.haml +7 -1
- data/app/views/effective/cpd_statements/submit.html.haml +1 -1
- data/config/effective_cpd.rb +47 -10
- data/config/routes.rb +18 -1
- data/db/migrate/01_create_effective_cpd.rb.erb +157 -1
- data/db/seeds.rb +2 -1
- data/lib/effective_cpd.rb +42 -3
- data/lib/effective_cpd/version.rb +1 -1
- data/lib/generators/effective_cpd/install_generator.rb +16 -3
- metadata +168 -9
- data/app/datatables/effective_cpd_statements_datatable.rb +0 -23
- data/app/views/admin/cpd_statements/_form.html.haml +0 -6
@@ -0,0 +1,91 @@
|
|
1
|
+
module Effective
|
2
|
+
class CpdAuditLevelQuestion < ActiveRecord::Base
|
3
|
+
belongs_to :cpd_audit_level
|
4
|
+
belongs_to :cpd_audit_level_section
|
5
|
+
|
6
|
+
has_many :cpd_audit_level_question_options, -> { CpdAuditLevelQuestionOption.sorted }, inverse_of: :cpd_audit_level_question, dependent: :delete_all
|
7
|
+
accepts_nested_attributes_for :cpd_audit_level_question_options, reject_if: :all_blank, allow_destroy: true
|
8
|
+
|
9
|
+
has_many :cpd_audit_responses
|
10
|
+
|
11
|
+
has_one_attached :upload_file
|
12
|
+
has_rich_text :body
|
13
|
+
|
14
|
+
if respond_to?(:log_changes)
|
15
|
+
log_changes(to: :cpd_audit_level, except: [:cpd_audit_responses])
|
16
|
+
end
|
17
|
+
|
18
|
+
CATEGORIES = [
|
19
|
+
'Choose one', # Radios
|
20
|
+
'Select all that apply', # Checks
|
21
|
+
'Select up to 1',
|
22
|
+
'Select up to 2',
|
23
|
+
'Select up to 3',
|
24
|
+
'Select up to 4',
|
25
|
+
'Select up to 5',
|
26
|
+
'Date', # Date Field
|
27
|
+
'Email', # Email Field
|
28
|
+
'Number', # Numeric Field
|
29
|
+
'Long Answer', # Text Area
|
30
|
+
'Short Answer', # Text Field
|
31
|
+
'Upload File' # File field
|
32
|
+
]
|
33
|
+
|
34
|
+
WITH_OPTIONS_CATEGORIES = [
|
35
|
+
'Choose one', # Radios
|
36
|
+
'Select all that apply', # Checks
|
37
|
+
'Select up to 1',
|
38
|
+
'Select up to 2',
|
39
|
+
'Select up to 3',
|
40
|
+
'Select up to 4',
|
41
|
+
'Select up to 5'
|
42
|
+
]
|
43
|
+
|
44
|
+
effective_resource do
|
45
|
+
title :text
|
46
|
+
category :string
|
47
|
+
required :boolean
|
48
|
+
|
49
|
+
position :integer
|
50
|
+
|
51
|
+
timestamps
|
52
|
+
end
|
53
|
+
|
54
|
+
scope :deep, -> { with_rich_text_body.includes(:cpd_audit_level_section, :cpd_audit_level_question_options) }
|
55
|
+
scope :sorted, -> { order(:position) }
|
56
|
+
|
57
|
+
before_validation(if: -> { cpd_audit_level_section.present? }) do
|
58
|
+
self.cpd_audit_level = cpd_audit_level_section.cpd_audit_level
|
59
|
+
self.position ||= (cpd_audit_level_section.cpd_audit_level_questions.map(&:position).compact.max || -1) + 1
|
60
|
+
end
|
61
|
+
|
62
|
+
validates :title, presence: true
|
63
|
+
validates :category, presence: true, inclusion: { in: CATEGORIES }
|
64
|
+
validates :position, presence: true
|
65
|
+
validates :cpd_audit_level_question_options, presence: true, if: -> { question_option? }
|
66
|
+
|
67
|
+
before_destroy do
|
68
|
+
if (count = cpd_audit_responses.length) > 0
|
69
|
+
raise("#{count} audit responses belong to this question")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Create choose_one? and select_all_that_apply? methods for each category
|
74
|
+
CATEGORIES.each do |category|
|
75
|
+
define_method(category.parameterize.underscore + '?') { self.category == category }
|
76
|
+
end
|
77
|
+
|
78
|
+
def question_option?
|
79
|
+
WITH_OPTIONS_CATEGORIES.include?(category)
|
80
|
+
end
|
81
|
+
|
82
|
+
def category_partial
|
83
|
+
category.to_s.parameterize.underscore
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_s
|
87
|
+
title.presence || 'audit question'
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Effective
|
2
|
+
class CpdAuditLevelQuestionOption < ActiveRecord::Base
|
3
|
+
belongs_to :cpd_audit_level_question
|
4
|
+
|
5
|
+
has_many :cpd_audit_response_options
|
6
|
+
|
7
|
+
effective_resource do
|
8
|
+
title :text
|
9
|
+
position :integer
|
10
|
+
|
11
|
+
timestamps
|
12
|
+
end
|
13
|
+
|
14
|
+
before_validation(if: -> { cpd_audit_level_question.present? }) do
|
15
|
+
self.position ||= (cpd_audit_level_question.cpd_audit_level_question_options.map { |obj| obj.position }.compact.max || -1) + 1
|
16
|
+
end
|
17
|
+
|
18
|
+
scope :sorted, -> { order(:position) }
|
19
|
+
|
20
|
+
validates :title, presence: true
|
21
|
+
validates :position, presence: true
|
22
|
+
|
23
|
+
before_destroy do
|
24
|
+
if (count = cpd_audit_response_options.length) > 0
|
25
|
+
raise("#{count} audit response options belong to this question option")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
title.presence || 'New Audit Question Option'
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Effective
|
2
|
+
class CpdAuditLevelSection < ActiveRecord::Base
|
3
|
+
belongs_to :cpd_audit_level
|
4
|
+
|
5
|
+
has_rich_text :top_content
|
6
|
+
has_rich_text :bottom_content
|
7
|
+
|
8
|
+
has_many :cpd_audit_level_questions, -> { CpdAuditLevelQuestion.sorted }, inverse_of: :cpd_audit_level_section, dependent: :destroy
|
9
|
+
accepts_nested_attributes_for :cpd_audit_level_questions, allow_destroy: true
|
10
|
+
|
11
|
+
has_many :cpd_audit_responses
|
12
|
+
|
13
|
+
if respond_to?(:log_changes)
|
14
|
+
log_changes(to: :cpd_audit_level, except: [:cpd_audit_responses])
|
15
|
+
end
|
16
|
+
|
17
|
+
effective_resource do
|
18
|
+
title :string
|
19
|
+
position :integer
|
20
|
+
|
21
|
+
timestamps
|
22
|
+
end
|
23
|
+
|
24
|
+
scope :deep, -> {
|
25
|
+
with_rich_text_top_content
|
26
|
+
.with_rich_text_bottom_content
|
27
|
+
.includes(cpd_audit_level_questions: [:rich_text_body])
|
28
|
+
}
|
29
|
+
|
30
|
+
scope :sorted, -> { order(:position) }
|
31
|
+
|
32
|
+
before_validation(if: -> { cpd_audit_level.present? }) do
|
33
|
+
self.position ||= (cpd_audit_level.cpd_audit_level_sections.map(&:position).compact.max || -1) + 1
|
34
|
+
end
|
35
|
+
|
36
|
+
validates :title, presence: true
|
37
|
+
validates :position, presence: true
|
38
|
+
|
39
|
+
before_destroy do
|
40
|
+
if (count = cpd_audit_responses.length) > 0
|
41
|
+
raise("#{count} audit responses belong to this section")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_s
|
46
|
+
title.presence || 'audit section'
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Effective
|
2
|
+
class CpdAuditResponse < ActiveRecord::Base
|
3
|
+
belongs_to :cpd_audit
|
4
|
+
belongs_to :cpd_audit_level_section
|
5
|
+
belongs_to :cpd_audit_level_question
|
6
|
+
|
7
|
+
has_many :cpd_audit_response_options, dependent: :delete_all
|
8
|
+
has_many :cpd_audit_level_question_options, through: :cpd_audit_response_options
|
9
|
+
|
10
|
+
has_one_attached :upload_file
|
11
|
+
|
12
|
+
if respond_to?(:log_changes)
|
13
|
+
log_changes(to: :cpd_audit)
|
14
|
+
end
|
15
|
+
|
16
|
+
effective_resource do
|
17
|
+
# The response
|
18
|
+
date :date
|
19
|
+
email :string
|
20
|
+
number :integer
|
21
|
+
long_answer :text
|
22
|
+
short_answer :text
|
23
|
+
|
24
|
+
timestamps
|
25
|
+
end
|
26
|
+
|
27
|
+
scope :submitted, -> { where(cpd_audit: Effective::CpdAudit.where.not(submitted_at: nil)) }
|
28
|
+
scope :deep, -> { includes(:cpd_audit, :cpd_audit_level_question) }
|
29
|
+
|
30
|
+
before_validation(if: -> { cpd_audit_level_question.present? }) do
|
31
|
+
self.cpd_audit_level_section = cpd_audit_level_question.cpd_audit_level_section
|
32
|
+
end
|
33
|
+
|
34
|
+
validates :date, presence: true, if: -> { cpd_audit_level_question&.required? && cpd_audit_level_question.date? }
|
35
|
+
validates :email, presence: true, email: true, if: -> { cpd_audit_level_question&.required? && cpd_audit_level_question.email? }
|
36
|
+
validates :number, presence: true, if: -> { cpd_audit_level_question&.required? && cpd_audit_level_question.number? }
|
37
|
+
validates :long_answer, presence: true, if: -> { cpd_audit_level_question&.required? && cpd_audit_level_question.long_answer? }
|
38
|
+
validates :short_answer, presence: true, if: -> { cpd_audit_level_question&.required? && cpd_audit_level_question.short_answer? }
|
39
|
+
validates :upload_file, presence: true, if: -> { cpd_audit_level_question&.required? && cpd_audit_level_question.upload_file? }
|
40
|
+
validates :cpd_audit_level_question_option_ids, presence: true, if: -> { cpd_audit_level_question&.required? && cpd_audit_level_question.question_option? }
|
41
|
+
|
42
|
+
validates :cpd_audit_level_question_option_ids, if: -> { cpd_audit_level_question&.choose_one? },
|
43
|
+
length: { maximum: 1, message: 'please choose 1 option only' }
|
44
|
+
|
45
|
+
validates :cpd_audit_level_question_option_ids, if: -> { cpd_audit_level_question&.select_up_to_1? },
|
46
|
+
length: { maximum: 1, message: 'please select 1 option or fewer' }
|
47
|
+
|
48
|
+
validates :cpd_audit_level_question_option_ids, if: -> { cpd_audit_level_question&.select_up_to_2? },
|
49
|
+
length: { maximum: 2, message: 'please select 2 options or fewer' }
|
50
|
+
|
51
|
+
validates :cpd_audit_level_question_option_ids, if: -> { cpd_audit_level_question&.select_up_to_3? },
|
52
|
+
length: { maximum: 3, message: 'please select 3 options or fewer' }
|
53
|
+
|
54
|
+
validates :cpd_audit_level_question_option_ids, if: -> { cpd_audit_level_question&.select_up_to_4? },
|
55
|
+
length: { maximum: 4, message: 'please select 4 options or fewer' }
|
56
|
+
|
57
|
+
validates :cpd_audit_level_question_option_ids, if: -> { cpd_audit_level_question&.select_up_to_5? },
|
58
|
+
length: { maximum: 5, message: 'please select 5 options or fewer' }
|
59
|
+
|
60
|
+
def to_s
|
61
|
+
'audit response'
|
62
|
+
end
|
63
|
+
|
64
|
+
def response
|
65
|
+
return nil unless cpd_audit_level_question.present?
|
66
|
+
|
67
|
+
return date if cpd_audit_level_question.date?
|
68
|
+
return email if cpd_audit_level_question.email?
|
69
|
+
return number if cpd_audit_level_question.number?
|
70
|
+
return long_answer if cpd_audit_level_question.long_answer?
|
71
|
+
return short_answer if cpd_audit_level_question.short_answer?
|
72
|
+
return upload_file if cpd_audit_level_question.upload_file?
|
73
|
+
|
74
|
+
return cpd_audit_level_question_options.first if cpd_audit_level_question.choose_one?
|
75
|
+
return cpd_audit_level_question_options.first if cpd_audit_level_question.select_up_to_1?
|
76
|
+
return cpd_audit_level_question_options if cpd_audit_level_question.question_option?
|
77
|
+
|
78
|
+
raise('unknown response for unexpected cpd audit question category')
|
79
|
+
end
|
80
|
+
|
81
|
+
def category_partial
|
82
|
+
cpd_audit_level_question&.category_partial
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
module Effective
|
2
|
+
class CpdAuditReview < ActiveRecord::Base
|
3
|
+
attr_accessor :current_user
|
4
|
+
attr_accessor :current_step
|
5
|
+
|
6
|
+
belongs_to :cpd_audit
|
7
|
+
belongs_to :cpd_audit_level
|
8
|
+
belongs_to :user, polymorphic: true # Auditor
|
9
|
+
|
10
|
+
has_many :cpd_audit_review_items, -> { CpdAuditReviewItem.sorted }, inverse_of: :cpd_audit_review
|
11
|
+
accepts_nested_attributes_for :cpd_audit_review_items, reject_if: :all_blank, allow_destroy: true
|
12
|
+
|
13
|
+
if respond_to?(:log_changes)
|
14
|
+
log_changes(to: :cpd_audit, except: :wizard_steps)
|
15
|
+
end
|
16
|
+
|
17
|
+
acts_as_email_form
|
18
|
+
acts_as_tokened
|
19
|
+
|
20
|
+
acts_as_wizard(
|
21
|
+
start: 'Start',
|
22
|
+
information: 'Information',
|
23
|
+
instructions: 'Instructions',
|
24
|
+
|
25
|
+
# Optional based on cpd_audit_level options
|
26
|
+
conflict: 'Conflict of Interest',
|
27
|
+
|
28
|
+
waiting: 'Waiting on Auditee Submission',
|
29
|
+
|
30
|
+
statements: 'Review CPD Statements',
|
31
|
+
# ... There will be one step per cpd_statement here. "statement1"
|
32
|
+
|
33
|
+
questionnaire: 'Review Questionnaire Responses',
|
34
|
+
# ... There will be one step per cpd_audit_level_sections here
|
35
|
+
|
36
|
+
recommendation: 'Recommendation',
|
37
|
+
submit: 'Confirm & Submit',
|
38
|
+
complete: 'Complete'
|
39
|
+
)
|
40
|
+
|
41
|
+
effective_resource do
|
42
|
+
token :string
|
43
|
+
due_date :date
|
44
|
+
|
45
|
+
# Auditor response
|
46
|
+
conflict_of_interest :boolean
|
47
|
+
conflict_of_interest_reason :text
|
48
|
+
|
49
|
+
# Rolling comments
|
50
|
+
comments :text
|
51
|
+
|
52
|
+
# Recommendation Step
|
53
|
+
recommendation :string
|
54
|
+
|
55
|
+
# Status Dates
|
56
|
+
submitted_at :datetime
|
57
|
+
|
58
|
+
# acts_as_statused
|
59
|
+
# I'm not using acts_as_statused yet, but I probably will later
|
60
|
+
status :string
|
61
|
+
status_steps :text
|
62
|
+
|
63
|
+
# Wizard Progress
|
64
|
+
wizard_steps :text
|
65
|
+
|
66
|
+
timestamps
|
67
|
+
end
|
68
|
+
|
69
|
+
scope :deep, -> { includes(:cpd_audit, :cpd_audit_level, :user) }
|
70
|
+
scope :sorted, -> { order(:id) }
|
71
|
+
|
72
|
+
scope :available, -> { where(submitted_at: nil) }
|
73
|
+
scope :completed, -> { where.not(submitted_at: nil) }
|
74
|
+
|
75
|
+
before_validation(if: -> { new_record? }) do
|
76
|
+
self.cpd_audit_level ||= cpd_audit&.cpd_audit_level
|
77
|
+
self.due_date ||= deadline_to_review()
|
78
|
+
end
|
79
|
+
|
80
|
+
validate(if: -> { recommendation.present? }) do
|
81
|
+
unless cpd_audit_level.recommendations.include?(recommendation)
|
82
|
+
self.errors.add(:recommendation, 'must exist in this audit level')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
after_commit(on: :create) { send_email(:cpd_audit_review_opened) }
|
87
|
+
after_commit(on: :destroy) { cpd_audit.review! }
|
88
|
+
|
89
|
+
def to_s
|
90
|
+
'audit review'
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_s
|
94
|
+
persisted? ? "#{cpd_audit_level} Audit Review by #{user}" : 'audit review'
|
95
|
+
end
|
96
|
+
|
97
|
+
# Find or build
|
98
|
+
def cpd_audit_review_item(item)
|
99
|
+
unless item.kind_of?(CpdAuditResponse) || item.kind_of?(CpdStatementActivity)
|
100
|
+
raise("expected a cpd_audit_response or cpd_statement_activity")
|
101
|
+
end
|
102
|
+
|
103
|
+
cpd_audit_review_item = cpd_audit_review_items.find { |cari| cari.item == item }
|
104
|
+
cpd_audit_review_item ||= cpd_audit_review_items.build(item: item)
|
105
|
+
end
|
106
|
+
|
107
|
+
# The dynamic CPD Statement review steps
|
108
|
+
def auditee_cpd_statements
|
109
|
+
cpd_audit.user.cpd_statements.select do |cpd_statement|
|
110
|
+
cpd_statement.completed? && (submitted_at.blank? || cpd_statement.submitted_at < submitted_at)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def cpd_statement(wizard_step)
|
115
|
+
cpd_cycle_id = (wizard_step.to_s.split('statement').last.to_i rescue false)
|
116
|
+
auditee_cpd_statements.find { |cpd_statement| cpd_statement.cpd_cycle_id == cpd_cycle_id }
|
117
|
+
end
|
118
|
+
|
119
|
+
def dynamic_wizard_statement_steps
|
120
|
+
@statement_steps ||= auditee_cpd_statements.each_with_object({}) do |cpd_statement, h|
|
121
|
+
h["statement#{cpd_statement.cpd_cycle_id}".to_sym] = "#{cpd_statement.cpd_cycle.to_s} Activities"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# The dynamic CPD Audit Level Sections steps
|
126
|
+
def cpd_audit_level_section(wizard_step)
|
127
|
+
position = (wizard_step.to_s.split('section').last.to_i rescue false)
|
128
|
+
cpd_audit_level.cpd_audit_level_sections.find { |section| (section.position + 1) == position }
|
129
|
+
end
|
130
|
+
|
131
|
+
def dynamic_wizard_questionnaire_steps
|
132
|
+
@questionnaire_steps ||= cpd_audit_level.cpd_audit_level_sections.each_with_object({}) do |section, h|
|
133
|
+
h["section#{section.position+1}".to_sym] = section.title
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def dynamic_wizard_steps
|
138
|
+
dynamic_wizard_statement_steps.merge(dynamic_wizard_questionnaire_steps)
|
139
|
+
end
|
140
|
+
|
141
|
+
def can_visit_step?(step)
|
142
|
+
return (step == :complete) if completed? # Can only view complete step once submitted
|
143
|
+
can_revisit_completed_steps(step)
|
144
|
+
end
|
145
|
+
|
146
|
+
def required_steps
|
147
|
+
steps = [:start, :information, :instructions]
|
148
|
+
|
149
|
+
steps << :conflict if cpd_audit_level.conflict_of_interest?
|
150
|
+
|
151
|
+
if conflict_of_interest?
|
152
|
+
return steps + [:submit, :complete]
|
153
|
+
end
|
154
|
+
|
155
|
+
steps += [:waiting] unless ready?
|
156
|
+
|
157
|
+
steps += [:statements] + dynamic_wizard_statement_steps.keys
|
158
|
+
steps += [:questionnaire] + dynamic_wizard_questionnaire_steps.keys
|
159
|
+
steps += [:recommendation, :submit, :complete]
|
160
|
+
|
161
|
+
steps
|
162
|
+
end
|
163
|
+
|
164
|
+
def wizard_step_title(step)
|
165
|
+
WIZARD_STEPS[step] || dynamic_wizard_steps.fetch(step)
|
166
|
+
end
|
167
|
+
|
168
|
+
# Called by CpdAudit.extension_granted
|
169
|
+
def extension_granted!
|
170
|
+
self.due_date = deadline_to_review()
|
171
|
+
end
|
172
|
+
|
173
|
+
# Called by CpdAudit.submit!
|
174
|
+
def ready!
|
175
|
+
send_email(:cpd_audit_review_ready)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Called by review wizard submit step
|
179
|
+
def submit!
|
180
|
+
update!(submitted_at: Time.zone.now)
|
181
|
+
cpd_audit.review! # maybe go from submitted->removed
|
182
|
+
|
183
|
+
send_email(:cpd_audit_review_submitted)
|
184
|
+
end
|
185
|
+
|
186
|
+
# When ready, the applicant review wizard hides the waiting step
|
187
|
+
def ready?
|
188
|
+
cpd_audit&.was_submitted?
|
189
|
+
end
|
190
|
+
|
191
|
+
def in_progress?
|
192
|
+
submitted_at.blank?
|
193
|
+
end
|
194
|
+
|
195
|
+
def completed?
|
196
|
+
submitted_at.present?
|
197
|
+
end
|
198
|
+
|
199
|
+
def email_form_defaults(action)
|
200
|
+
{ from: EffectiveCpd.mailer_sender }
|
201
|
+
end
|
202
|
+
|
203
|
+
def send_email(email)
|
204
|
+
EffectiveCpd.send_email(email, self, email_form_params) unless email_form_skip?
|
205
|
+
true
|
206
|
+
end
|
207
|
+
|
208
|
+
def deadline_to_conflict_of_interest
|
209
|
+
cpd_audit.deadline_to_conflict_of_interest
|
210
|
+
end
|
211
|
+
|
212
|
+
def deadline_to_review
|
213
|
+
return nil unless cpd_audit_level&.days_to_review.present?
|
214
|
+
|
215
|
+
date = cpd_audit.deadline_to_submit
|
216
|
+
return nil unless date.present?
|
217
|
+
|
218
|
+
EffectiveResources.advance_date(date, business_days: cpd_audit_level.days_to_review)
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
222
|
+
end
|