effective_cpd 0.1 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +33 -0
- data/app/assets/javascripts/effective_cpd/activities.js +6 -0
- data/app/controllers/admin/cpd_special_rules_controller.rb +13 -0
- data/app/datatables/admin/effective_cpd_special_rules_datatable.rb +23 -0
- data/app/helpers/effective_cpd_helper.rb +10 -0
- data/app/models/concerns/effective_cpd_user.rb +29 -0
- data/app/models/effective/cpd_cycle.rb +20 -2
- data/app/models/effective/cpd_rule.rb +20 -5
- 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 +10 -3
- data/app/views/admin/cpd_cycles/_form.html.haml +3 -0
- data/app/views/admin/cpd_cycles/_form_content.html.haml +8 -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/effective/cpd_audits/cpd_audit_level_section.html.haml +1 -1
- data/app/views/effective/cpd_statement_activities/_form.html.haml +1 -0
- data/app/views/effective/cpd_statements/_activities_new.html.haml +3 -0
- data/app/views/effective/cpd_statements/_activities_table.html.haml +1 -1
- data/app/views/effective/cpd_statements/_layout.html.haml +3 -4
- data/app/views/effective/cpd_statements/submit.html.haml +1 -2
- data/config/effective_cpd.rb +2 -0
- data/config/routes.rb +1 -0
- data/db/migrate/01_create_effective_cpd.rb.erb +18 -0
- data/db/seeds.rb +15 -13
- data/lib/effective_cpd.rb +1 -1
- data/lib/effective_cpd/engine.rb +7 -0
- data/lib/effective_cpd/version.rb +1 -1
- data/lib/generators/effective_cpd/install_generator.rb +2 -0
- metadata +9 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c655d5b1c2e948affbfee9558e51b7e66ca5cfd0e43fbf2a58fecc7a3b2d885f
|
|
4
|
+
data.tar.gz: 06fe0a9add6534d48e5b51055343018c75723fa288f9051666fd29767fab0f86
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bd95fa02fad56e41914ac568da4cdaf0b2413297a35fbccf4d8c32659d1e2e30c0e474871d33f124a03f7c962372a9136149cd587a520c6c41447e269692901a
|
|
7
|
+
data.tar.gz: 212aaa715dcee4635d708a579fad03e18bdfd8b253515d2a3b3b2dcaf1419eb7237766c68260d83992c6520d802fc128ebd355537b820578ea823b9362101661
|
data/README.md
CHANGED
|
@@ -134,6 +134,39 @@ As an admin, visit the CPD Categories, then CPD Cycles, and CPD Audit levels.
|
|
|
134
134
|
|
|
135
135
|
Once all these 3 areas have been configured, users can submit statements and audits can be performed.
|
|
136
136
|
|
|
137
|
+
## Required Score
|
|
138
|
+
|
|
139
|
+
You can specify the required score in the CPD Cycle.
|
|
140
|
+
|
|
141
|
+
You can also programatically do it. Add the following to your user class.
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
# This is an ActiveRecord concern to add the has_many :cpd_statements
|
|
145
|
+
effective_cpd_user
|
|
146
|
+
|
|
147
|
+
# We require 100 points in the last 3 years.
|
|
148
|
+
def cpd_statement_required_score(cpd_statement)
|
|
149
|
+
# We always consider the 3 year window, of the passed cpd_statement and the last two statements
|
|
150
|
+
last_two_statements = cpd_statements.select do |statement|
|
|
151
|
+
statement.completed? && statement.cpd_cycle_id < cpd_statement.cpd_cycle_id
|
|
152
|
+
end.last(2)
|
|
153
|
+
|
|
154
|
+
# They can submit 0 0 100
|
|
155
|
+
return 0 if last_two_statements.length < 2
|
|
156
|
+
|
|
157
|
+
# 100 points in the last 3 years.
|
|
158
|
+
required_score = 100
|
|
159
|
+
|
|
160
|
+
# Score so far
|
|
161
|
+
existing_score = last_two_statements.sum { |statement| statement.score }
|
|
162
|
+
raise('expected existing_score to be >= 0') if existing_score < 0
|
|
163
|
+
|
|
164
|
+
# Required score minus previous
|
|
165
|
+
return 0 if existing_score >= required_score
|
|
166
|
+
(required_score - existing_score)
|
|
167
|
+
end
|
|
168
|
+
```
|
|
169
|
+
|
|
137
170
|
## Authorization
|
|
138
171
|
|
|
139
172
|
All authorization checks are handled via the effective_resources gem found in the `config/initializers/effective_resources.rb` file.
|
|
@@ -44,6 +44,12 @@ $(document).on('click', '[data-cpd-edit-activity]', function(event) {
|
|
|
44
44
|
$activity.children('.statement-activity-form').show()
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
+
// When we click any x button
|
|
48
|
+
$(document).on('click', '[data-cpd-collapse]', function(event) {
|
|
49
|
+
event.preventDefault()
|
|
50
|
+
collapse_effective_cpd_activities()
|
|
51
|
+
});
|
|
52
|
+
|
|
47
53
|
// Initializers
|
|
48
54
|
$(document).ready(function() { initialize_effective_cpd_activities() });
|
|
49
55
|
$(document).on('turbolinks:load', function() { initialize_effective_cpd_activities() });
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Admin
|
|
2
|
+
class CpdSpecialRulesController < ApplicationController
|
|
3
|
+
before_action(:authenticate_user!) if defined?(Devise)
|
|
4
|
+
before_action { EffectiveResources.authorize!(self, :admin, :effective_cpd) }
|
|
5
|
+
|
|
6
|
+
include Effective::CrudController
|
|
7
|
+
|
|
8
|
+
def permitted_params
|
|
9
|
+
params.require(:effective_cpd_special_rule).permit!
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Admin
|
|
2
|
+
class EffectiveCpdSpecialRulesDatatable < Effective::Datatable
|
|
3
|
+
datatable do
|
|
4
|
+
col :id, visible: false
|
|
5
|
+
col :created_at, visible: false
|
|
6
|
+
col :updated_at, visible: false
|
|
7
|
+
|
|
8
|
+
col :cpd_cycle, label: cpd_cycle_label.titleize
|
|
9
|
+
|
|
10
|
+
col :cpd_rules
|
|
11
|
+
|
|
12
|
+
col :category
|
|
13
|
+
col :max_credits_per_cycle, label: 'Max ' + cpd_credits_label.titleize
|
|
14
|
+
|
|
15
|
+
actions_col
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
collection do
|
|
19
|
+
cpd_cycle = Effective::CpdCycle.find(attributes[:cpd_cycle_id])
|
|
20
|
+
Effective::CpdSpecialRule.all.deep.where(cpd_cycle: cpd_cycle)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -30,6 +30,16 @@ module EffectiveCpdHelper
|
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
def cpd_statement_submit_label(cpd_statement)
|
|
34
|
+
label = "Yes, I understand I will not be able to submit more #{cpd_credits_label} or modify any of my activities for this #{cpd_cycle_label}, once I submit."
|
|
35
|
+
|
|
36
|
+
if (end_at = cpd_statement.cpd_cycle.end_at).present?
|
|
37
|
+
label += " The last date to submit this form is #{end_at.strftime("%B %d, %Y")}."
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
label
|
|
41
|
+
end
|
|
42
|
+
|
|
33
43
|
def effective_cpd_categories
|
|
34
44
|
@effective_cpd_categories ||= Effective::CpdCategory.deep.sorted
|
|
35
45
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# EffectiveCpdUser
|
|
2
|
+
#
|
|
3
|
+
# Mark your user model with effective_cpd_user to get a few helpers
|
|
4
|
+
# And user specific point required scores
|
|
5
|
+
|
|
6
|
+
module EffectiveCpdUser
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
module Base
|
|
10
|
+
def effective_cpd_user
|
|
11
|
+
include ::EffectiveCpdUser
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
included do
|
|
16
|
+
has_many :cpd_statements, -> { Effective::CpdStatement.sorted }, class_name: 'Effective::CpdStatement'
|
|
17
|
+
has_many :cpd_audits, -> { Effective::CpdAudit.sorted }, inverse_of: :user, class_name: 'Effective::CpdAudit'
|
|
18
|
+
has_many :cpd_audit_reviews, -> { Effective::CpdAuditReview.sorted }, inverse_of: :user, class_name: 'Effective::CpdAuditReview'
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def cpd_statement_required_score(cpd_statement)
|
|
22
|
+
nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
module ClassMethods
|
|
26
|
+
def effective_cpd_user?; true; end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
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
|
|
@@ -76,13 +81,26 @@ module Effective
|
|
|
76
81
|
attributes = cycle.dup.attributes.except('title', 'token', 'start_at', 'end_at')
|
|
77
82
|
assign_attributes(attributes)
|
|
78
83
|
|
|
79
|
-
[: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|
|
|
80
85
|
self.send("#{rich_text}=", cycle.send(rich_text))
|
|
81
86
|
end
|
|
82
87
|
|
|
83
88
|
cycle.cpd_rules.each do |rule|
|
|
84
89
|
attributes = rule.dup.attributes.except('cpd_cycle_id')
|
|
85
|
-
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
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
cycle.cpd_special_rules.each do |special_rule|
|
|
98
|
+
attributes = special_rule.dup.attributes.except('cpd_cycle_id')
|
|
99
|
+
cpd_special_rule = self.cpd_special_rules.build(attributes)
|
|
100
|
+
|
|
101
|
+
special_rule.ruleables.each do |ruleable|
|
|
102
|
+
cpd_special_rule.cpd_special_rule_mates.build(cpd_rule: self.rule_for(ruleable))
|
|
103
|
+
end
|
|
86
104
|
end
|
|
87
105
|
|
|
88
106
|
self
|
|
@@ -3,6 +3,12 @@ module Effective
|
|
|
3
3
|
belongs_to :cpd_cycle
|
|
4
4
|
belongs_to :ruleable, polymorphic: true # Activity or Category
|
|
5
5
|
|
|
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
|
+
|
|
6
12
|
if respond_to?(:log_changes)
|
|
7
13
|
log_changes(to: :cpd_cycle)
|
|
8
14
|
end
|
|
@@ -12,7 +18,6 @@ module Effective
|
|
|
12
18
|
|
|
13
19
|
effective_resource do
|
|
14
20
|
# A plaintext description of the formula
|
|
15
|
-
# For a Category: A maximum of 35 PDHs/year may be claimed in the Contributions to Knowledge category
|
|
16
21
|
# For a Activity: 15 hours of work equals 1 credit
|
|
17
22
|
credit_description :text
|
|
18
23
|
|
|
@@ -23,6 +28,7 @@ module Effective
|
|
|
23
28
|
formula :string
|
|
24
29
|
|
|
25
30
|
# Maximum number of cycles can carry forward
|
|
31
|
+
# Only considered for activities
|
|
26
32
|
max_cycles_can_carry_forward :integer
|
|
27
33
|
|
|
28
34
|
# Cannot be entered in this cycle
|
|
@@ -31,19 +37,22 @@ module Effective
|
|
|
31
37
|
timestamps
|
|
32
38
|
end
|
|
33
39
|
|
|
34
|
-
scope :
|
|
40
|
+
scope :sorted, -> { order(:id) }
|
|
41
|
+
scope :deep, -> { with_rich_text_category_credit_description.includes(:cpd_cycle, :ruleable) }
|
|
35
42
|
scope :categories, -> { where(ruleable_type: 'Effective::CpdCategory') }
|
|
36
43
|
scope :activities, -> { where(ruleable_type: 'Effective::CpdActivity') }
|
|
37
44
|
scope :unavailable, -> { where(unavailable: true) }
|
|
38
45
|
|
|
39
46
|
#validates :cpd_cycle_id, uniqueness: { scope: [:ruleable_id, :ruleable_type] }
|
|
40
|
-
validates :credit_description, presence: true
|
|
41
47
|
validates :max_credits_per_cycle, numericality: { greater_than: 0, allow_nil: true }
|
|
42
|
-
validates :max_cycles_can_carry_forward, numericality: {
|
|
48
|
+
validates :max_cycles_can_carry_forward, numericality: { greater_than_or_equal_to: 0, allow_nil: true }
|
|
43
49
|
|
|
44
50
|
validates :formula, presence: true, if: -> { activity? }
|
|
45
51
|
validates :formula, absence: true, if: -> { category? }
|
|
46
52
|
|
|
53
|
+
validates :credit_description, presence: true, if: -> { activity? }
|
|
54
|
+
validates :category_credit_description, presence: true, if: -> { category? }
|
|
55
|
+
|
|
47
56
|
validate(if: -> { formula.present? }) do
|
|
48
57
|
if formula.gsub('amount2', '').gsub('amount', '').gsub(' ', '').match(INVALID_FORMULA_CHARS).present?
|
|
49
58
|
self.errors.add(:formula, "may only contain amount, amount2 and 0-9 + - / * ( ) characters")
|
|
@@ -79,7 +88,13 @@ module Effective
|
|
|
79
88
|
end
|
|
80
89
|
|
|
81
90
|
def to_s
|
|
82
|
-
|
|
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
|
|
83
98
|
end
|
|
84
99
|
|
|
85
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
|
|
@@ -54,9 +54,9 @@ module Effective
|
|
|
54
54
|
self.score ||= 0
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
-
validate(if: -> { completed?
|
|
58
|
-
min =
|
|
59
|
-
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
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
with_options(if: -> { current_step == :agreements }) do
|
|
@@ -87,6 +87,13 @@ module Effective
|
|
|
87
87
|
submitted_at.present?
|
|
88
88
|
end
|
|
89
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
|
|
95
|
+
end
|
|
96
|
+
|
|
90
97
|
def carry_forward
|
|
91
98
|
cpd_statement_activities.sum { |activity| activity.carry_forward.to_i }
|
|
92
99
|
end
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
= render 'admin/cpd_cycles/form_cpd_cycle', cpd_cycle: cpd_cycle
|
|
4
4
|
|
|
5
5
|
- if cpd_cycle.persisted?
|
|
6
|
+
= tab 'Special Rules' do
|
|
7
|
+
= render 'admin/cpd_cycles/form_cpd_special_rules', cpd_cycle: cpd_cycle
|
|
8
|
+
|
|
6
9
|
= tab 'Category and Activity Rules' do
|
|
7
10
|
= tabs do
|
|
8
11
|
- Effective::CpdCategory.deep.sorted.each do |cpd_category|
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
= effective_form_with(model: [:admin, cpd_cycle], engine: true) do |f|
|
|
2
|
+
%h2 All Steps Content
|
|
3
|
+
|
|
2
4
|
.card.mb-4
|
|
3
5
|
.card-body
|
|
4
6
|
%h5.card-title All Steps Content
|
|
5
7
|
= f.rich_text_area :all_steps_content, label: false, hint: 'displayed on all statement steps'
|
|
6
8
|
|
|
9
|
+
.card.mb-4
|
|
10
|
+
.card-body
|
|
11
|
+
%h5.card-title Sidebar Content
|
|
12
|
+
= f.rich_text_area :sidebar_content, label: false, hint: 'displayed on the sidebar on all statement steps'
|
|
13
|
+
|
|
14
|
+
%h2 Individual Steps Content
|
|
7
15
|
.card.mb-4
|
|
8
16
|
.card-body
|
|
9
17
|
%h5.card-title Start Step
|
|
@@ -6,12 +6,17 @@
|
|
|
6
6
|
%p= link_to 'Edit Category', edit_path, target: '_blank'
|
|
7
7
|
|
|
8
8
|
= f.fields_for :cpd_rules, cpd_cycle.rule_for(cpd_category) do |fc|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
.row
|
|
10
|
+
.col
|
|
11
|
+
= fc.number_field :max_credits_per_cycle,
|
|
12
|
+
label: "Max #{cpd_credits_label} per #{cpd_cycle_label}",
|
|
13
|
+
hint: "The maximum number of #{cpd_credits_label} that may be earned in this category for this #{cpd_cycle_label}. Leave blank for no limit."
|
|
14
|
+
.col
|
|
15
|
+
= fc.select :cpd_special_rule_ids, cpd_cycle.cpd_special_rules, multiple: true,
|
|
16
|
+
label: 'Special Rules', hint: "Special rules operate on more than one category at a time"
|
|
17
|
+
|
|
18
|
+
= fc.rich_text_area :category_credit_description,
|
|
19
|
+
hint: "A description of the maximum #{cpd_credits_label} and carry forward policy of activities for this category"
|
|
15
20
|
|
|
16
21
|
= fc.check_box :unavailable, label: "Unavailable in this #{cpd_cycle_label}"
|
|
17
22
|
|
|
@@ -49,7 +54,7 @@
|
|
|
49
54
|
|
|
50
55
|
= fa.number_field :max_cycles_can_carry_forward,
|
|
51
56
|
label: "Max #{cpd_cycles_label} can carry forward",
|
|
52
|
-
hint: "leave blank for no limit"
|
|
57
|
+
hint: "leave blank for no limit. enter zero for no carry forward."
|
|
53
58
|
|
|
54
59
|
= fa.check_box :unavailable, label: "Unavailable in this #{cpd_cycle_label}"
|
|
55
60
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
= effective_form_with(model: [:admin, cpd_special_rule], engine: true) do |f|
|
|
2
|
+
- if inline_datatable?
|
|
3
|
+
= f.hidden_field :cpd_cycle_id
|
|
4
|
+
- else
|
|
5
|
+
= f.select :cpd_cycle_id, Effective::CpdCycle.sorted.all, label: cpd_cycle_label.titleize
|
|
6
|
+
|
|
7
|
+
= f.select :category, Effective::CpdSpecialRule::CATEGORIES
|
|
8
|
+
|
|
9
|
+
= f.show_if :category, 'cumulative max credits' do
|
|
10
|
+
= f.number_field :max_credits_per_cycle, label: "Cumulative max credits per #{cpd_cycle_label}",
|
|
11
|
+
hint: "The cumulative max #{cpd_credits_label} per #{cpd_cycle_label} that can be earned between the following categories."
|
|
12
|
+
|
|
13
|
+
- cpd_category_rules = cpd_special_rule.cpd_cycle.cpd_rules.select(&:category?)
|
|
14
|
+
|
|
15
|
+
= f.select :cpd_rule_ids, cpd_category_rules, label: 'Cumulative max included categories',
|
|
16
|
+
hint: "These categories will shared a maximum cumulative #{cpd_credits_label} per #{cpd_cycle_label}."
|
|
17
|
+
|
|
18
|
+
= effective_submit(f)
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
- cpd_audit_response = resource.cpd_audit_response(cpd_audit_level_question)
|
|
13
13
|
|
|
14
14
|
= f.fields_for :cpd_audit_responses, cpd_audit_response do |fcar|
|
|
15
|
-
= render('/effective/cpd_audit_responses/fields', f: fcar, cpd_audit_level_question: cpd_audit_level_question)
|
|
15
|
+
.mt-2= render('/effective/cpd_audit_responses/fields', f: fcar, cpd_audit_level_question: cpd_audit_level_question)
|
|
16
16
|
|
|
17
17
|
- if cpd_audit_level_section.bottom_content.present?
|
|
18
18
|
.mb-2= cpd_audit_level_section.bottom_content
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
- next if rule.unavailable?
|
|
8
8
|
|
|
9
9
|
= tab(category) do
|
|
10
|
+
.float-right= link_to icon('x', class: 'small-1'), '#', 'data-cpd-collapse': true
|
|
11
|
+
|
|
10
12
|
%h3
|
|
11
13
|
#{category}
|
|
12
14
|
%small
|
|
@@ -22,6 +24,7 @@
|
|
|
22
24
|
.mb-3
|
|
23
25
|
%strong #{cpd_credits_label.titleize} Calculation
|
|
24
26
|
%br
|
|
27
|
+
= rule.category_credit_description
|
|
25
28
|
= rule.credit_description
|
|
26
29
|
|
|
27
30
|
%div
|
|
@@ -22,10 +22,7 @@
|
|
|
22
22
|
%h4= resource.score.to_i
|
|
23
23
|
|
|
24
24
|
%td
|
|
25
|
-
|
|
26
|
-
#{cpd_credits_label} out of #{cpd_cycle.required_score} required
|
|
27
|
-
- else
|
|
28
|
-
#{cpd_credits_label} in current #{cpd_cycle_label}
|
|
25
|
+
#{cpd_credits_label} out of #{resource.required_score} required
|
|
29
26
|
|
|
30
27
|
%tr
|
|
31
28
|
%td
|
|
@@ -33,5 +30,7 @@
|
|
|
33
30
|
|
|
34
31
|
%td #{cpd_credits_label} in last 3 #{cpd_cycles_label} (including this #{cpd_cycle_label})
|
|
35
32
|
|
|
33
|
+
= cpd_cycle.sidebar_content
|
|
34
|
+
|
|
36
35
|
.col-9
|
|
37
36
|
= yield
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
= effective_form_with(model: resource, url: wizard_path(step), method: :put) do |f|
|
|
15
15
|
= f.hidden_field :current_step
|
|
16
16
|
|
|
17
|
-
= f.check_box :confirm_readonly,
|
|
18
|
-
label: 'Yes, I understand that I will not be able to modify this statement after submission'
|
|
17
|
+
= f.check_box :confirm_readonly, label: cpd_statement_submit_label(f.object)
|
|
19
18
|
|
|
20
19
|
= f.submit 'Submit Statement', center: true
|
data/config/effective_cpd.rb
CHANGED
|
@@ -4,6 +4,8 @@ EffectiveCpd.setup do |config|
|
|
|
4
4
|
|
|
5
5
|
config.cpd_cycles_table_name = :cpd_cycles
|
|
6
6
|
config.cpd_rules_table_name = :cpd_rules
|
|
7
|
+
config.cpd_special_rules_table_name = :cpd_special_rules
|
|
8
|
+
config.cpd_special_rule_mates_table_name = :cpd_special_rule_mates
|
|
7
9
|
|
|
8
10
|
config.cpd_statements_table_name = :cpd_statements
|
|
9
11
|
config.cpd_statement_activities_table_name = :cpd_statement_activities
|
data/config/routes.rb
CHANGED
|
@@ -32,6 +32,7 @@ EffectiveCpd::Engine.routes.draw do
|
|
|
32
32
|
resources :cpd_activities, except: [:show]
|
|
33
33
|
resources :cpd_cycles, except: [:show]
|
|
34
34
|
resources :cpd_rules, only: [:index]
|
|
35
|
+
resources :cpd_special_rules, except: [:show]
|
|
35
36
|
|
|
36
37
|
resources :cpd_statements, only: [:index, :show]
|
|
37
38
|
|
|
@@ -51,6 +51,24 @@ class CreateEffectiveCpd < ActiveRecord::Migration[6.0]
|
|
|
51
51
|
t.datetime :created_at
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
+
create_table <%= @cpd_special_rules_table_name %> do |t|
|
|
55
|
+
t.references :cpd_cycle
|
|
56
|
+
|
|
57
|
+
t.integer :max_credits_per_cycle
|
|
58
|
+
t.string :category
|
|
59
|
+
|
|
60
|
+
t.datetime :updated_at
|
|
61
|
+
t.datetime :created_at
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
create_table <%= @cpd_special_rule_mates_table_name %> do |t|
|
|
65
|
+
t.references :cpd_rule
|
|
66
|
+
t.references :cpd_special_rule
|
|
67
|
+
|
|
68
|
+
t.datetime :updated_at
|
|
69
|
+
t.datetime :created_at
|
|
70
|
+
end
|
|
71
|
+
|
|
54
72
|
create_table <%= @cpd_statement_activities_table_name %> do |t|
|
|
55
73
|
t.references :cpd_statement
|
|
56
74
|
t.references :cpd_activity
|
data/db/seeds.rb
CHANGED
|
@@ -7,9 +7,10 @@ if Rails.env.test?
|
|
|
7
7
|
Effective::CpdCategory.delete_all
|
|
8
8
|
Effective::CpdActivity.delete_all
|
|
9
9
|
Effective::CpdRule.delete_all
|
|
10
|
+
Effective::CpdSpecialRule.delete_all
|
|
11
|
+
Effective::CpdSpecialRuleMate.delete_all
|
|
10
12
|
|
|
11
13
|
ActionText::RichText.where(record_type: ['Effective::CpdCycle', 'Effective::CpdCycle', 'Effective::CpdActivity', 'Effective::CpdAudit', 'Effective::CpdAuditLevelSection', 'Effective::CpdAuditLevelQuestion']).delete_all
|
|
12
|
-
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
# Build the first CpdCycle
|
|
@@ -18,11 +19,12 @@ cycle = Effective::CpdCycle.create!(
|
|
|
18
19
|
start_at: now.beginning_of_year,
|
|
19
20
|
end_at: now.end_of_year,
|
|
20
21
|
required_score: 100,
|
|
21
|
-
all_steps_content: "
|
|
22
|
-
start_content: "
|
|
23
|
-
activities_content: "
|
|
24
|
-
submit_content: "
|
|
25
|
-
complete_content: "
|
|
22
|
+
all_steps_content: "All Steps Content",
|
|
23
|
+
start_content: "Start Content",
|
|
24
|
+
activities_content: "Activities Content",
|
|
25
|
+
submit_content: "Submit Content",
|
|
26
|
+
complete_content: "Complete Content",
|
|
27
|
+
sidebar_content: "Sidebar Content"
|
|
26
28
|
)
|
|
27
29
|
|
|
28
30
|
# Professional Practice
|
|
@@ -33,7 +35,7 @@ category = Effective::CpdCategory.create!(
|
|
|
33
35
|
Effective::CpdRule.create!(
|
|
34
36
|
cpd_cycle: cycle,
|
|
35
37
|
ruleable: category,
|
|
36
|
-
|
|
38
|
+
category_credit_description: 'Upto a maximum of 20 claimable points per CPD year. Points cannot be carried forward to future years.',
|
|
37
39
|
max_credits_per_cycle: 20
|
|
38
40
|
)
|
|
39
41
|
|
|
@@ -74,7 +76,7 @@ category = Effective::CpdCategory.create!(
|
|
|
74
76
|
Effective::CpdRule.create!(
|
|
75
77
|
cpd_cycle: cycle,
|
|
76
78
|
ruleable: category,
|
|
77
|
-
|
|
79
|
+
category_credit_description: 'Upto a maximum of 20 claimable points per CPD year. Points cannot be carried forward to future years.',
|
|
78
80
|
max_credits_per_cycle: 20
|
|
79
81
|
)
|
|
80
82
|
|
|
@@ -144,7 +146,7 @@ category = Effective::CpdCategory.create!(
|
|
|
144
146
|
Effective::CpdRule.create!(
|
|
145
147
|
cpd_cycle: cycle,
|
|
146
148
|
ruleable: category,
|
|
147
|
-
|
|
149
|
+
category_credit_description: 'Upto a maximum of 35 claimable points per CPD year. Points may be carried over upto a maximum of 2 years after the year in which they were earned.',
|
|
148
150
|
max_credits_per_cycle: 35
|
|
149
151
|
)
|
|
150
152
|
|
|
@@ -183,7 +185,7 @@ category = Effective::CpdCategory.create!(
|
|
|
183
185
|
Effective::CpdRule.create!(
|
|
184
186
|
cpd_cycle: cycle,
|
|
185
187
|
ruleable: category,
|
|
186
|
-
|
|
188
|
+
category_credit_description: 'Upto a maximum of 15 claimable points per CPD year. Points may be carried over upto a maximum of 2 years after the year in which they were earned.',
|
|
187
189
|
max_credits_per_cycle: 15
|
|
188
190
|
)
|
|
189
191
|
|
|
@@ -265,7 +267,7 @@ category = Effective::CpdCategory.create!(
|
|
|
265
267
|
Effective::CpdRule.create!(
|
|
266
268
|
cpd_cycle: cycle,
|
|
267
269
|
ruleable: category,
|
|
268
|
-
|
|
270
|
+
category_credit_description: 'Upto a maximum of 15 claimable points per CPD year. Points may be carried over upto a maximum of 2 years after the year in which they were earned.',
|
|
269
271
|
max_credits_per_cycle: 15
|
|
270
272
|
)
|
|
271
273
|
|
|
@@ -319,7 +321,7 @@ category = Effective::CpdCategory.create!(
|
|
|
319
321
|
Effective::CpdRule.create!(
|
|
320
322
|
cpd_cycle: cycle,
|
|
321
323
|
ruleable: category,
|
|
322
|
-
|
|
324
|
+
category_credit_description: 'Upto a maximum of 30 claimable points per CPD year. Points may be carried over upto a maximum of 2 years after the year in which they were earned.',
|
|
323
325
|
max_credits_per_cycle: 30
|
|
324
326
|
)
|
|
325
327
|
|
|
@@ -421,7 +423,7 @@ category = Effective::CpdCategory.create!(
|
|
|
421
423
|
Effective::CpdRule.create!(
|
|
422
424
|
cpd_cycle: cycle,
|
|
423
425
|
ruleable: category,
|
|
424
|
-
|
|
426
|
+
category_credit_description: 'Upto a maximum of 10 points per mentee or position per CPD year. Points may be carried over upto a maximum of 2 years after the year in which they were earned.',
|
|
425
427
|
max_credits_per_cycle: nil
|
|
426
428
|
)
|
|
427
429
|
|
data/lib/effective_cpd.rb
CHANGED
|
@@ -8,7 +8,7 @@ module EffectiveCpd
|
|
|
8
8
|
def self.config_keys
|
|
9
9
|
[
|
|
10
10
|
:cpd_categories_table_name, :cpd_activities_table_name,
|
|
11
|
-
:cpd_cycles_table_name, :cpd_rules_table_name,
|
|
11
|
+
:cpd_cycles_table_name, :cpd_rules_table_name, :cpd_special_rules_table_name, :cpd_special_rule_mates_table_name,
|
|
12
12
|
:cpd_statements_table_name, :cpd_statement_activities_table_name,
|
|
13
13
|
:cpd_audit_levels_table_name, :cpd_audit_level_sections_table_name,
|
|
14
14
|
:cpd_audit_level_questions_table_name, :cpd_audit_level_question_options_table_name,
|
data/lib/effective_cpd/engine.rb
CHANGED
|
@@ -7,5 +7,12 @@ module EffectiveCpd
|
|
|
7
7
|
eval File.read("#{config.root}/config/effective_cpd.rb")
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
+
# Include acts_as_addressable concern and allow any ActiveRecord object to call it
|
|
11
|
+
initializer 'effective_cpd.active_record' do |app|
|
|
12
|
+
ActiveSupport.on_load :active_record do
|
|
13
|
+
ActiveRecord::Base.extend(EffectiveCpdUser::Base)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
10
17
|
end
|
|
11
18
|
end
|
|
@@ -25,6 +25,8 @@ module EffectiveCpd
|
|
|
25
25
|
|
|
26
26
|
@cpd_cycles_table_name = ':' + EffectiveCpd.cpd_cycles_table_name.to_s
|
|
27
27
|
@cpd_rules_table_name = ':' + EffectiveCpd.cpd_rules_table_name.to_s
|
|
28
|
+
@cpd_special_rules_table_name = ':' + EffectiveCpd.cpd_special_rules_table_name.to_s
|
|
29
|
+
@cpd_special_rule_mates_table_name = ':' + EffectiveCpd.cpd_special_rule_mates_table_name.to_s
|
|
28
30
|
|
|
29
31
|
@cpd_statements_table_name = ':' + EffectiveCpd.cpd_statements_table_name.to_s
|
|
30
32
|
@cpd_statement_activities_table_name = ':' + EffectiveCpd.cpd_statement_activities_table_name.to_s
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: effective_cpd
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 0.1.5
|
|
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: 2021-
|
|
11
|
+
date: 2021-05-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -188,6 +188,7 @@ files:
|
|
|
188
188
|
- app/controllers/admin/cpd_categories_controller.rb
|
|
189
189
|
- app/controllers/admin/cpd_cycles_controller.rb
|
|
190
190
|
- app/controllers/admin/cpd_rules_controller.rb
|
|
191
|
+
- app/controllers/admin/cpd_special_rules_controller.rb
|
|
191
192
|
- app/controllers/admin/cpd_statements_controller.rb
|
|
192
193
|
- app/controllers/effective/cpd_audit_reviews_controller.rb
|
|
193
194
|
- app/controllers/effective/cpd_audits_controller.rb
|
|
@@ -202,6 +203,7 @@ files:
|
|
|
202
203
|
- app/datatables/admin/effective_cpd_categories_datatable.rb
|
|
203
204
|
- app/datatables/admin/effective_cpd_cycles_datatable.rb
|
|
204
205
|
- app/datatables/admin/effective_cpd_rules_datatable.rb
|
|
206
|
+
- app/datatables/admin/effective_cpd_special_rules_datatable.rb
|
|
205
207
|
- app/datatables/admin/effective_cpd_statements_datatable.rb
|
|
206
208
|
- app/datatables/effective_cpd_available_audit_reviews_datatable.rb
|
|
207
209
|
- app/datatables/effective_cpd_available_audits_datatable.rb
|
|
@@ -212,6 +214,7 @@ files:
|
|
|
212
214
|
- app/helpers/effective_cpd_audits_helper.rb
|
|
213
215
|
- app/helpers/effective_cpd_helper.rb
|
|
214
216
|
- app/mailers/effective/cpd_mailer.rb
|
|
217
|
+
- app/models/concerns/effective_cpd_user.rb
|
|
215
218
|
- app/models/effective/cpd_activity.rb
|
|
216
219
|
- app/models/effective/cpd_audit.rb
|
|
217
220
|
- app/models/effective/cpd_audit_level.rb
|
|
@@ -226,6 +229,8 @@ files:
|
|
|
226
229
|
- app/models/effective/cpd_cycle.rb
|
|
227
230
|
- app/models/effective/cpd_rule.rb
|
|
228
231
|
- app/models/effective/cpd_scorer.rb
|
|
232
|
+
- app/models/effective/cpd_special_rule.rb
|
|
233
|
+
- app/models/effective/cpd_special_rule_mate.rb
|
|
229
234
|
- app/models/effective/cpd_statement.rb
|
|
230
235
|
- app/models/effective/cpd_statement_activity.rb
|
|
231
236
|
- app/views/admin/cpd_activities/_form.html.haml
|
|
@@ -252,6 +257,8 @@ files:
|
|
|
252
257
|
- app/views/admin/cpd_cycles/_form_content.html.haml
|
|
253
258
|
- app/views/admin/cpd_cycles/_form_cpd_cycle.html.haml
|
|
254
259
|
- app/views/admin/cpd_cycles/_form_cpd_rules.html.haml
|
|
260
|
+
- app/views/admin/cpd_cycles/_form_cpd_special_rules.html.haml
|
|
261
|
+
- app/views/admin/cpd_special_rules/_form.html.haml
|
|
255
262
|
- app/views/admin/cpd_statements/_cpd_statement.html.haml
|
|
256
263
|
- app/views/effective/cpd_audit_level_questions/_cpd_audit_level_question.html.haml
|
|
257
264
|
- app/views/effective/cpd_audit_responses/_cpd_audit_response.html.haml
|