effective_cpd 0.6.9 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,241 @@
1
+ # EffectiveCpdUser
2
+ #
3
+ # Mark your user model with effective_cpd_user to get a few helpers
4
+ # And user specific point required scores
5
+
6
+ module EffectiveCpdAuditReview
7
+ extend ActiveSupport::Concern
8
+
9
+ module Base
10
+ def effective_cpd_audit_review
11
+ include ::EffectiveCpdAuditReview
12
+ end
13
+ end
14
+
15
+ module ClassMethods
16
+ def effective_cpd_audit_review?; true; end
17
+ end
18
+
19
+ included do
20
+ acts_as_email_form
21
+ acts_as_tokened
22
+ acts_as_reportable if respond_to?(:acts_as_reportable)
23
+ log_changes(to: :cpd_audit, except: :wizard_steps) if respond_to?(:log_changes)
24
+
25
+ acts_as_wizard(
26
+ start: 'Start',
27
+ information: 'Information',
28
+ instructions: 'Instructions',
29
+
30
+ # Optional based on cpd_audit_level options
31
+ conflict: 'Conflict of Interest',
32
+
33
+ waiting: 'Waiting on Auditee Submission',
34
+
35
+ statements: 'Review CPD Statements',
36
+ # ... There will be one step per cpd_statement here. "statement1"
37
+
38
+ questionnaire: 'Review Questionnaire Responses',
39
+ # ... There will be one step per cpd_audit_level_sections here
40
+
41
+ recommendation: 'Recommendation',
42
+ submit: 'Confirm & Submit',
43
+ complete: 'Complete'
44
+ )
45
+
46
+ attr_accessor :current_user
47
+ attr_accessor :current_step
48
+
49
+ # App scoped
50
+ belongs_to :cpd_audit, polymorphic: true
51
+ belongs_to :cpd_audit_level, polymorphic: true
52
+
53
+ belongs_to :user, polymorphic: true # Auditor
54
+
55
+ # Effective scoped
56
+ has_many :cpd_audit_review_items, -> { Effective::CpdAuditReviewItem.sorted }, class_name: 'Effective::CpdAuditReviewItem', inverse_of: :cpd_audit_review, dependent: :destroy
57
+ accepts_nested_attributes_for :cpd_audit_review_items, reject_if: :all_blank, allow_destroy: true
58
+
59
+ effective_resource do
60
+ token :string
61
+ due_date :date
62
+
63
+ # Auditor response
64
+ conflict_of_interest :boolean
65
+ conflict_of_interest_reason :text
66
+
67
+ # Rolling comments
68
+ comments :text
69
+
70
+ # Recommendation Step
71
+ recommendation :string
72
+
73
+ # Status Dates
74
+ submitted_at :datetime
75
+
76
+ # acts_as_statused
77
+ # I'm not using acts_as_statused yet, but I probably will later
78
+ status :string
79
+ status_steps :text
80
+
81
+ # Wizard Progress
82
+ wizard_steps :text
83
+
84
+ timestamps
85
+ end
86
+
87
+ scope :deep, -> { includes(:cpd_audit, :cpd_audit_level, :user) }
88
+ scope :sorted, -> { order(:id) }
89
+
90
+ scope :available, -> { where(submitted_at: nil) }
91
+ scope :completed, -> { where.not(submitted_at: nil) }
92
+
93
+ # effective_reports
94
+ def reportable_scopes
95
+ { available: nil, completed: nil }
96
+ end
97
+
98
+ before_validation(if: -> { new_record? }) do
99
+ self.cpd_audit_level ||= cpd_audit&.cpd_audit_level
100
+ self.due_date ||= deadline_to_review()
101
+ end
102
+
103
+ validate(if: -> { recommendation.present? }) do
104
+ unless cpd_audit_level.recommendations.include?(recommendation)
105
+ self.errors.add(:recommendation, 'must exist in this audit level')
106
+ end
107
+ end
108
+
109
+ after_commit(on: :create) { send_email(:cpd_audit_review_opened) }
110
+ after_commit(on: :destroy) { cpd_audit.review! }
111
+ end
112
+
113
+ def to_s
114
+ persisted? ? "#{cpd_audit_level} Audit Review by #{user}" : 'audit review'
115
+ end
116
+
117
+ # Find or build
118
+ def cpd_audit_review_item(item)
119
+ unless item.kind_of?(CpdAuditResponse) || item.kind_of?(CpdStatementActivity)
120
+ raise("expected a cpd_audit_response or cpd_statement_activity")
121
+ end
122
+
123
+ cpd_audit_review_item = cpd_audit_review_items.find { |cari| cari.item == item }
124
+ cpd_audit_review_item ||= cpd_audit_review_items.build(item: item)
125
+ end
126
+
127
+ # The dynamic CPD Statement review steps
128
+ def auditee_cpd_statements
129
+ cpd_audit.user.cpd_statements.select do |cpd_statement|
130
+ cpd_statement.completed? && (submitted_at.blank? || cpd_statement.submitted_at < submitted_at)
131
+ end
132
+ end
133
+
134
+ def cpd_statement(wizard_step)
135
+ cpd_cycle_id = (wizard_step.to_s.split('statement').last.to_i rescue false)
136
+ auditee_cpd_statements.find { |cpd_statement| cpd_statement.cpd_cycle_id == cpd_cycle_id }
137
+ end
138
+
139
+ def dynamic_wizard_statement_steps
140
+ @statement_steps ||= auditee_cpd_statements.each_with_object({}) do |cpd_statement, h|
141
+ h["statement#{cpd_statement.cpd_cycle_id}".to_sym] = "#{cpd_statement.cpd_cycle.to_s} Activities"
142
+ end
143
+ end
144
+
145
+ # The dynamic CPD Audit Level Sections steps
146
+ def cpd_audit_level_section(wizard_step)
147
+ position = (wizard_step.to_s.split('section').last.to_i rescue false)
148
+ cpd_audit_level.cpd_audit_level_sections.find { |section| (section.position + 1) == position }
149
+ end
150
+
151
+ def dynamic_wizard_questionnaire_steps
152
+ @questionnaire_steps ||= cpd_audit_level.cpd_audit_level_sections.each_with_object({}) do |section, h|
153
+ h["section#{section.position+1}".to_sym] = section.title
154
+ end
155
+ end
156
+
157
+ def dynamic_wizard_steps
158
+ dynamic_wizard_statement_steps.merge(dynamic_wizard_questionnaire_steps)
159
+ end
160
+
161
+ def can_visit_step?(step)
162
+ return (step == :complete) if completed? # Can only view complete step once submitted
163
+ can_revisit_completed_steps(step)
164
+ end
165
+
166
+ def required_steps
167
+ steps = [:start, :information, :instructions]
168
+
169
+ steps << :conflict if cpd_audit_level.conflict_of_interest?
170
+
171
+ if conflict_of_interest?
172
+ return steps + [:submit, :complete]
173
+ end
174
+
175
+ steps += [:waiting] unless ready?
176
+
177
+ steps += [:statements] + dynamic_wizard_statement_steps.keys
178
+ steps += [:questionnaire] + dynamic_wizard_questionnaire_steps.keys
179
+ steps += [:recommendation, :submit, :complete]
180
+
181
+ steps
182
+ end
183
+
184
+ def wizard_step_title(step)
185
+ WIZARD_STEPS[step] || dynamic_wizard_steps.fetch(step)
186
+ end
187
+
188
+ # Called by CpdAudit.extension_granted
189
+ def extension_granted!
190
+ self.due_date = deadline_to_review()
191
+ end
192
+
193
+ # Called by CpdAudit.submit!
194
+ def ready!
195
+ send_email(:cpd_audit_review_ready)
196
+ end
197
+
198
+ # Called by review wizard submit step
199
+ def submit!
200
+ update!(submitted_at: Time.zone.now)
201
+ cpd_audit.review! # maybe go from submitted->removed
202
+
203
+ send_email(:cpd_audit_review_submitted)
204
+ end
205
+
206
+ # When ready, the applicant review wizard hides the waiting step
207
+ def ready?
208
+ cpd_audit&.was_submitted?
209
+ end
210
+
211
+ def in_progress?
212
+ submitted_at.blank?
213
+ end
214
+
215
+ def completed?
216
+ submitted_at.present?
217
+ end
218
+
219
+ def email_form_defaults(action)
220
+ { from: EffectiveCpd.mailer_sender }
221
+ end
222
+
223
+ def send_email(email)
224
+ EffectiveCpd.send_email(email, self, email_form_params) unless email_form_skip?
225
+ true
226
+ end
227
+
228
+ def deadline_to_conflict_of_interest
229
+ cpd_audit.deadline_to_conflict_of_interest
230
+ end
231
+
232
+ def deadline_to_review
233
+ return nil unless cpd_audit_level&.days_to_review.present?
234
+
235
+ date = cpd_audit.deadline_to_submit
236
+ return nil unless date.present?
237
+
238
+ EffectiveResources.advance_date(date, business_days: cpd_audit_level.days_to_review)
239
+ end
240
+
241
+ end
@@ -12,6 +12,10 @@ module EffectiveCpdUser
12
12
  end
