effective_cpd 0.0.1

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 (66) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +117 -0
  4. data/Rakefile +18 -0
  5. data/app/assets/config/effective_cpd_manifest.js +2 -0
  6. data/app/assets/javascripts/effective_cpd.js +2 -0
  7. data/app/assets/javascripts/effective_cpd/activities.js +49 -0
  8. data/app/assets/javascripts/effective_cpd/activities_new.js +21 -0
  9. data/app/assets/stylesheets/effective_cpd.scss +1 -0
  10. data/app/assets/stylesheets/effective_cpd/_activities.scss +19 -0
  11. data/app/controllers/admin/cpd_activities_controller.rb +13 -0
  12. data/app/controllers/admin/cpd_categories_controller.rb +13 -0
  13. data/app/controllers/admin/cpd_cycles_controller.rb +13 -0
  14. data/app/controllers/admin/cpd_rules_controller.rb +13 -0
  15. data/app/controllers/admin/cpd_statements_controller.rb +13 -0
  16. data/app/controllers/effective/cpd_cycles_controller.rb +19 -0
  17. data/app/controllers/effective/cpd_statement_activities_controller.rb +27 -0
  18. data/app/controllers/effective/cpd_statements_controller.rb +71 -0
  19. data/app/datatables/admin/effective_cpd_activities_datatable.rb +31 -0
  20. data/app/datatables/admin/effective_cpd_categories_datatable.rb +24 -0
  21. data/app/datatables/admin/effective_cpd_cycles_datatable.rb +22 -0
  22. data/app/datatables/admin/effective_cpd_rules_datatable.rb +43 -0
  23. data/app/datatables/admin/effective_cpd_statements_datatable.rb +30 -0
  24. data/app/datatables/effective_cpd_datatable.rb +30 -0
  25. data/app/datatables/effective_cpd_statements_datatable.rb +23 -0
  26. data/app/helpers/effective_cpd_helper.rb +37 -0
  27. data/app/mailers/effective/cpd_mailer.rb +7 -0
  28. data/app/models/effective/cpd_activity.rb +41 -0
  29. data/app/models/effective/cpd_category.rb +35 -0
  30. data/app/models/effective/cpd_cycle.rb +113 -0
  31. data/app/models/effective/cpd_rule.rb +108 -0
  32. data/app/models/effective/cpd_scorer.rb +158 -0
  33. data/app/models/effective/cpd_statement.rb +95 -0
  34. data/app/models/effective/cpd_statement_activity.rb +78 -0
  35. data/app/views/admin/cpd_activities/_form.html.haml +20 -0
  36. data/app/views/admin/cpd_categories/_form.html.haml +21 -0
  37. data/app/views/admin/cpd_categories/_form_cpd_category.html.haml +5 -0
  38. data/app/views/admin/cpd_cycles/_form.html.haml +17 -0
  39. data/app/views/admin/cpd_cycles/_form_content.html.haml +32 -0
  40. data/app/views/admin/cpd_cycles/_form_cpd_cycle.html.haml +21 -0
  41. data/app/views/admin/cpd_cycles/_form_cpd_rules.html.haml +56 -0
  42. data/app/views/admin/cpd_statements/_form.html.haml +6 -0
  43. data/app/views/effective/cpd_statement_activities/_form.html.haml +70 -0
  44. data/app/views/effective/cpd_statements/_activities.html.haml +64 -0
  45. data/app/views/effective/cpd_statements/_activities_new.html.haml +39 -0
  46. data/app/views/effective/cpd_statements/_agreements.html.haml +6 -0
  47. data/app/views/effective/cpd_statements/_cpd_statement.html.haml +5 -0
  48. data/app/views/effective/cpd_statements/_layout.html.haml +37 -0
  49. data/app/views/effective/cpd_statements/_summary.html.haml +36 -0
  50. data/app/views/effective/cpd_statements/activities.html.haml +25 -0
  51. data/app/views/effective/cpd_statements/agreements.html.haml +14 -0
  52. data/app/views/effective/cpd_statements/complete.html.haml +13 -0
  53. data/app/views/effective/cpd_statements/start.html.haml +13 -0
  54. data/app/views/effective/cpd_statements/submit.html.haml +20 -0
  55. data/app/views/layouts/effective_cpd_mailer_layout.html.haml +7 -0
  56. data/config/effective_cpd.rb +29 -0
  57. data/config/routes.rb +28 -0
  58. data/db/migrate/01_create_effective_cpd.rb.erb +98 -0
  59. data/db/seeds.rb +472 -0
  60. data/lib/effective_cpd.rb +18 -0
  61. data/lib/effective_cpd/engine.rb +11 -0
  62. data/lib/effective_cpd/version.rb +3 -0
  63. data/lib/generators/effective_cpd/install_generator.rb +46 -0
  64. data/lib/generators/templates/effective_cpd_mailer_preview.rb +4 -0
  65. data/lib/tasks/effective_cpd_tasks.rake +6 -0
  66. metadata +233 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 96dbf47b702128c10b5b8f1ee07f9fb4798efdd77220b506b75124737e55e8ec
