effective_cpd 0.1.2 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -9
  3. data/app/assets/javascripts/effective_cpd/activities.js +6 -0
  4. data/app/controllers/admin/cpd_special_rules_controller.rb +13 -0
  5. data/app/datatables/admin/effective_cpd_special_rules_datatable.rb +23 -0
  6. data/app/helpers/effective_cpd_helper.rb +10 -0
  7. data/app/models/effective/cpd_cycle.rb +20 -2
  8. data/app/models/effective/cpd_rule.rb +24 -9
  9. data/app/models/effective/cpd_scorer.rb +23 -1
  10. data/app/models/effective/cpd_special_rule.rb +54 -0
  11. data/app/models/effective/cpd_special_rule_mate.rb +6 -0
  12. data/app/views/admin/cpd_cycles/_form.html.haml +3 -0
  13. data/app/views/admin/cpd_cycles/_form_content.html.haml +8 -0
  14. data/app/views/admin/cpd_cycles/_form_cpd_rules.html.haml +41 -33
  15. data/app/views/admin/cpd_cycles/_form_cpd_special_rules.html.haml +4 -0
  16. data/app/views/admin/cpd_special_rules/_form.html.haml +18 -0
  17. data/app/views/effective/cpd_statement_activities/_form.html.haml +1 -0
  18. data/app/views/effective/cpd_statements/_activities_new.html.haml +3 -0
  19. data/app/views/effective/cpd_statements/_activities_table.html.haml +1 -1
  20. data/app/views/effective/cpd_statements/_layout.html.haml +2 -0
  21. data/app/views/effective/cpd_statements/submit.html.haml +1 -2
  22. data/config/effective_cpd.rb +2 -0
  23. data/config/routes.rb +1 -0
  24. data/db/migrate/01_create_effective_cpd.rb.erb +18 -0
  25. data/db/seeds.rb +15 -13
  26. data/lib/effective_cpd.rb +1 -1
  27. data/lib/effective_cpd/version.rb +1 -1
  28. data/lib/generators/effective_cpd/install_generator.rb +2 -0
  29. metadata +8 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 789dc178c0c706f7d63d166f48e512b36b3f905eb6e70abd242f98924773a679
4
- data.tar.gz: 86e4bb9b95b8a53dd810316b3d4ea9ca2031c8063155cc57d1e192f29b3a84b5
3
+ metadata.gz: fa00b7ca9d6039709adb5c4948137632b28eeb8d8c551b5b489d9c4070722b51
4
+ data.tar.gz: b53448836a6adc80ad1430ad3c569b5ee4f19cb1bc10339dbc222b44998dd97c
5
5
  SHA512:
6
- metadata.gz: d96d0a42c29587ab1ead57f7b6707f3acc9e607f0eb9fdf9c05336242491351833a2e14ddb81b883c1f8b811ca3a8712a8814ba97fec679ebc0d5b635b04f69a
7
- data.tar.gz: e9d5453a4816d34b7668027b96c82a3a2208d7158a656ebaf02fff1776f5dc03e642b2e3b9d34272dcd28d94d6ced94310470d85fdd532c51eb4628ab9ba6f97
6
+ metadata.gz: 9bbc1c8162e0c595d1c51ea8148caa59de3654993b5764a177a5c66eda39ed7f29554a2bb1cf2f31f3b1f2d8eeaf6373866e64bbbdcf00e3bf6f796b8e918ddb
7
+ data.tar.gz: 3f1cfa5651851f517a697f9cf540d3319b0f17583557d0e5dcf0d2a1e32c9de49a96b421c82b458677191650f7e722bd5468e133a610844f55a5474313324e12
data/README.md CHANGED
@@ -144,18 +144,26 @@ You can also programatically do it. Add the following to your user class.
144
144
  # This is an ActiveRecord concern to add the has_many :cpd_statements
145
145
  effective_cpd_user
146
146
 
147
- # Just this one magic method. Chcked when submitting the passed cpd_Statement
148
- # Must be 100 points in the last 3 years. Can submit 0 0 100 or 33 33 34
147
+ # We require 100 points in the last 3 years.
149
148
  def cpd_statement_required_score(cpd_statement)
150
- # All completed cpd statements before this cpd_statement
151
- existing = cpd_statements.select { |s| s.completed? && s.cpd_cycle_id < cpd_statement.cpd_cycle_id }
152
- return 0 if existing.length < 2
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
153
 
154
- existing_score = others.last(2).map { |statement| statement.score }.sum
155
- raise('expected existing to be >= 0') if existing < 0
156
- return 0 if existing >= 100
154
+ # They can submit 0 0 100
155
+ return 0 if last_two_statements.length < 2
157
156
 
158
- (100 - existing)
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)
159
167
  end