13
13
  end
14
14
 
15
+ module ClassMethods
16
+ def effective_cpd_user?; true; end
17
+ end
18
+
15
19
  included do
16
20
  # App Scoped
17
21
  has_many :cpd_statements, -> { order(:cpd_cycle_id) }, inverse_of: :user
@@ -39,18 +43,14 @@ module EffectiveCpdUser
39
43
  end
40
44
 
41
45
  def cpd_statement(cpd_cycle:)
42
- raise('expected an Effective::CpdCycle') unless cpd_cycle.class.respond_to?(:effective_cpd_cpd_cycle?)
46
+ raise('expected an CpdCycle') unless cpd_cycle.class.respond_to?(:effective_cpd_cycle?)
43
47
  cpd_statements.find { |cpd_statement| cpd_statement.cpd_cycle_id == cpd_cycle.id }
44
48
  end
45
49
 
46
50
  # Find or build
47
51
  def build_cpd_statement(cpd_cycle:)
48
- raise('expected an Effective::CpdCycle') unless cpd_cycle.class.respond_to?(:effective_cpd_cpd_cycle?)
52
+ raise('expected an CpdCycle') unless cpd_cycle.class.respond_to?(:effective_cpd_cycle?)
49
53
  cpd_statement(cpd_cycle: cpd_cycle) || cpd_statements.build(cpd_cycle: cpd_cycle)
