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