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
@@ -3,10 +3,13 @@ module Effective
3
3
  belongs_to :cpd_category
4
4
 
5
5
  has_rich_text :body
6
- log_changes(to: :cpd_category) if respond_to?(:log_changes)
7
6
 
8
- # has_many :rules, -> { order(cycle_id: :desc) }, as: :ruleable, dependent: :delete_all
9
- # accepts_nested_attributes_for :rules, allow_destroy: true
7
+ if respond_to?(:log_changes)
8
+ log_changes(to: :cpd_category, except: [:cpd_statement_activities])
9
+ end
10
+
11
+ #has_many :rules, class_name: 'Effective::CpdRule', as: :ruleable
12
+ has_many :cpd_statement_activities
10
13
 
11
14
  effective_resource do
12
15
  title :string
@@ -33,9 +36,19 @@ module Effective
33
36
  validates :title, presence: true
34
37
  validates :position, presence: true
35
38
 
39
+ before_destroy do
40
+ if (count = cpd_statement_activities.length) > 0
41
+ raise("#{count} statement activities belong to this activity")
42
+ end
43
+ end
44
+
36
45
  def to_s
37
46
  title.presence || 'activity'
38
47
  end
39
48
 
49
+ def amount_static?
50
+ amount_label.blank? && amount2_label.blank?
51
+ end
52
+
40
53
  end
41
54
  end