50
54
  end
51
55
 
52
- module ClassMethods
53
- def effective_cpd_user?; true; end
54
- end
55
-
56
56
  end
@@ -1,401 +1,5 @@
1
1
  module Effective
2
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
- acts_as_reportable if respond_to?(:acts_as_reportable)
27
-
28
- COMPLETED_STATES = [:exemption_granted, :closed]
29
-
30
- WAITING_ON_ADMIN_STATES = [:conflicted, :exemption_requested, :extension_requested, :reviewed]
31
- WAITING_ON_REVIEWERS_STATES = [:submitted]
32
- WAITING_ON_AUDITEE_STATES = [:opened, :started, :conflicted_resolved, :exemption_denied, :extension_granted, :extension_denied]
33
-
34
- acts_as_statused(
35
- :opened, # Just Opened
36
- :started, # First screen clicked
37
- :conflicted, # Auditee has declared a conflict of interest
38
- :conflicted_resolved, # The conflict of interest has been resolved
39
- :exemption_requested, # Auditee has requested an exemption
40
- :exemption_granted, # Exemption granted -> Audit is cancelled. Exit state.
41
- :exemption_denied, # Exemption denied
42
- :extension_requested, # Audittee has requested an extension
43
- :extension_granted, # Extension granted
44
- :extension_denied, # Extension denied
45
- :submitted, # Audittee has completed questionnaire submitted. Audittee is done.
46
- :reviewed, # All audit reviews completed. Ready for a determination.
47
- :closed # Determination made by admin and/or audit committee. Exit state. All done.
48
- )
49
-
50
- acts_as_wizard(
51
- start: 'Start',
52
- information: 'Information',
53
- instructions: 'Instructions',
54
-
55
- # These 4 steps are determined by audit_level settings
56
- conflict: 'Conflict of Interest',
57
- exemption: 'Request Exemption',
58
- extension: 'Request Extension',
59
- waiting: 'Waiting',
60
- cpd: 'CPD',
61
-
62
- questionnaire: 'Questionnaire',
63
- # ... There will be one step per cpd_audit_level_sections here
64
- files: 'Upload Resume',
65
-
66
- submit: 'Confirm & Submit',
67
- complete: 'Complete'
68
- )
69
-
70
- effective_resource do
71
- due_date :date # Computed due date based on notification and extension date
72
-
73
- # Important dates
74
- notification_date :date # Can be set on CpdAudits#new, but basically created_at
75
- extension_date :date # set by admin if extension if granted
76
-
77
- # Final determination
78
- determination :string
79
-
80
- # Override Deadlines
81
- ignore_deadlines :boolean
82
-
83
- # Auditee response
84
- conflict_of_interest :boolean
85
- conflict_of_interest_reason :text
86
-
87
- exemption_request :boolean
88
- exemption_request_reason :text
89
-
90
- extension_request :boolean
91
- extension_request_date :date
92
- extension_request_reason :text
93
-
94
- # acts_as_statused
95
- status :string
96
- status_steps :text
97
-
98
- # Status dates
99
- started_at :datetime
100
- submitted_at :datetime
101
- reviewed_at :datetime
102
- closed_at :datetime
103
-
104
- # Acts as tokened
105
- token :string
106
-
107
- # Acts as Wizard
108
- wizard_steps :text
109
-
110
- timestamps
111
- end
112
-
113
- scope :deep, -> { includes(:cpd_audit_level, user: [:cpd_statements], cpd_audit_reviews: [:cpd_audit_level, :user, :cpd_audit_review_items]) }
114
- scope :sorted, -> { order(:id) }
115
-
116
- scope :draft, -> { where(submitted_at: nil) }
117
- scope :available, -> { where.not(status: COMPLETED_STATES) }
118
- scope :completed, -> { where(status: COMPLETED_STATES) }
119
-
120
- scope :waiting_on_admin, -> { where(status: WAITING_ON_ADMIN_STATES) }
121
- scope :waiting_on_auditee, -> { where(status: WAITING_ON_AUDITEE_STATES) }
122
- scope :waiting_on_reviewers, -> { where(status: WAITING_ON_REVIEWERS_STATES) }
123
-
124
- # effective_reports
125
- def reportable_scopes
126
- { draft: nil, available: nil, completed: nil, waiting_on_admin: nil, waiting_on_auditee: nil, waiting_on_reviewers: nil }
127
- end
128
-
129
- before_validation(if: -> { new_record? }) do
130
- self.notification_date ||= Time.zone.now
131
- self.due_date ||= deadline_to_submit()
132
- end
133
-
134
- validates :notification_date, presence: true
135
- validates :determination, presence: true, if: -> { closed? }
136
-
137
- validates :conflict_of_interest_reason, presence: true, if: -> { conflict_of_interest? }
138
- validates :exemption_request_reason, presence: true, if: -> { exemption_request? }
139
- validates :extension_request_date, presence: true, if: -> { extension_request? }
140
- validates :extension_request_reason, presence: true, if: -> { extension_request? }
141
-
142
- validate(if: -> { current_step == :conflict && conflict_of_interest? && !ignore_deadlines? }) do
143
- deadline = deadline_to_conflict_of_interest()
144
- self.errors.add(:base, 'deadline to declare conflict of interest has already passed') if deadline && deadline < Time.zone.now
145
- end
146
-
147
- validate(if: -> { current_step == :exemption && exemption_request? && !ignore_deadlines? }) do
148
- deadline = deadline_to_exemption()
149
- self.errors.add(:base, 'deadline to request exemption has already passed') if deadline && deadline < Time.zone.now
150
- end
151
-
152
- validate(if: -> { current_step == :extension && extension_request? && !ignore_deadlines? }) do
153
- deadline = deadline_to_extension()
154
- self.errors.add(:base, 'deadline to request extension has already passed') if deadline && deadline < Time.zone.now
155
- end
156
-
157
- validate(if: -> { determination.present? }) do
158
- unless cpd_audit_level.determinations.include?(determination)
159
- self.errors.add(:determination, 'must exist in this audit level')
160
- end
161
- end
162
-
163
- # If we're submitted. Check if we can go into reviewed?
164
- before_save(if: -> { submitted? }) { review! }
165
-
166
- after_commit(on: :create) do
167
- send_email(:cpd_audit_opened)
168
- end
169
-
170
- def to_s
171
- persisted? ? "#{cpd_audit_level} Audit of #{user}" : 'audit'
172
- end
173
-
174
- def dynamic_wizard_steps
175
- cpd_audit_level.cpd_audit_level_sections.each_with_object({}) do |section, h|
176
- h["section#{section.position+1}".to_sym] = section.title
177
- end
178
- end
179
-
180
- def can_visit_step?(step)
181
- return (step == :complete) if was_submitted? # Can only view complete step once submitted
182
- can_revisit_completed_steps(step)
183
- end
184
-
185
- def required_steps
186
- steps = [:start, :information, :instructions]
187
-
188
- steps << :conflict if cpd_audit_level.conflict_of_interest?
189
-
190
- steps << :exemption if cpd_audit_level.can_request_exemption?
191
-
192
- unless exemption_requested?
193
- steps << :extension if cpd_audit_level.can_request_extension?
194
- end
195
-
196
- if exemption_requested? || extension_requested?
197
- steps += [:waiting]
198
- end
199
-
200
- steps += [:cpd, :questionnaire] + dynamic_wizard_steps.keys + [:files, :submit, :complete]
201
-
202
- steps
203
- end
204
-
205
- def wizard_step_title(step)
206
- WIZARD_STEPS[step] || dynamic_wizard_steps.fetch(step)
207
- end
208
-
209
- def deadline_date
210
- (extension_date || notification_date)
211
- end
212
-
213
- def completed?
214
- COMPLETED_STATES.include?(status.to_sym)
215
- end
216
-
217
- def in_progress?
218
- COMPLETED_STATES.include?(status.to_sym) == false
219
- end
220
-
221
- def cpd_audit_level_section(wizard_step)
222
- position = (wizard_step.to_s.split('section').last.to_i rescue false)
223
- cpd_audit_level.cpd_audit_level_sections.find { |section| (section.position + 1) == position }
224
- end
225
-
226
- # Find or build
227
- def cpd_audit_response(cpd_audit_level_question)
228
- cpd_audit_response = cpd_audit_responses.find { |r| r.cpd_audit_level_question_id == cpd_audit_level_question.id }
229
- cpd_audit_response ||= cpd_audit_responses.build(cpd_audit: self, cpd_audit_level_question: cpd_audit_level_question)
230
- end
231
-
232
- # Auditee wizard action
233
- def start!
234
- started!
235
- end
236
-
237
- # Admin action
238
- def resolve_conflict!
239
- wizard_steps[:conflict] = nil # Have them complete the conflict step again.
240
-
241
- assign_attributes(conflict_of_interest: false, conflict_of_interest_reason: nil)
242
- conflicted_resolved!
243
- submitted!
244
-
245
- send_email(:cpd_audit_conflict_resolved)
246
- true
247
- end
248
-
249
- # Auditee wizard action
250
- def exemption!
251
- return started! unless exemption_request?
252
-
253
- update!(status: :exemption_requested)
254
- send_email(:cpd_audit_exemption_request)
255
- end
256
-
257
- # Admin action
258
- def process_exemption!
259
- case admin_process_request
260
- when 'Granted' then grant_exemption!
261
- when 'Denied' then deny_exemption!
262
- else
263
- self.errors.add(:admin_process_request, "can't be blank"); save!
264
- end
265
- end
266
-
267
- def grant_exemption!
268
- wizard_steps[:submit] ||= Time.zone.now
269
- submitted! && exemption_granted!
270
- send_email(:cpd_audit_exemption_granted)
271
- end
272
-
273
- def deny_exemption!
274
- assign_attributes(exemption_request: false)
275
- exemption_denied!
276
- send_email(:cpd_audit_exemption_denied)
277
- end
278
-
279
- # Auditee wizard action
280
- def extension!
281
- return started! unless extension_request?
282
-
283
- update!(status: :extension_requested)
284
- send_email(:cpd_audit_extension_request)
285
- end
286
-
287
- # Admin action
288
- def process_extension!
289
- case admin_process_request
290
- when 'Granted' then grant_extension!
291
- when 'Denied' then deny_extension!
292
- else
293
- self.errors.add(:admin_process_request, "can't be blank"); save!
294
- end
295
- end
296
-
297
- def grant_extension!
298
- self.extension_date = extension_request_date
299
- self.due_date = deadline_to_submit()
300
-
301
- cpd_audit_reviews.each { |cpd_audit_review| cpd_audit_review.extension_granted! }
302
- extension_granted!
303
- send_email(:cpd_audit_extension_granted)
304
- end
305
-
306
- def deny_extension!
307
- assign_attributes(extension_request: false)
308
- extension_denied!
309
- send_email(:cpd_audit_extension_denied)
310
- end
311
-
312
- # Require CPD step
313
- def user_cpd_required?
314
- return false unless user.cpd_audit_cpd_required?
315
- required_cpd_cycle.present?
316
- end
317
-
318
- def user_cpd_completed?
319
- return true if required_cpd_cycle.blank?
320
- user.cpd_statements.any? { |s| s.completed? && s.cpd_cycle_id == required_cpd_cycle.id }
321
- end
322
-
323
- def required_cpd_cycle
324
- @required_cpd_cycle ||= begin
325
- last_year = ((notification_date || created_at || Time.zone.now) - 1.year).all_year
326
- Effective::CpdCycle.available.where(start_at: last_year).first
327
- end
328
- end
329
-
330
- # Auditee wizard action
331
- def submit!
332
- if conflict_of_interest?
333
- conflicted!
334
- send_email(:cpd_audit_conflicted)
335
- else
336
- submitted!
337
-
338
- cpd_audit_reviews.each { |cpd_audit_review| cpd_audit_review.ready! }
339
- send_email(:cpd_audit_submitted)
340
- end
341
-
342
- true
343
- end
344
-
345
- # Called in a before_save. Intended for applicant_review to call in its submit! method
346
- def review!
347
- return false unless submitted?
348
- return false unless cpd_audit_reviews.present? && cpd_audit_reviews.all?(&:completed?)
349
-
350
- reviewed!
351
- send_email(:cpd_audit_reviewed)
352
- end
353
-
354
- # Admin action
355
- def close!
356
- closed!
357
- send_email(:cpd_audit_closed)
358
- end
359
-
360
- def email_form_defaults(action)
361
- { from: EffectiveCpd.mailer_sender }
362
- end
363
-
364
- def send_email(email)
365
- EffectiveCpd.send_email(email, self, email_form_params) unless email_form_skip?
366
- true
367
- end
368
-
369
- def deadline_to_conflict_of_interest
370
- return nil unless cpd_audit_level&.conflict_of_interest?
371
- return nil unless cpd_audit_level.days_to_declare_conflict.present?
372
-
373
- date = (notification_date || created_at || Time.zone.now)
374
- EffectiveResources.advance_date(date, business_days: cpd_audit_level.days_to_declare_conflict)
375
- end
376
-
377
- def deadline_to_exemption
378
- return nil unless cpd_audit_level&.can_request_exemption?
379
- return nil unless cpd_audit_level.days_to_request_exemption.present?
380
-
381
- date = (notification_date || created_at || Time.zone.now)
382
- EffectiveResources.advance_date(date, business_days: cpd_audit_level.days_to_request_exemption)
383
- end
384
-
385
- def deadline_to_extension
386
- return nil unless cpd_audit_level&.can_request_extension?
387
- return nil unless cpd_audit_level.days_to_request_extension.present?
388
-
389
- date = (notification_date || created_at || Time.zone.now)
390
- EffectiveResources.advance_date(date, business_days: cpd_audit_level.days_to_request_extension)
391
- end
392
-
393
- def deadline_to_submit
394
- return nil unless cpd_audit_level&.days_to_submit.present?
395
-
396
- date = (extension_date || notification_date || created_at || Time.zone.now)
397
- EffectiveResources.advance_date(date, business_days: cpd_audit_level.days_to_submit)
398
- end
399
-
3
+ effective_cpd_audit
400
4
  end
401
5
  end