4
+ data.tar.gz: a5e0a5a658c43ffef382a8becb65b7a84b61a24a01f701dbb7daaecf63aa391f
5
+ SHA512:
6
+ metadata.gz: 3ee41586315d02240cbdaa075b91aec80122db0c6820db8f5c79595af3e45a617fcabbe5284d67ef149209bd998870dd9684bf3d3bce0c88ddfec836e7e82e6b
7
+ data.tar.gz: ab8a10bd1aed88b3971d596a22bae191a6e9d77c20ba1f7343bc8888d9930feb57ab8d57cc2cb3aded11b938d5c83e8689ed2c54f85bd28cdd76291921c7fc7e
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2021 Code and Effect Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # Effective CPD
2
+
3
+ Continuing professional development.
4
+
5
+ An admin creates a set of categories, activites and rules. User enters number of hours or work done to have a scored statement. Audits.
6
+
7
+ Works with action_text for content bodies, and active_storage for file uploads.
8
+
9
+ ## Getting Started
10
+
11
+ This requires Rails 6+ and Twitter Bootstrap 4 and just works with Devise.
12
+
13
+ Please first install the [effective_datatables](https://github.com/code-and-effect/effective_datatables) gem.
14
+
15
+ Please download and install the [Twitter Bootstrap4](http://getbootstrap.com)
16
+
17
+ Add to your Gemfile:
18
+
19
+ ```ruby
20
+ gem 'haml-rails' # or try using gem 'hamlit-rails'
21
+ gem 'effective_cpd'
22
+ ```
23
+
24
+ Run the bundle command to install it:
25
+
26
+ ```console
27
+ bundle install
28
+ ```
29
+
30
+ Then run the generator:
31
+
32
+ ```ruby
33
+ rails generate effective_cpd:install
34
+ ```
35
+
36
+ The generator will install an initializer which describes all configuration options and creates a database migration.
37
+
38
+ If you want to tweak the table names, manually adjust both the configuration file and the migration now.
39
+
40
+ Then migrate the database:
41
+
42
+ ```ruby
43
+ rake db:migrate
44
+ ```
45
+
46
+ Render the "available statements for current_user" datatable on your user dashboard:
47
+
48
+ ```haml
49
+ %h2 Continuing Professional Development
50
+
51
+ %p Please submit a CPD statement for the following available #{cpd_cycles_label}:
52
+ = render_datatable(EffectiveCpdDatatable.new, simple: true)
53
+
54
+ - datatable = EffectiveCpdStatementsDatatable.new(self)
55
+ - if datatable.present?
56
+ .mt-4
57
+ %p You completed these statements:
58
+ = render_datatable(datatable, simple: true)
59
+
60
+ ```
61
+
62
+ Add a link to the admin menu:
63
+
64
+ ```haml
65
+ - if can? :admin, :effective_cpd
66
+ = link_to 'CPD Categories', effective_cpd.admin_cpd_categories_path
67
+ = link_to 'CPD Cycles', effective_cpd.admin_cpd_cycles_path
68
+ = link_to 'CPD Statements', effective_cpd.admin_cpd_statements_path
69
+ ```
70
+
71
+ ## Authorization
72
+
73
+ All authorization checks are handled via the effective_resources gem found in the `config/initializers/effective_resources.rb` file.
74
+
75
+ ## Permissions
76
+
77
+ The permissions you actually want to define are as follows (using CanCan):
78
+
79
+ ```ruby
80
+ # Regular signed up user. Guest users not supported.
81
+ if user.persisted?
82
+ can :new, Effective::CpdStatement
83
+ can [:index, :show, :update], Effective::CpdStatement, user_id: user.id
84
+ can [:index, :show], Effective::CpdCycle
85
+ can([:create, :update, :destroy], Effective::CpdStatementActivity) { |sa| sa.cpd_statement.user_id == user.id }
86
+ end
87
+
88
+ if user.admin?
89
+ can :admin, :effective_cpd
90
+ can :manage, Effective::CpdActivity
91
+ can :manage, Effective::CpdCategory
92
+ can :manage, Effective::CpdCycle
93
+ can :manage, Effective::CpdRule
94
+ can :manage, Effective::CpdStatement
95
+ end
96
+ ```
97
+
98
+ ## License
99
+
100
+ MIT License. Copyright [Code and Effect Inc.](http://www.codeandeffect.com/)
101
+
102
+ ## Testing
103
+
104
+ Run tests by:
105
+
106
+ ```ruby
107
+ rails test
108
+ ```
109
+
110
+ ## Contributing
111
+
112
+ 1. Fork it
113
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
114
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
115
+ 4. Push to the branch (`git push origin my-new-feature`)
116
+ 5. Bonus points for test coverage
117
+ 6. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
9
+
10
+ require "rake/testtask"
11
+
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.libs << 'test'
14
+ t.pattern = 'test/**/*_test.rb'
15
+ t.verbose = false
16
+ end
17
+
18
+ task default: :test
@@ -0,0 +1,2 @@
1
+ //= link_directory ../javascripts .js
2
+ //= link_directory ../stylesheets .css
@@ -0,0 +1,2 @@
1
+ //= require effective_cpd/activities
2
+ //= require effective_cpd/activities_new
@@ -0,0 +1,49 @@
1
+ const initialize_effective_cpd_activities = function() {
2
+ const $obj = $('.cpd-statement-activities:not(.initialized)')
3
+
4
+ if($obj.length > 0) {
5
+ $(document).on('mousedown touchstart', function(event) { collapse_effective_cpd_activities() })
6
+
7
+ // When we click outside the New Activity or Edit Activity
8
+ $(document).on('mousedown touchstart', '.activities-new', function(event) { event.stopPropagation() });
9
+ $(document).on('mousedown touchstart', '.statement-activity', function(event) { event.stopPropagation() });
10
+
11
+ $obj.addClass('initialized')
12
+ }
13
+ };
14
+
15
+ // Collapse the New Activity and all Edit Activity forms
16
+ const collapse_effective_cpd_activities = function() {
17
+ const $activities = $('#cpd-statement-activities')
18
+ $activities.children('.activities-new').hide()
19
+ $activities.children('.activities-index').show()
20
+
21
+ const $statementActivities = $('.cpd-statement-activities')
22
+ $statementActivities.find('.statement-activity-content').show()
23
+ $statementActivities.find('.statement-activity-form').hide()
24
+ };
25
+
26
+ // When we click the New Activity button, use the whole screen
27
+ $(document).on('click', '[data-cpd-new-activity]', function(event) {
28
+ event.preventDefault()
29
+ event.stopPropagation()
30
+
31
+ const $statement = $('#cpd-statement-activities')
32
+ $statement.children('.activities-index').hide()
33
+ $statement.children('.activities-new').show()
34
+ });
35
+
36
+ // When we click the Edit Activity
37
+ $(document).on('click', '[data-cpd-edit-activity]', function(event) {
38
+ event.preventDefault()
39
+
40
+ collapse_effective_cpd_activities()
41
+
42
+ const $activity = $(event.currentTarget).closest('.statement-activity')
43
+ $activity.children('.statement-activity-content').hide()
44
+ $activity.children('.statement-activity-form').show()
45
+ });
46
+
47
+ // Initializers
48
+ $(document).ready(function() { initialize_effective_cpd_activities() });
49
+ $(document).on('turbolinks:load', function() { initialize_effective_cpd_activities() });
@@ -0,0 +1,21 @@
1
+ // In the New Activity Form
2
+ $(document).on('click', '[data-cpd-show-activity]', function(event) {
3
+ event.preventDefault()
4
+
5
+ let $obj = $(event.currentTarget)
6
+ let $form = $obj.siblings('.statement-activity-form')
7
+ let $tabContent = $obj.closest('.tab-content').children('.statement-activity-content')
8
+
9
+ $tabContent.html($form.html())
10
+ $tabContent.siblings('.active').removeClass('active')
11
+ $tabContent.addClass('active')
12
+ });
13
+
14
+ $(document).on('click', '[data-cpd-back-activity]', function(event) {
15
+ event.preventDefault()
16
+
17
+ let $activities = $(event.currentTarget).closest('.activities-new')
18
+ let anchor = $activities.find('.nav[role=tablist]').find('a.nav-link.active').first().attr('href')
19
+
20
+ $(anchor).siblings('.active').removeClass('active').end().addClass('active')
21
+ });
@@ -0,0 +1 @@
1
+ @import 'effective_cpd/activities';
@@ -0,0 +1,19 @@
1
+ .cpd-statement-activities {
2
+ .statement-activity-category {
3
+ .progress { height: 3px; }
4
+ }
5
+
6
+ .statement-activity-content {
7
+ a { color: inherit; }
8
+ }
9
+
10
+ .carry-forward {
11
+ text-align: right;
12
+ padding-right: 2rem;
13
+ }
14
+
15
+ .score {
16
+ text-align: right;
17
+ }
18
+
19
+ }
@@ -0,0 +1,13 @@
1
+ module Admin
2
+ class CpdActivitiesController < 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_activity).permit!
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Admin
2
+ class CpdCategoriesController < 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_category).permit!
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Admin
2
+ class CpdCyclesController < 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_cycle).permit!
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Admin
2
+ class CpdRulesController < 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_rule).permit!
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Admin
2
+ class CpdStatementsController < 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_statement).permit!
10
+ # end
11
+
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ module Effective
2
+ class CpdCyclesController < ApplicationController
3
+ before_action(:authenticate_user!) if defined?(Devise)
4
+
5
+ def show
6
+ cycle = Effective::CpdCycle.find(params[:id])
7
+ EffectiveResources.authorize!(self, :show, cycle)
8
+
9
+ statement = Effective::CpdStatement.where(cpd_cycle: cycle, user: current_user).first
10
+
11
+ if statement.present?
12
+ redirect_to effective_cpd.cpd_cycle_cpd_statement_build_path(cycle, statement, statement.next_step)
13
+ else
14
+ redirect_to effective_cpd.cpd_cycle_cpd_statement_build_path(cycle, :new, :start)
15
+ end
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ module Effective
2
+ class CpdStatementActivitiesController < ApplicationController
3
+ before_action(:authenticate_user!) if defined?(Devise)
4
+
5
+ include Effective::CrudController
6
+
7
+ resource_scope -> { CpdStatement.find(params[:cpd_statement_id]).cpd_statement_activities }
8
+
9
+ # Score all statements when we change any activity
10
+ after_save do
11
+ CpdScorer.new(user: resource.cpd_statement.user, from: resource.cpd_statement).score!
12
+ end
13
+
14
+ # Redirect the remote form back to the activities page
15
+ on :save, redirect: -> {
16
+ statement = resource.cpd_statement
17
+ effective_cpd.cpd_cycle_cpd_statement_build_path(statement.cpd_cycle, statement, :activities)
18
+ }
19
+
20
+ def permitted_params
21
+ params.require(:effective_cpd_statement_activity).permit(
22
+ :id, :cpd_category_id, :cpd_activity_id, :amount, :amount2, :description, files: []
23
+ )
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,71 @@
1
+ module Effective
2
+ class CpdStatementsController < ApplicationController
3
+ before_action(:authenticate_user!) if defined?(Devise)
4
+
5
+ include Effective::WizardController
6
+
7
+ resource_scope do
8
+ cycle = Effective::CpdCycle.find(params[:cpd_cycle_id])
9
+ Effective::CpdStatement.deep.where(cpd_cycle: cycle, user: current_user)
10
+ end
11
+
12
+ after_save(if: -> { step == :start }) do
13
+ CpdScorer.new(user: resource.user).score!
14
+ end
15
+
16
+ # Enforce one statement per user per cycle. Redirect them to an existing statement for this cycle.
17
+ before_action(only: [:new, :show]) do
18
+ cycle = Effective::CpdCycle.find(params[:cpd_cycle_id])
19
+ existing = Effective::CpdStatement.where(cpd_cycle: cycle, user: current_user).where.not(id: resource).first
20
+
21
+ if existing&.completed?
22
+ flash[:danger] = "You have already completed a statement for this #{cpd_cycle_label}."
23
+ redirect_to(root_path)
24
+ elsif existing.present?
25
+ flash[:success] = "You have been redirected to the #{resource_wizard_step_title(existing.next_step)} step."
26
+ redirect_to effective_cpd.cpd_cycle_cpd_statement_build_path(existing.cpd_cycle, existing, existing.next_step)
27
+ end
28
+ end
29
+
30
+ # Enforce cycle availability
31
+ before_action(only: [:show, :update]) do
32
+ cycle = resource.cpd_cycle
33
+
34
+ unless cycle.available?
35
+ flash[:danger] = begin
36
+ if cycle.ended?
37
+ "This #{cpd_cycle_label} has ended"
38
+ elsif !cycle.started?
39
+ "This #{cpd_cycle_label} has not yet started"
40
+ else
41
+ "This #{cpd_cycle_label} is unavailable"
42
+ end
43
+ end
44
+
45
+ redirect_to(root_path)
46
+ end
47
+ end
48
+
49
+ def permitted_params
50
+ case step
51
+ when :start
52
+ params.require(:effective_cpd_statement).permit(:current_step)
53
+ when :activities
54
+ params.require(:effective_cpd_statement).permit(:current_step)
55
+ when :agreements
56
+ params.require(:effective_cpd_statement).permit(
57
+ :current_step, :confirm_read, :confirm_factual, files: []
58
+ )
59
+ when :submit
60
+ params.require(:effective_cpd_statement).permit(
61
+ :current_step, :confirm_readonly
62
+ )
63
+ when :complete
64
+ raise('unexpected post to complete')
65
+ else
66
+ raise('unexpected step')
67
+ end
68
+ end
69
+
70
+ end
71
+ end