@@ -0,0 +1,380 @@
1
+ module Effective
2
+ class CpdAudit < ActiveRecord::Base
3
+ attr_accessor :current_user
4
+ attr_accessor :current_step
5
+
6
+ attr_accessor :admin_process_request
7
+ ADMIN_PROCESS_REQUEST_OPTIONS = ['Granted', 'Denied']
8
+
9
+ belongs_to :cpd_audit_level
10
+ belongs_to :user, polymorphic: true # The user being audited
11
+
12
+ has_many :cpd_audit_reviews, -> { order(:id) }, inverse_of: :cpd_audit, dependent: :destroy
13
+ accepts_nested_attributes_for :cpd_audit_reviews, allow_destroy: true
14
+
15
+ has_many :cpd_audit_responses, -> { order(:id) }, inverse_of: :cpd_audit, dependent: :destroy
16
+ accepts_nested_attributes_for :cpd_audit_responses
17
+
18
+ has_many_attached :files
19
+
20
+ if respond_to?(:log_changes)
21
+ log_changes(except: [:wizard_steps, :cpd_audit_reviews])
22
+ end
23
+
24
+ acts_as_email_form
25
+ acts_as_tokened
26
+
27
+ COMPLETED_STATES = [:exemption_granted, :closed]
28
+
29
+ WAITING_ON_ADMIN_STATES = [:conflicted, :exemption_requested, :extension_requested, :reviewed]
30
+ WAITING_ON_REVIEWERS_STATES = [:submitted]
31
+ WAITING_ON_AUDITEE_STATES = [:opened, :started, :conflicted_resolved, :exemption_denied, :extension_granted, :extension_denied]
32
+
33
+ acts_as_statused(
34
+ :opened, # Just Opened
35
+ :started, # First screen clicked
36
+ :conflicted, # Auditee has declared a conflict of interest
37
+ :conflicted_resolved, # The conflict of interest has been resolved
38
+ :exemption_requested, # Auditee has requested an exemption
39
+ :exemption_granted, # Exemption granted -> Audit is cancelled. Exit state.
40
+ :exemption_denied, # Exemption denied
41
+ :extension_requested, # Audittee has requested an extension
42
+ :extension_granted, # Extension granted
43
+ :extension_denied, # Extension denied
44
+ :submitted, # Audittee has completed questionnaire submitted. Audittee is done.
45
+ :reviewed, # All audit reviews completed. Ready for a determination.
46
+ :closed # Determination made by admin and/or audit committee. Exit state. All done.
47
+ )
48
+
49
+ acts_as_wizard(
50
+ start: 'Start',
51
+ information: 'Information',
52
+ instructions: 'Instructions',
53
+
54
+ # These 4 steps are determined by audit_level settings
55
+ conflict: 'Conflict of Interest',
56
+ exemption: 'Request Exemption',
57
+ extension: 'Request Extension',
58
+ waiting: 'Waiting',
59
+
60
+ questionnaire: 'Questionnaire',
61
+ # ... There will be one step per cpd_audit_level_sections here
62
+ files: 'Upload Resume',
63
+
64
+ submit: 'Confirm & Submit',
65
+ complete: 'Complete'
66
+ )
67
+
68
+ effective_resource do
69
+ due_date :date # Computed due date based on notification and extension date
70
+
71
+ # Important dates
72
+ notification_date :date # Can be set on CpdAudits#new, but basically created_at
73
+ extension_date :date # set by admin if extension if granted
74
+
75
+ # Final determination
76
+ determination :string
77
+
78
+ # Auditee response
79
+ conflict_of_interest :boolean
80
+ conflict_of_interest_reason :text
81
+
82
+ exemption_request :boolean
83
+ exemption_request_reason :text
84
+
85
+ extension_request :boolean
86
+ extension_request_date :date
87
+ extension_request_reason :text
88
+
89
+ # acts_as_statused
90
+ status :string
91
+ status_steps :text
92
+
93
+ # Status dates
94
+ started_at :datetime
95
+ submitted_at :datetime
96
+ reviewed_at :datetime
97
+ audited_at :datetime # TODO: CHange to closed_at
98
+
99
+ # Acts as tokened
100
+ token :string
101
+
102
+ # Acts as Wizard
103
+ wizard_steps :text
104
+
105
+ timestamps
106
+ end
107
+
108
+ scope :deep, -> { includes(:cpd_audit_level, user: [:cpd_statements], cpd_audit_reviews: [:cpd_audit_level, :user, :cpd_audit_review_items]) }
109
+ scope :sorted, -> { order(:id) }
110
+
111
+ scope :draft, -> { where(submitted_at: nil) }
112
+ scope :available, -> { where.not(status: COMPLETED_STATES) }
113
+ scope :completed, -> { where(status: COMPLETED_STATES) }
114
+
115
+ scope :waiting_on_admin, -> { where(status: WAITING_ON_ADMIN_STATES) }
116
+ scope :waiting_on_auditee, -> { where(status: WAITING_ON_AUDITEE_STATES) }
117
+ scope :waiting_on_reviewers, -> { where(status: WAITING_ON_REVIEWERS_STATES) }
118
+
119
+ before_validation(if: -> { new_record? }) do
120
+ self.notification_date ||= Time.zone.now
121
+ self.due_date ||= deadline_to_submit()
122
+ end
123
+
124
+ validates :notification_date, presence: true
125
+ validates :determination, presence: true, if: -> { closed? }
126
+
127
+ validates :conflict_of_interest_reason, presence: true, if: -> { conflict_of_interest? }
128
+ validates :exemption_request_reason, presence: true, if: -> { exemption_request? }
129
+ validates :extension_request_date, presence: true, if: -> { extension_request? }
130
+ validates :extension_request_reason, presence: true, if: -> { extension_request? }
131
+
132
+ validate(if: -> { determination.present? }) do
133
+ unless cpd_audit_level.determinations.include?(determination)
134
+ self.errors.add(:determination, 'must exist in this audit level')
135
+ end
136
+ end
137
+
138
+ # If we're submitted. Check if we can go into reviewed?
139
+ before_save(if: -> { submitted? }) { review! }
140
+
141
+ after_commit(on: :create) do
142
+ send_email(:cpd_audit_opened)
143
+ end
144
+
145
+ def to_s
146
+ persisted? ? "#{cpd_audit_level} Audit of #{user}" : 'audit'
147
+ end
148
+
149
+ acts_as_wizard(
150
+ start: 'Start',
151
+ information: 'Information',
152
+ instructions: 'Instructions',
153
+
154
+ # These 4 steps are determined by audit_level settings
155
+ conflict: 'Conflict of Interest',
156
+ exemption: 'Request Exemption',
157
+ extension: 'Request Extension',
158
+ waiting: 'Waiting on Request',
159
+
160
+ questionaire: 'Questionaire',
161
+ # ... There will be one step per cpd_audit_level_sections here
162
+ files: 'Upload Resume',
163
+
164
+ submit: 'Confirm & Submit',
165
+ complete: 'Complete'
166
+ )
167
+
168
+ def dynamic_wizard_steps
169
+ cpd_audit_level.cpd_audit_level_sections.each_with_object({}) do |section, h|
170
+ h["section#{section.position+1}".to_sym] = section.title
171
+ end
172
+ end
173
+
174
+ def can_visit_step?(step)
175
+ return (step == :complete) if was_submitted? # Can only view complete step once submitted
176
+ can_revisit_completed_steps(step)
177
+ end
178
+
179
+ def required_steps
180
+ steps = [:start, :information, :instructions]
181
+
182
+ steps << :conflict if cpd_audit_level.conflict_of_interest?
183
+
184
+ if conflicted?
185
+ return steps + [:waiting, :submit, :complete]
186
+ end
187
+
188
+ steps << :exemption if cpd_audit_level.can_request_exemption?
189
+
190
+ unless exemption_requested?
191
+ steps << :extension if cpd_audit_level.can_request_extension?
192
+ end
193
+
194
+ if exemption_requested? || extension_requested?
195
+ steps += [:waiting]
196
+ end
197
+
198
+ steps += [:questionnaire] + dynamic_wizard_steps.keys + [:files, :submit, :complete]
199
+
200
+ steps
201
+ end
202
+
203
+ def wizard_step_title(step)
204
+ WIZARD_STEPS[step] || dynamic_wizard_steps.fetch(step)
205
+ end
206
+
207
+ def deadline_date
208
+ (extension_date || notification_date)
209
+ end
210
+
211
+ def completed?
212
+ COMPLETED_STATES.include?(status.to_sym)
213
+ end
214
+
215
+ def in_progress?
216
+ COMPLETED_STATES.include?(status.to_sym) == false
217
+ end
218
+
219
+ def cpd_audit_level_section(wizard_step)
220
+ position = (wizard_step.to_s.split('section').last.to_i rescue false)
221
+ cpd_audit_level.cpd_audit_level_sections.find { |section| (section.position + 1) == position }
222
+ end
223
+
224
+ # Find or build
225
+ def cpd_audit_response(cpd_audit_level_question)
226
+ cpd_audit_response = cpd_audit_responses.find { |r| r.cpd_audit_level_question_id == cpd_audit_level_question.id }
227
+ cpd_audit_response ||= cpd_audit_responses.build(cpd_audit: self, cpd_audit_level_question: cpd_audit_level_question)
228
+ end
229
+
230
+ # Auditee wizard action
231
+ def start!
232
+ started!
233
+ end
234
+
235
+ # Auditee wizard action
236
+ def conflict!
237
+ return started! unless conflict_of_interest?
238
+
239
+ update!(status: :conflicted)
240
+ send_email(:cpd_audit_conflicted)
241
+ end
242
+
243
+ # Admin action
244
+ def resolve_conflict!
245
+ wizard_steps[:conflict] = nil # Have them complete the conflict step again.
246
+
247
+ assign_attributes(conflict_of_interest: false, conflict_of_interest_reason: nil)
248
+ conflicted_resolved!
249
+
250
+ send_email(:cpd_audit_conflict_resolved)
251
+ true
252
+ end
253
+
254
+ # Auditee wizard action
255
+ def exemption!
256
+ return started! unless exemption_request?
257
+
258
+ update!(status: :exemption_requested)
259
+ send_email(:cpd_audit_exemption_request)
260
+ end
261
+
262
+ # Admin action
263
+ def process_exemption!
264
+ case admin_process_request
265
+ when 'Granted' then grant_exemption!
266
+ when 'Denied' then deny_exemption!
267
+ else
268
+ self.errors.add(:admin_process_request, "can't be blank"); save!
269
+ end
270
+ end
271
+
272
+ def grant_exemption!
273
+ wizard_steps[:submit] ||= Time.zone.now
274
+ submitted! && exemption_granted!
275
+ send_email(:cpd_audit_exemption_granted)
276
+ end
277
+
278
+ def deny_exemption!
279
+ assign_attributes(exemption_request: false)
280
+ exemption_denied!
281
+ send_email(:cpd_audit_exemption_denied)
282
+ end
283
+
284
+ # Auditee wizard action
285
+ def extension!
286
+ return started! unless extension_request?
287
+
288
+ update!(status: :extension_requested)
289
+ send_email(:cpd_audit_extension_request)
290
+ end
291
+
292
+ # Admin action
293
+ def process_extension!
294
+ case admin_process_request
295
+ when 'Granted' then grant_extension!
296
+ when 'Denied' then deny_extension!
297
+ else
298
+ self.errors.add(:admin_process_request, "can't be blank"); save!
299
+ end
300
+ end
301
+
302
+ def grant_extension!
303
+ self.extension_date = extension_request_date
304
+ self.due_date = deadline_to_submit()
305
+
306
+ cpd_audit_reviews.each { |cpd_audit_review| cpd_audit_review.extension_granted! }
307
+ extension_granted!
308
+ send_email(:cpd_audit_extension_granted)
309
+ end
310
+
311
+ def deny_extension!
312
+ assign_attributes(extension_request: false)
313
+ extension_denied!
314
+ send_email(:cpd_audit_extension_denied)
315
+ end
316
+
317
+ # Auditee wizard action
318
+ def submit!
319
+ submitted!
320
+ cpd_audit_reviews.each { |cpd_audit_review| cpd_audit_review.ready! }
321
+ send_email(:cpd_audit_submitted)
322
+ end
323
+
324
+ # Called in a before_save. Intended for applicant_review to call in its submit! method
325
+ def review!
326
+ return false unless submitted?
327
+ return false unless cpd_audit_reviews.present? && cpd_audit_reviews.all?(&:completed?)
328
+
329
+ reviewed!
330
+ send_email(:cpd_audit_reviewed)
331
+ end
332
+
333
+ # Admin action
334
+ def close!
335
+ closed!
336
+ send_email(:cpd_audit_closed)
337
+ end
338
+
339
+ def email_form_defaults(action)
340
+ { from: EffectiveCpd.mailer_sender }
341
+ end
342
+
343
+ def send_email(email)
344
+ EffectiveCpd.send_email(email, self, email_form_params) unless email_form_skip?
345
+ true
346
+ end
347
+
348
+ def deadline_to_conflict_of_interest
349
+ return nil unless cpd_audit_level&.conflict_of_interest?
350
+ return nil unless cpd_audit_level.days_to_declare_conflict.present?
351
+
352
+ date = (notification_date || created_at || Time.zone.now)
353
+ EffectiveResources.advance_date(date, business_days: cpd_audit_level.days_to_declare_conflict)
354
+ end
355
+
356
+ def deadline_to_exemption
357
+ return nil unless cpd_audit_level&.can_request_exemption?
358
+ return nil unless cpd_audit_level.days_to_request_exemption.present?
359
+
360
+ date = (notification_date || created_at || Time.zone.now)
361
+ EffectiveResources.advance_date(date, business_days: cpd_audit_level.days_to_request_exemption)
362
+ end
363
+
364
+ def deadline_to_extension
365
+ return nil unless cpd_audit_level&.can_request_extension?
366
+ return nil unless cpd_audit_level.days_to_request_extension.present?
367
+
368
+ date = (notification_date || created_at || Time.zone.now)
369
+ EffectiveResources.advance_date(date, business_days: cpd_audit_level.days_to_request_extension)
370
+ end
371
+
372
+ def deadline_to_submit
373
+ return nil unless cpd_audit_level&.days_to_submit.present?
374
+
375
+ date = (extension_date || notification_date || created_at || Time.zone.now)
376
+ EffectiveResources.advance_date(date, business_days: cpd_audit_level.days_to_submit)
377
+ end
378
+
379
+ end
380
+ end
@@ -0,0 +1,87 @@
1
+ module Effective
2
+ class CpdAuditLevel < ActiveRecord::Base
3
+ has_many_rich_texts
4
+
5
+ # For each cpd audit and cpd audit review wizard step
6
+ # rich_text_all_steps_audit_content
7
+ # rich_text_step_audit_content
8
+
9
+ # rich_text_all_steps_audit_review_content
10
+ # rich_text_step_audit_review_content
11
+
12
+ has_many :cpd_audit_level_sections, -> { CpdAuditLevelSection.sorted }, inverse_of: :cpd_audit_level, dependent: :destroy
13
+ accepts_nested_attributes_for :cpd_audit_level_sections, allow_destroy: true
14
+
15
+ has_many :cpd_audit_level_questions, -> { CpdAuditLevelQuestion.sorted }, through: :cpd_audit_level_sections
16
+
17
+ has_many :cpd_audit_reviews, -> { CpdAuditReview.sorted }, inverse_of: :cpd_audit_level, dependent: :destroy
18
+ accepts_nested_attributes_for :cpd_audit_reviews, allow_destroy: true
19
+
20
+ has_many :cpd_audits
21
+
22
+ if respond_to?(:log_changes)
23
+ log_changes(except: [:cpd_audits, :cpd_audit_reviews, :cpd_audit_level_sections, :cpd_audit_level_questions])
24
+ end
25
+
26
+ effective_resource do
27
+ title :string
28
+
29
+ determinations :text # Final determination by auditor
30
+ recommendations :text # Recommendations by audit reviewers
31
+
32
+ days_to_submit :integer # For auditee to submit statement
33
+ days_to_review :integer # For auditor/audit_review to be completed
34
+
35
+ conflict_of_interest :boolean # Feature flags
36
+ can_request_exemption :boolean
37
+ can_request_extension :boolean
38
+
39
+ days_to_declare_conflict :integer
40
+ days_to_request_exemption :integer
41
+ days_to_request_extension :integer
42
+
43
+ timestamps
44
+ end
45
+
46
+ serialize :determinations, Array
47
+ serialize :recommendations, Array
48
+
49
+ scope :deep, -> { all }
50
+ scope :sorted, -> { order(:title) }
51
+
52
+ validates :title, presence: true
53
+ validates :determinations, presence: true
54
+ validates :recommendations, presence: true
55
+
56
+ validates :days_to_submit, numericality: { greater_than: 0, allow_nil: true }
57
+ validates :days_to_review, numericality: { greater_than: 0, allow_nil: true }
58
+
59
+ validates :days_to_declare_conflict, presence: true, if: -> { conflict_of_interest? }
60
+ validates :days_to_declare_conflict, numericality: { greater_than: 0, allow_nil: true }
61
+
62
+ validates :days_to_request_exemption, presence: true, if: -> { can_request_exemption? }
63
+ validates :days_to_request_exemption, numericality: { greater_than: 0, allow_nil: true }
64
+
65
+ validates :days_to_request_extension, presence: true, if: -> { can_request_extension? }
66
+ validates :days_to_request_extension, numericality: { greater_than: 0, allow_nil: true }
67
+
68
+ before_destroy do
69
+ if (count = cpd_audits.length) > 0
70
+ raise("#{count} audits belong to this audit level")
71
+ end
72
+ end
73
+
74
+ def to_s
75
+ title.presence || 'audit level'
76
+ end
77
+
78
+ def determinations
79
+ Array(self[:determinations]) - [nil, '']
80
+ end
81
+
82
+ def recommendations
83
+ Array(self[:recommendations]) - [nil, '']
84
+ end
85
+
86
+ end
87
+ end