160
168
  ```
161
169
 
@@ -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
@@ -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, :agreements_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,16 +3,21 @@ 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
9
15
 
10
- # Only permit the words amount, amount2 and any charater 0-9 + - / * ( )
11
- INVALID_FORMULA_CHARS = /[^0-9\+\-\/\*\(\)]/
16
+ # Only permit the words amount, amount2 and any charater 0-9 + - / * . ( )
17
+ INVALID_FORMULA_CHARS = /[^0-9\+\-\.\/\*\(\)]/
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,22 +37,25 @@ module Effective
31
37
  timestamps
32
38
  end
33
39
 
34
- scope :deep, -> { includes(:cpd_cycle, :ruleable) }
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: { greater_than: 0, allow_nil: true }
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
- self.errors.add(:formula, "may only contain amount, amount2 and 0-9 + - / * ( ) characters")
58
+ self.errors.add(:formula, "may only contain amount, amount2 and 0-9 + - / * . ( ) characters")
50
59
  else
51
60
  begin
52
61
  eval_equation(amount: 0, amount2: 0)
@@ -79,7 +88,13 @@ module Effective
79
88
  end
80
89
 
81
90
  def to_s
82
- formula.presence || 'category'
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?
@@ -103,7 +118,7 @@ module Effective
103
118
 
104
119
  def eval_equation(amount: nil, amount2: nil)
105
120
  equation = formula.gsub('amount2', amount2.to_s).gsub('amount', amount.to_s)
106
- eval(equation).to_i
121
+ eval(equation).round.to_i
107
122
  end
108
123
 
109
124
  end
@@ -78,7 +78,7 @@ module Effective
78
78
  end
79
79
  end
80
80
 
81
- # This enforced CycleCategory.max_credits_per_cycle
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
@@ -0,0 +1,6 @@
1
+ module Effective
2
+ class CpdSpecialRuleMate < ActiveRecord::Base
3
+ belongs_to :cpd_rule
4
+ belongs_to :cpd_special_rule
5
+ end
6
+ 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
@@ -1,4 +1,5 @@
1
1
  - edit_path = effective_cpd.edit_admin_cpd_category_path(cpd_category)
2
+ - cpd_rule = cpd_cycle.rule_for(cpd_category)
2
3
 
3
4
  = effective_form_with(model: [:admin, cpd_cycle], engine: true) do |f|
4
5
  %h2= cpd_category
@@ -6,51 +7,58 @@
6
7
  %p= link_to 'Edit Category', edit_path, target: '_blank'
7
8
 
8
9
  = f.fields_for :cpd_rules, cpd_cycle.rule_for(cpd_category) do |fc|
9
- = fc.number_field :max_credits_per_cycle,
10
- label: "Max #{cpd_credits_label} per #{cpd_cycle_label}",
11
- 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."
12
-
13
- = fc.text_field :credit_description,
14
- hint: "A simple description of the maximum #{cpd_credits_label} and carry forward policy of activities for this category"
10
+ .row
11
+ .col
12
+ = fc.number_field :max_credits_per_cycle,
13
+ label: "Max #{cpd_credits_label} per #{cpd_cycle_label}",
14
+ 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."
15
+ .col
16
+ = fc.select :cpd_special_rule_ids, cpd_cycle.cpd_special_rules, multiple: true,
17
+ label: 'Special Rules', hint: "Special rules operate on more than one category at a time"
18
+
19
+ = fc.rich_text_area :category_credit_description,
20
+ hint: "A description of the maximum #{cpd_credits_label} and carry forward policy of activities for this category"
15
21
 
16
22
  = fc.check_box :unavailable, label: "Unavailable in this #{cpd_cycle_label}"
17
23
 
18
24
  = f.submit
19
25
 
20
- = effective_form_with(model: [:admin, cpd_cycle], engine: true) do |f|
21
- %h2 Activities
22
- - rules = cpd_category.cpd_activities.map { |activity| cpd_cycle.rule_for(activity) }
26
+ - unless cpd_rule.unavailable?
23
27
 
24
- = f.fields_for :cpd_rules, rules do |fa|
25
- - activity = fa.object.ruleable
28
+ = effective_form_with(model: [:admin, cpd_cycle], engine: true) do |f|
29
+ %h2 Activities
30
+ - rules = cpd_category.cpd_activities.map { |activity| cpd_cycle.rule_for(activity) }
26
31
 
27
- = card do
28
- .row
29
- .col
30
- %h5.card-title= activity
31
- - if activity.body.present?
32
- %p= activity.body
32
+ = f.fields_for :cpd_rules, rules do |fa|
33
+ - activity = fa.object.ruleable
33
34
 
34
- %p= link_to 'Edit Activity', edit_path, target: '_blank'
35
+ = card do
36
+ .row
37
+ .col
38
+ %h5.card-title= activity
39
+ - if activity.body.present?
40
+ %p= activity.body
35
41
 
36
- = f.static_field :amount_label, value: activity.amount_label.presence || '-'
37
- = f.static_field :amount2_label, value: activity.amount2_label.presence || '-'
38
- = f.static_field :requires_upload_file, value: (activity.requires_upload_file ? 'Yes' : '-')
42
+ %p= link_to 'Edit Activity', edit_path, target: '_blank'
39
43
 
40
- .col
41
- = fa.text_field :formula, hint: cpd_rule_formula_hint(activity)
44
+ = f.static_field :amount_label, value: activity.amount_label.presence || '-'
45
+ = f.static_field :amount2_label, value: activity.amount2_label.presence || '-'
46
+ = f.static_field :requires_upload_file, value: (activity.requires_upload_file ? 'Yes' : '-')
42
47
 
43
- = fa.text_field :credit_description, label: "#{cpd_credits_label.titleize} description",
44
- hint: "A simple description of the formula and its #{cpd_credits_label} calculation"
48
+ .col
49
+ = fa.text_field :formula, hint: cpd_rule_formula_hint(activity)
45
50
 
46
- = fa.number_field :max_credits_per_cycle,
47
- label: "Max #{cpd_credits_label} per #{cpd_cycle_label}",
48
- hint: 'leave blank for no limit'
51
+ = fa.text_field :credit_description, label: "#{cpd_credits_label.titleize} description",
52
+ hint: "A simple description of the formula and its #{cpd_credits_label} calculation"
49
53
 
50
- = fa.number_field :max_cycles_can_carry_forward,
51
- label: "Max #{cpd_cycles_label} can carry forward",
52
- hint: "leave blank for no limit"
54
+ = fa.number_field :max_credits_per_cycle,
55
+ label: "Max #{cpd_credits_label} per #{cpd_cycle_label}",
56
+ hint: 'leave blank for no limit'
53
57
 
54
- = fa.check_box :unavailable, label: "Unavailable in this #{cpd_cycle_label}"
58
+ = fa.number_field :max_cycles_can_carry_forward,
59
+ label: "Max #{cpd_cycles_label} can carry forward",
60
+ hint: "leave blank for no limit. enter zero for no carry forward."
55
61
 
56
- = f.submit
62
+ = fa.check_box :unavailable, label: "Unavailable in this #{cpd_cycle_label}"
63
+
64
+ = f.submit
@@ -0,0 +1,4 @@
1
+ %p Special rules operate on more than one category at a time.
2
+
3
+ - datatable = Admin::EffectiveCpdSpecialRulesDatatable.new(cpd_cycle_id: cpd_cycle.id)
4
+ = render_datatable(datatable, inline: true)
@@ -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)
@@ -6,6 +6,7 @@
6
6
  - activity = cpd_statement_activity.cpd_activity
7
7
  - rule = statement.cpd_cycle.rule_for(activity)
8
8
 
9
+ .float-right= link_to icon('x', class: 'small-1'), '#', 'data-cpd-collapse': true
9
10
  %h3= activity
10
11
 
11
12
  - if activity.body.present?
@@ -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
@@ -50,7 +50,7 @@
50
50
 
51
51
  .statement-activity-form.row{style: 'display: none;'}
52
52
  .col
53
- .border.p-3
53
+ .border.px-3
54
54
  = yield(statement_activity)
55
55
 
56
56
  %hr
@@ -30,5 +30,7 @@
30
30
 
31
31
  %td #{cpd_credits_label} in last 3 #{cpd_cycles_label} (including this #{cpd_cycle_label})
32
32
 
33
+ = cpd_cycle.sidebar_content
34
+
33
35
  .col-9
34
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
@@ -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: "<div>All Steps Content</div>",
22
- start_content: "<div>Start Content</div>",
23
- activities_content: "<div>Activities Content</div>",
24
- submit_content: "<div>Submit Content</div>",
25
- complete_content: "<div>Complete Content</div>"
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
- credit_description: 'Upto a maximum of 20 claimable points per CPD year. Points cannot be carried forward to future years.',
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
- credit_description: 'Upto a maximum of 20 claimable points per CPD year. Points cannot be carried forward to future years.',
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
- 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.',
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
- 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.',
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
- 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.',
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
- 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.',
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
- 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.',
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,
@@ -1,3 +1,3 @@
1
1
  module EffectiveCpd
2
- VERSION = '0.1.2'
2
+ VERSION = '0.1.7'
3
3
  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: 0.1.2
4
+ version: 0.1.7
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-05-05 00:00:00.000000000 Z
11
+ date: 2021-05-26 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
@@ -227,6 +229,8 @@ files:
227
229
  - app/models/effective/cpd_cycle.rb
228
230
  - app/models/effective/cpd_rule.rb
229
231
  - app/models/effective/cpd_scorer.rb
232
+ - app/models/effective/cpd_special_rule.rb
233
+ - app/models/effective/cpd_special_rule_mate.rb
230
234
  - app/models/effective/cpd_statement.rb
231
235
  - app/models/effective/cpd_statement_activity.rb
232
236
  - app/views/admin/cpd_activities/_form.html.haml
@@ -253,6 +257,8 @@ files:
253
257
  - app/views/admin/cpd_cycles/_form_content.html.haml
254
258
  - app/views/admin/cpd_cycles/_form_cpd_cycle.html.haml
255
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
256
262
  - app/views/admin/cpd_statements/_cpd_statement.html.haml
257
263
  - app/views/effective/cpd_audit_level_questions/_cpd_audit_level_question.html.haml
258
264
  - app/views/effective/cpd_audit_responses/_cpd_audit_response.html.haml