effective_cpd 0.6.7 → 1.0.0

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.
@@ -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