effective_cpd 0.0.1 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +122 -10
- data/app/assets/javascripts/effective_cpd/activities.js +6 -0
- 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/admin/cpd_special_rules_controller.rb +13 -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_special_rules_datatable.rb +23 -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/helpers/effective_cpd_helper.rb +10 -0
- data/app/mailers/effective/cpd_mailer.rb +155 -3
- data/app/models/concerns/effective_cpd_user.rb +29 -0
- 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 +18 -2
- data/app/models/effective/cpd_rule.rb +23 -6
- data/app/models/effective/cpd_scorer.rb +23 -1
- data/app/models/effective/cpd_special_rule.rb +54 -0
- data/app/models/effective/cpd_special_rule_mate.rb +6 -0
- data/app/models/effective/cpd_statement.rb +25 -11
- 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 +6 -3
- data/app/views/admin/cpd_cycles/_form_content.html.haml +8 -0
- data/app/views/admin/cpd_cycles/_form_cpd_cycle.html.haml +3 -0
- data/app/views/admin/cpd_cycles/_form_cpd_rules.html.haml +12 -7
- data/app/views/admin/cpd_cycles/_form_cpd_special_rules.html.haml +4 -0
- data/app/views/admin/cpd_special_rules/_form.html.haml +18 -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 +10 -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_new.html.haml +3 -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/_layout.html.haml +3 -4
- 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 +2 -3
- data/config/effective_cpd.rb +49 -10
- data/config/routes.rb +19 -1
- data/db/migrate/01_create_effective_cpd.rb.erb +175 -1
- data/db/seeds.rb +16 -13
- data/lib/effective_cpd.rb +42 -3
- data/lib/effective_cpd/engine.rb +7 -0
- data/lib/effective_cpd/version.rb +1 -1
- data/lib/generators/effective_cpd/install_generator.rb +18 -3
- metadata +175 -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,34 @@
|
|
1
|
+
module Effective
|
2
|
+
class CpdAuditReviewItem < ActiveRecord::Base
|
3
|
+
belongs_to :cpd_audit_review
|
4
|
+
belongs_to :item, polymorphic: true # CpdAuditResponse or CpdStatementActivity
|
5
|
+
|
6
|
+
if respond_to?(:log_changes)
|
7
|
+
log_changes(to: :cpd_audit_review)
|
8
|
+
end
|
9
|
+
|
10
|
+
effective_resource do
|
11
|
+
recommendation :string
|
12
|
+
comments :text
|
13
|
+
|
14
|
+
timestamps
|
15
|
+
end
|
16
|
+
|
17
|
+
scope :deep, -> { includes(:cpd_audit_review, :item) }
|
18
|
+
scope :sorted, -> { order(:id) }
|
19
|
+
|
20
|
+
validates :recommendation, presence: true
|
21
|
+
validates :item_id, presence: true, uniqueness: { scope: [:cpd_audit_review_id, :item_type] }
|
22
|
+
|
23
|
+
validate(if: -> { recommendation.present? && cpd_audit_review.present? }) do
|
24
|
+
unless cpd_audit_review.cpd_audit_level.recommendations.include?(recommendation)
|
25
|
+
self.errors.add(:recommendation, 'must exist in this audit level')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
recommendation.presence || 'cpd audit review item'
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -1,13 +1,16 @@
|
|
1
1
|
module Effective
|
2
2
|
class CpdCategory < ActiveRecord::Base
|
3
3
|
has_rich_text :body
|
4
|
-
log_changes if respond_to?(:log_changes)
|
5
4
|
|
6
5
|
has_many :cpd_activities, -> { order(:position) }, inverse_of: :cpd_category, dependent: :destroy
|
7
6
|
accepts_nested_attributes_for :cpd_activities, allow_destroy: true
|
8
7
|
|
9
|
-
|
10
|
-
|
8
|
+
has_many :rules, class_name: 'Effective::CpdRule', as: :ruleable
|
9
|
+
has_many :cpd_statement_activities
|
10
|
+
|
11
|
+
if respond_to?(:log_changes)
|
12
|
+
log_changes(except: [:rules, :cpd_statement_activities])
|
13
|
+
end
|
11
14
|
|
12
15
|
effective_resource do
|
13
16
|
title :string
|
@@ -27,6 +30,12 @@ module Effective
|
|
27
30
|
validates :position, presence: true
|
28
31
|
validates :body, presence: true
|
29
32
|
|
33
|
+
before_destroy do
|
34
|
+
if (count = cpd_statement_activities.length) > 0
|
35
|
+
raise("#{count} statement activities belong to this category")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
30
39
|
def to_s
|
31
40
|
title.presence || 'category'
|
32
41
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module Effective
|
2
2
|
class CpdCycle < ActiveRecord::Base
|
3
3
|
has_rich_text :all_steps_content # Update build_from_cycle() below if these change
|
4
|
+
has_rich_text :sidebar_content
|
4
5
|
has_rich_text :start_content
|
5
6
|
has_rich_text :activities_content
|
6
7
|
has_rich_text :agreements_content
|
@@ -10,6 +11,9 @@ module Effective
|
|
10
11
|
has_many :cpd_rules, dependent: :delete_all
|
11
12
|
accepts_nested_attributes_for :cpd_rules, allow_destroy: true
|
12
13
|
|
14
|
+
has_many :cpd_special_rules, dependent: :delete_all
|
15
|
+
accepts_nested_attributes_for :cpd_special_rules, allow_destroy: true
|
16
|
+
|
13
17
|
has_many :cpd_statements
|
14
18
|
|
15
19
|
if respond_to?(:log_changes)
|
@@ -29,6 +33,7 @@ module Effective
|
|
29
33
|
|
30
34
|
scope :deep, -> {
|
31
35
|
with_rich_text_all_steps_content
|
36
|
+
.with_rich_text_sidebar_content
|
32
37
|
.with_rich_text_start_content
|
33
38
|
.with_rich_text_activities_content
|
34
39
|
.with_rich_text_submit_content
|
@@ -49,11 +54,18 @@ module Effective
|
|
49
54
|
|
50
55
|
validates :title, presence: true
|
51
56
|
validates :start_at, presence: true
|
57
|
+
validates :required_score, numericality: { greater_than: 0, allow_nil: true }
|
52
58
|
|
53
59
|
validate(if: -> { start_at.present? && end_at.present? }) do
|
54
60
|
self.errors.add(:end_at, 'must be after the start date') unless end_at > start_at
|
55
61
|
end
|
56
62
|
|
63
|
+
before_destroy do
|
64
|
+
if (count = cpd_statements.length) > 0
|
65
|
+
raise("#{count} statement belong to this cycle")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
57
69
|
def self.latest_cycle
|
58
70
|
order(id: :desc).first
|
59
71
|
end
|
@@ -69,13 +81,17 @@ module Effective
|
|
69
81
|
attributes = cycle.dup.attributes.except('title', 'token', 'start_at', 'end_at')
|
70
82
|
assign_attributes(attributes)
|
71
83
|
|
72
|
-
[:all_steps_content, :start_content, :activities_content, :submit_content, :complete_content].each do |rich_text|
|
84
|
+
[:all_steps_content, :sidebar_content, :start_content, :activities_content, :submit_content, :complete_content].each do |rich_text|
|
73
85
|
self.send("#{rich_text}=", cycle.send(rich_text))
|
74
86
|
end
|
75
87
|
|
76
88
|
cycle.cpd_rules.each do |rule|
|
77
89
|
attributes = rule.dup.attributes.except('cpd_cycle_id')
|
78
|
-
self.cpd_rules.build(attributes)
|
90
|
+
cpd_rule = self.cpd_rules.build(attributes)
|
91
|
+
|
92
|
+
if rule.category?
|
93
|
+
cpd_rule.category_credit_description = rule.category_credit_description
|
94
|
+
end
|
79
95
|
end
|
80
96
|
|
81
97
|
self
|
@@ -3,14 +3,21 @@ module Effective
|
|
3
3
|
belongs_to :cpd_cycle
|
4
4
|
belongs_to :ruleable, polymorphic: true # Activity or Category
|
5
5
|
|
6
|
-
|
6
|
+
# For a Category: A maximum of 35 PDHs/year may be claimed in the Contributions to Knowledge category
|
7
|
+
has_rich_text :category_credit_description
|
8
|
+
|
9
|
+
has_many :cpd_special_rule_mates, dependent: :destroy, inverse_of: :cpd_rule
|
10
|
+
has_many :cpd_special_rules, -> { CpdSpecialRule.sorted }, through: :cpd_special_rule_mates
|
11
|
+
|
12
|
+
if respond_to?(:log_changes)
|
13
|
+
log_changes(to: :cpd_cycle)
|
14
|
+
end
|
7
15
|
|
8
16
|
# Only permit the words amount, amount2 and any charater 0-9 + - / * ( )
|
9
17
|
INVALID_FORMULA_CHARS = /[^0-9\+\-\/\*\(\)]/
|
10
18
|
|
11
19
|
effective_resource do
|
12
20
|
# A plaintext description of the formula
|
13
|
-
# For a Category: A maximum of 35 PDHs/year may be claimed in the Contributions to Knowledge category
|
14
21
|
# For a Activity: 15 hours of work equals 1 credit
|
15
22
|
credit_description :text
|
16
23
|
|
@@ -21,6 +28,7 @@ module Effective
|
|
21
28
|
formula :string
|
22
29
|
|
23
30
|
# Maximum number of cycles can carry forward
|
31
|
+
# Only considered for activities
|
24
32
|
max_cycles_can_carry_forward :integer
|
25
33
|
|
26
34
|
# Cannot be entered in this cycle
|
@@ -29,19 +37,22 @@ module Effective
|
|
29
37
|
timestamps
|
30
38
|
end
|
31
39
|
|
32
|
-
scope :
|
40
|
+
scope :sorted, -> { order(:id) }
|
41
|
+
scope :deep, -> { with_rich_text_category_credit_description.includes(:cpd_cycle, :ruleable) }
|
33
42
|
scope :categories, -> { where(ruleable_type: 'Effective::CpdCategory') }
|
34
43
|
scope :activities, -> { where(ruleable_type: 'Effective::CpdActivity') }
|
35
44
|
scope :unavailable, -> { where(unavailable: true) }
|
36
45
|
|
37
46
|
#validates :cpd_cycle_id, uniqueness: { scope: [:ruleable_id, :ruleable_type] }
|
38
|
-
validates :credit_description, presence: true
|
39
47
|
validates :max_credits_per_cycle, numericality: { greater_than: 0, allow_nil: true }
|
40
|
-
validates :max_cycles_can_carry_forward, numericality: {
|
48
|
+
validates :max_cycles_can_carry_forward, numericality: { greater_than_or_equal_to: 0, allow_nil: true }
|
41
49
|
|
42
50
|
validates :formula, presence: true, if: -> { activity? }
|
43
51
|
validates :formula, absence: true, if: -> { category? }
|
44
52
|
|
53
|
+
validates :credit_description, presence: true, if: -> { activity? }
|
54
|
+
validates :category_credit_description, presence: true, if: -> { category? }
|
55
|
+
|
45
56
|
validate(if: -> { formula.present? }) do
|
46
57
|
if formula.gsub('amount2', '').gsub('amount', '').gsub(' ', '').match(INVALID_FORMULA_CHARS).present?
|
47
58
|
self.errors.add(:formula, "may only contain amount, amount2 and 0-9 + - / * ( ) characters")
|
@@ -77,7 +88,13 @@ module Effective
|
|
77
88
|
end
|
78
89
|
|
79
90
|
def to_s
|
80
|
-
|
91
|
+
if activity?
|
92
|
+
formula.presence || ruleable.to_s.presence || 'activity rule'
|
93
|
+
elsif category?
|
94
|
+
ruleable.to_s.presence || 'category rule'
|
95
|
+
else
|
96
|
+
'new rule'
|
97
|
+
end
|
81
98
|
end
|
82
99
|
|
83
100
|
def activity?
|
@@ -78,7 +78,7 @@ module Effective
|
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
81
|
-
# This
|
81
|
+
# This enforces CycleCategory.max_credits_per_cycle
|
82
82
|
statement.cpd_statement_activities.group_by(&:cpd_category).each do |cpd_category, activities|
|
83
83
|
rule = cycle.rule_for(cpd_category)
|
84
84
|
max_credits_per_cycle = rule.max_credits_per_cycle
|
@@ -98,6 +98,28 @@ module Effective
|
|
98
98
|
end
|
99
99
|
end
|
100
100
|
|
101
|
+
# This enforces cumulative max credits CpdSpecialRule.max_credits_per_cycle special rules
|
102
|
+
cycle.cpd_special_rules.select(&:cumulative_max_credits?).each do |special_rule|
|
103
|
+
cpd_categories = special_rule.ruleables.select { |obj| obj.kind_of?(Effective::CpdCategory) }
|
104
|
+
|
105
|
+
max_credits_per_cycle = special_rule.max_credits_per_cycle
|
106
|
+
raise('expected max credits per cycle to be present') unless max_credits_per_cycle.to_i > 0
|
107
|
+
|
108
|
+
activities = statement.cpd_statement_activities.select { |sa| cpd_categories.include?(sa.cpd_category) }
|
109
|
+
|
110
|
+
activities.each do |activity|
|
111
|
+
next if activity.marked_for_destruction?
|
112
|
+
|
113
|
+
max_credits_per_cycle -= activity.score # We're already scored. Counting down...
|
114
|
+
|
115
|
+
if max_credits_per_cycle < 0
|
116
|
+
activity.score = [activity.score + max_credits_per_cycle, 0].max
|
117
|
+
activity.carry_forward = activity.max_score - activity.score
|
118
|
+
activity.reduced_messages["category_#{activity.cpd_category_id}"] = "You have reached the cumulative maximum of #{special_rule.max_credits_per_cycle}/#{cpd_cycle_label} for activities in the #{cpd_categories.map(&:to_s).to_sentence} categories"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
101
123
|
# This enforces the max_cycles_can_carry_forward logic
|
102
124
|
# If an Activity cannot be carried forward another cycle, its carry_forward should be 0
|
103
125
|
next_cycle = @cycles[@cycles.index(cycle) + 1]
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Effective
|
2
|
+
class CpdSpecialRule < ActiveRecord::Base
|
3
|
+
belongs_to :cpd_cycle
|
4
|
+
|
5
|
+
has_many :cpd_special_rule_mates, dependent: :destroy, inverse_of: :cpd_special_rule
|
6
|
+
has_many :cpd_rules, -> { CpdRule.sorted }, through: :cpd_special_rule_mates
|
7
|
+
|
8
|
+
if respond_to?(:log_changes)
|
9
|
+
log_changes
|
10
|
+
end
|
11
|
+
|
12
|
+
CATEGORIES = ['cumulative max credits']
|
13
|
+
|
14
|
+
effective_resource do
|
15
|
+
category :string # Special rule tyoes
|
16
|
+
|
17
|
+
# For cumulative max credits
|
18
|
+
max_credits_per_cycle :integer
|
19
|
+
|
20
|
+
timestamps
|
21
|
+
end
|
22
|
+
|
23
|
+
scope :deep, -> { includes(:cpd_special_rule_mates, cpd_rules: [:ruleable]) }
|
24
|
+
scope :sorted, -> { order(:id) }
|
25
|
+
|
26
|
+
before_validation do
|
27
|
+
self.category ||= CATEGORIES.first
|
28
|
+
end
|
29
|
+
|
30
|
+
validates :category, presence: true, inclusion: { in: CATEGORIES }
|
31
|
+
|
32
|
+
with_options(if: -> { cumulative_max_credits? }) do
|
33
|
+
validates :max_credits_per_cycle, presence: true, numericality: { greater_than: 0 }
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
if cumulative_max_credits?
|
38
|
+
"Cumulative max #{max_credits_per_cycle} credits"
|
39
|
+
else
|
40
|
+
'cpd special rule'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def cumulative_max_credits?
|
45
|
+
category == 'cumulative max credits'
|
46
|
+
end
|
47
|
+
|
48
|
+
# Right now this is going to be just Effective::CpdCategory objects
|
49
|
+
def ruleables
|
50
|
+
cpd_rules.map(&:ruleable)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -10,7 +10,10 @@ module Effective
|
|
10
10
|
accepts_nested_attributes_for :cpd_statement_activities
|
11
11
|
|
12
12
|
has_many_attached :files
|
13
|
-
|
13
|
+
|
14
|
+
if respond_to?(:log_changes)
|
15
|
+
log_changes(except: [:wizard_steps])
|
16
|
+
end
|
14
17
|
|
15
18
|
acts_as_tokened
|
16
19
|
|
@@ -29,30 +32,31 @@ module Effective
|
|
29
32
|
confirm_factual :boolean
|
30
33
|
confirm_readonly :boolean
|
31
34
|
|
32
|
-
|
35
|
+
submitted_at :datetime, permitted: false
|
33
36
|
|
34
37
|
# Acts as tokened
|
35
38
|
token :string, permitted: false
|
36
39
|
|
37
40
|
# Acts as Wizard
|
38
41
|
wizard_steps :text, permitted: false
|
42
|
+
|
39
43
|
timestamps
|
40
44
|
end
|
41
45
|
|
42
46
|
scope :deep, -> { includes(:cpd_cycle, :user, cpd_statement_activities: [:files_attachments, :cpd_category, :original, cpd_activity: [:rich_text_body]]) }
|
43
47
|
scope :sorted, -> { order(:cpd_cycle_id) }
|
44
48
|
|
45
|
-
scope :draft, -> { where(
|
46
|
-
scope :completed, -> { where.not(
|
49
|
+
scope :draft, -> { where(submitted_at: nil) }
|
50
|
+
scope :completed, -> { where.not(submitted_at: nil) }
|
47
51
|
|
48
52
|
before_validation(if: -> { new_record? }) do
|
49
53
|
self.user ||= current_user
|
50
54
|
self.score ||= 0
|
51
55
|
end
|
52
56
|
|
53
|
-
validate(if: -> { completed?
|
54
|
-
min =
|
55
|
-
self.errors.add(:score, "must be #{min} or greater to submit statement") if score < min
|
57
|
+
validate(if: -> { completed? }) do
|
58
|
+
min = required_score()
|
59
|
+
self.errors.add(:score, "must be #{min} or greater to submit a statement") if score < min
|
56
60
|
end
|
57
61
|
|
58
62
|
with_options(if: -> { current_step == :agreements }) do
|
@@ -65,19 +69,29 @@ module Effective
|
|
65
69
|
end
|
66
70
|
|
67
71
|
def to_s
|
68
|
-
|
72
|
+
cpd_cycle.present? ? "#{cpd_cycle} Statement" : 'statement'
|
69
73
|
end
|
70
74
|
|
71
75
|
# This is the review step where they click Submit Ballot
|
72
76
|
def submit!
|
73
77
|
wizard_steps[:complete] ||= Time.zone.now
|
74
|
-
self.completed_at ||= Time.zone.now
|
75
78
|
|
76
|
-
|
79
|
+
update!(submitted_at: Time.zone.now)
|
80
|
+
end
|
81
|
+
|
82
|
+
def in_progress?
|
83
|
+
submitted_at.blank?
|
77
84
|
end
|
78
85
|
|
79
86
|
def completed?
|
80
|
-
|
87
|
+
submitted_at.present?
|
88
|
+
end
|
89
|
+
|
90
|
+
def required_score
|
91
|
+
required_by_cycle = cpd_cycle&.required_score
|
92
|
+
required_by_user = user.cpd_statement_required_score(self) if user.respond_to?(:cpd_statement_required_score)
|
93
|
+
|
94
|
+
[required_by_cycle.to_i, required_by_user.to_i].max
|
81
95
|
end
|
82
96
|
|
83
97
|
def carry_forward
|
@@ -0,0 +1,100 @@
|
|
1
|
+
= effective_form_with(model: [:admin, cpd_audit_level_question], engine: true) do |f|
|
2
|
+
- if inline_datatable?
|
3
|
+
= f.hidden_field :cpd_audit_level_section_id
|
4
|
+
- else
|
5
|
+
= f.select :cpd_audit_level_section_id, Effective::CpdAuditLevelSection.sorted.all
|
6
|
+
|
7
|
+
= f.text_field :title, label: 'Question Title'
|
8
|
+
= f.rich_text_area :body, label: 'Body (optional)'
|
9
|
+
|
10
|
+
= f.check_box :required, hint: 'A response to this question will be required'
|
11
|
+
= f.select :category, Effective::CpdAuditLevelQuestion::CATEGORIES
|
12
|
+
|
13
|
+
= f.show_if :category, 'Choose one' do
|
14
|
+
.card
|
15
|
+
.card-body
|
16
|
+
%h5 Choose one
|
17
|
+
%p Display radio buttons to choose one option
|
18
|
+
|
19
|
+
= f.show_if :category, 'Select all that apply' do
|
20
|
+
.card
|
21
|
+
.card-body
|
22
|
+
%h5 Select all that apply
|
23
|
+
%p Display checkboxes to select all options that apply
|
24
|
+
|
25
|
+
= f.show_if :category, 'Select up to 1' do
|
26
|
+
.card
|
27
|
+
.card-body
|
28
|
+
%h5 Select up to 1 (one)
|
29
|
+
%p Display checkboxes to select up to 1 option
|
30
|
+
|
31
|
+
= f.show_if :category, 'Select up to 2' do
|
32
|
+
.card
|
33
|
+
.card-body
|
34
|
+
%h5 Select up to 2 (two)
|
35
|
+
%p Display checkboxes to select up to 2 options
|
36
|
+
|
37
|
+
= f.show_if :category, 'Select up to 3' do
|
38
|
+
.card
|
39
|
+
.card-body
|
40
|
+
%h5 Select up to 3 (three)
|
41
|
+
%p Display checkboxes to select up to 3 options
|
42
|
+
|
43
|
+
= f.show_if :category, 'Select up to 4' do
|
44
|
+
.card
|
45
|
+
.card-body
|
46
|
+
%h5 Select up to 4 (four)
|
47
|
+
%p Display checkboxes to select up to 4 options
|
48
|
+
|
49
|
+
= f.show_if :category, 'Select up to 5' do
|
50
|
+
.card
|
51
|
+
.card-body
|
52
|
+
%h5 Select up to 5 (five)
|
53
|
+
%p Display checkboxes to select up to 5 options
|
54
|
+
|
55
|
+
= f.show_if :category, 'Short Answer' do
|
56
|
+
.card
|
57
|
+
.card-body
|
58
|
+
%h5 Short Answer
|
59
|
+
%p Display a text field to enter a short text answer
|
60
|
+
|
61
|
+
= f.show_if :category, 'Long Answer' do
|
62
|
+
.card
|
63
|
+
.card-body
|
64
|
+
%h5 Long Answer
|
65
|
+
%p Display a textarea to enter a long text answer
|
66
|
+
|
67
|
+
= f.show_if :category, 'Date' do
|
68
|
+
.card
|
69
|
+
.card-body
|
70
|
+
%h5 Date
|
71
|
+
%p Display a date field to enter a date
|
72
|
+
|
73
|
+
= f.show_if :category, 'Email' do
|
74
|
+
.card
|
75
|
+
.card-body
|
76
|
+
%h5 Email
|
77
|
+
%p Display an email field to enter an email
|
78
|
+
|
79
|
+
= f.show_if :category, 'Number' do
|
80
|
+
.card
|
81
|
+
.card-body
|
82
|
+
%h5 Number
|
83
|
+
%p Display a number field to enter an integer number
|
84
|
+
|
85
|
+
= f.show_if :category, 'Upload File' do
|
86
|
+
.card
|
87
|
+
.card-body
|
88
|
+
%h5 Upload File
|
89
|
+
%p Display a file field to upload a file
|
90
|
+
|
91
|
+
= f.show_if_any :category, Effective::CpdAuditLevelQuestion::WITH_OPTIONS_CATEGORIES do
|
92
|
+
.mt-3.card
|
93
|
+
.card-body
|
94
|
+
%h5 Options
|
95
|
+
%p Display the following options:
|
96
|
+
|
97
|
+
= f.has_many :cpd_audit_level_question_options, class: 'tight' do |fa|
|
98
|
+
= fa.text_field :title, label: false
|
99
|
+
|
100
|
+
= effective_submit(f)
|