Skip to content

Commit c66b757

Browse files
jhendersonthomasleese
authored andcommitted
Implement logic needs consent status request scheduled/not scheduled
This adds the logic where if a child is eligible for the programme and hasn't been sent any consent requests and has parent contact details: - If part of a session where consent requests are scheduled to go out in the future, the programme status becomes `needs_consent_request_scheduled`. - If not assigned to a session with a request scheduled, the programme status becomes `needs_consent_request_not_scheduled`. Jira-Issue: MAV-5882
1 parent df416d7 commit c66b757

24 files changed

Lines changed: 469 additions & 75 deletions

app/components/app_patient_search_form_component.rb

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@
22

33
class AppPatientSearchFormComponent < ViewComponent::Base
44
# Remove these statuses once implemented.
5-
HIDDEN_PROGRAMME_STATUSES = %w[
6-
needs_consent_request_failed
7-
needs_consent_request_not_scheduled
8-
needs_consent_request_scheduled
9-
].freeze
5+
HIDDEN_PROGRAMME_STATUSES = %w[needs_consent_request_failed].freeze
106

117
def initialize(
128
form,

app/components/app_patient_session_consent_component.html.erb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
<p><%= patient.full_name %> is ready for the vaccinator.</p>
2222
<% elsif consent_status_value == :no_contact_details %>
2323
<p>We cannot send consent requests because we have no parent contact details.</p>
24+
<% elsif consent_status_value == :request_not_scheduled %>
25+
<p>No consent request is scheduled to be sent because the child has not been added to a scheduled session.</p>
26+
<% elsif consent_status_value == :request_scheduled %>
27+
<p>A consent request will be sent on <%= session.send_consent_requests_at.to_fs(:long) %>.</p>
2428
<% end %>
2529

2630
<div class="nhsuk-button-group">

app/components/app_patient_session_consent_component.rb

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ def consent_status_generator
6363
patient:,
6464
consents:,
6565
vaccination_records:,
66-
parents:
66+
parents:,
67+
sessions: [session],
68+
consent_notifications:
6769
)
6870
end
6971

@@ -76,19 +78,14 @@ def triage_status_generator
7678
consents:,
7779
triages:,
7880
vaccination_records:,
79-
parents:
81+
parents:,
82+
sessions: [session],
83+
consent_notifications:
8084
)
8185
end
8286

8387
def latest_consent_request
84-
@latest_consent_request ||=
85-
patient
86-
.consent_notifications
87-
.request
88-
.has_all_programmes_of([programme])
89-
.for_academic_year(academic_year)
90-
.order(sent_at: :desc)
91-
.first
88+
@latest_consent_request ||= consent_notifications.first
9289
end
9390

9491
def consents
@@ -116,8 +113,14 @@ def vaccination_records
116113
patient.vaccination_records.for_programme(programme).order_by_performed_at
117114
end
118115

116+
SEND_CONSENT_REQUEST_STATUSES = %i[
117+
no_response
118+
request_scheduled
119+
request_not_scheduled
120+
].freeze
121+
119122
def can_send_consent_request?
120-
consent_status_value == :no_response &&
123+
consent_status_value.in?(SEND_CONSENT_REQUEST_STATUSES) &&
121124
patient.send_notifications?(team: @session.team) &&
122125
session.can_receive_consent? && patient.parents.any?(&:contactable?)
123126
end
@@ -134,4 +137,13 @@ def who_refused
134137
def show_health_answers?
135138
grouped_consents.any?(&:response_given?)
136139
end
140+
141+
def consent_notifications
142+
patient
143+
.consent_notifications
144+
.request
145+
.has_all_programmes_of([programme])
146+
.for_academic_year(academic_year)
147+
.order(sent_at: :desc)
148+
end
137149
end

app/components/app_patient_session_triage_component.rb

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ def initialize(
1414
@current_user = current_user
1515
@triage_form = triage_form || default_triage_form
1616
@parents = patient.parents
17+
@patient_locations =
18+
patient.patient_locations.includes(
19+
location: [
20+
:location_programme_year_groups,
21+
{ team_locations: { sessions: :session_programme_year_groups } }
22+
]
23+
)
1724
end
1825

1926
def render?
@@ -28,7 +35,8 @@ def render?
2835
:programme,
2936
:current_user,
3037
:triage_form,
31-
:parents
38+
:parents,
39+
:patient_locations
3240

3341
delegate :academic_year, :team, to: :session
3442

@@ -77,7 +85,9 @@ def triage_status_generator
7785
consents:,
7886
triages:,
7987
vaccination_records:,
80-
parents:
88+
parents:,
89+
sessions: [session],
90+
consent_notifications:
8191
)
8292
end
8393

@@ -89,7 +99,9 @@ def consent_status_generator
8999
patient:,
90100
consents:,
91101
vaccination_records:,
92-
parents:
102+
parents:,
103+
sessions: [session],
104+
consent_notifications:
93105
)
94106
end
95107

@@ -118,4 +130,12 @@ def latest_triage
118130
def default_triage_form
119131
TriageForm.new(patient:, session:, programme:, current_user:)
120132
end
133+
134+
def consent_notifications
135+
patient
136+
.consent_notifications
137+
.request
138+
.has_all_programmes_of([programme])
139+
.for_academic_year(academic_year)
140+
end
121141
end

app/forms/triage_form.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,9 @@ def consent_status_generator
130130
patient:,
131131
consents: patient.consents,
132132
vaccination_records: [],
133-
parents: []
133+
parents: [],
134+
sessions: [],
135+
consent_notifications: []
134136
)
135137
end
136138

app/lib/patient_status_updater.rb

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,13 @@ def update_programme_statuses!
5151
:patient_locations,
5252
:triages,
5353
:vaccination_records,
54-
:parents
54+
:parents,
55+
:consent_notifications,
56+
patient_locations: {
57+
location: [
58+
{ team_locations: { sessions: :session_programme_year_groups } }
59+
]
60+
}
5561
).to_a
5662

5763
batch.each(&:assign)
@@ -182,4 +188,23 @@ def programme_types_per_session_id_and_year_group
182188
hash[session_id][year_group] << programme_type
183189
end
184190
end
191+
192+
# We preload this association separately because including it in the nested
193+
# `patient_locations` preload (see includes above) caused the updater process
194+
# to be killed, even with very small batches. The likely cause is memory pressure
195+
# from eager loading a deeply nested association graph.
196+
#
197+
# Preloading it here for the distinct `Location` records in each batch keeps
198+
# `StatusGenerator::Programme` query-free without incurring the cost of the
199+
# larger nested preload.
200+
def preload_location_programme_year_groups(batch)
201+
locations = batch.flat_map(&:patient_locations).map(&:location).uniq
202+
203+
ActiveRecord::Associations::Preloader.new(
204+
records: locations,
205+
associations: {
206+
location_programme_year_groups: :location_year_group
207+
}
208+
).call
209+
end
185210
end

app/lib/status_generator/consent.rb

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,18 @@ def initialize(
77
patient:,
88
consents:,
99
vaccination_records:,
10-
parents:
10+
parents:,
11+
sessions:,
12+
consent_notifications:
1113
)
1214
@programme_type = programme_type
1315
@academic_year = academic_year
1416
@patient = patient
1517
@consents = consents
1618
@vaccination_records = vaccination_records
1719
@parents = parents
20+
@sessions = sessions
21+
@consent_notifications = consent_notifications
1822
end
1923

2024
def programme
@@ -32,6 +36,10 @@ def status
3236
:conflicts
3337
elsif status_should_be_no_contact_details?
3438
:no_contact_details
39+
elsif status_should_be_request_scheduled?
40+
:request_scheduled
41+
elsif status_should_be_request_not_scheduled?
42+
:request_not_scheduled
3543
elsif status_should_be_no_response?
3644
:no_response
3745
else
@@ -62,7 +70,9 @@ def disease_types
6270
:patient,
6371
:consents,
6472
:vaccination_records,
65-
:parents
73+
:parents,
74+
:sessions,
75+
:consent_notifications
6676

6777
def vaccinated?
6878
return @vaccinated if defined?(@vaccinated)
@@ -131,6 +141,20 @@ def status_should_be_no_contact_details?
131141
parents.none?(&:contactable?)
132142
end
133143

144+
def status_should_be_request_scheduled?
145+
return false if vaccinated?
146+
147+
parents_contactable? && consent_notifications.empty? &&
148+
sessions.any? { consent_request_scheduled_in_future?(it) }
149+
end
150+
151+
def status_should_be_request_not_scheduled?
152+
return false if vaccinated?
153+
154+
parents_contactable? && consent_notifications.empty? &&
155+
(sessions.empty? || sessions.any? { consent_request_not_scheduled?(it) })
156+
end
157+
134158
def agreed_vaccine_methods
135159
@agreed_vaccine_methods ||=
136160
consents_for_status.map(&:vaccine_methods).inject(&:intersection)
@@ -162,4 +186,21 @@ def latest_consents
162186
@latest_consents ||=
163187
ConsentGrouper.call(consents, programme_type:, academic_year:)
164188
end
189+
190+
def parents_contactable? = parents.any?(&:contactable?)
191+
192+
def consent_request_scheduled_in_future?(session)
193+
send_at = session.send_consent_requests_at
194+
195+
# Not using future? because it doesn't work with Timecop
196+
send_at.present? && send_at > Time.current
197+
end
198+
199+
# Treat consent requests as not scheduled when send_consent_requests_at is
200+
# missing or has already passed, such as for sessions activated on the
201+
# same day they are created.
202+
def consent_request_not_scheduled?(session)
203+
send_at = session.send_consent_requests_at
204+
send_at.nil? || send_at < Time.current
205+
end
165206
end

app/lib/status_generator/programme.rb

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ def initialize(
1717
triages:,
1818
attendance_record:,
1919
vaccination_records:,
20-
parents:
20+
parents:,
21+
consent_notifications:
2122
)
2223
@programme_type = programme_type
2324
@academic_year = academic_year
@@ -28,6 +29,8 @@ def initialize(
2829
@attendance_record = attendance_record
2930
@vaccination_records = vaccination_records
3031
@parents = parents
32+
@consent_notifications =
33+
find_matching_consent_notifications(consent_notifications)
3134

3235
@vaccination_criteria =
3336
VaccinationCriteria.new(
@@ -172,7 +175,8 @@ def consent_vaccine_methods
172175
:triages,
173176
:attendance_record,
174177
:vaccination_criteria,
175-
:parents
178+
:parents,
179+
:consent_notifications
176180

177181
delegate :vaccinated?,
178182
:vaccinated_vaccination_record,
@@ -237,11 +241,11 @@ def should_be_needs_consent_request_failed?
237241
end
238242

239243
def should_be_needs_consent_request_scheduled?
240-
false # TODO: Implement this status.
244+
is_eligible? && consent_status == :request_scheduled
241245
end
242246

243247
def should_be_needs_consent_request_not_scheduled?
244-
false # TODO: Implement this status.
248+
is_eligible? && consent_status == :request_not_scheduled
245249
end
246250

247251
def should_be_needs_consent_no_contact_details?
@@ -288,6 +292,13 @@ def default_programme_year_groups
288292
Programme.find(programme_type).default_year_groups
289293
end
290294

295+
def find_matching_consent_notifications(notifications)
296+
notifications.select do |notification|
297+
notification.programme_types.include?(programme_type) &&
298+
notification.session&.team_location&.academic_year == academic_year
299+
end
300+
end
301+
291302
def consent_generator
292303
@consent_generator ||=
293304
StatusGenerator::Consent.new(
@@ -296,7 +307,9 @@ def consent_generator
296307
patient:,
297308
consents:,
298309
vaccination_records:,
299-
parents:
310+
parents:,
311+
sessions:,
312+
consent_notifications:
300313
)
301314
end
302315

@@ -309,7 +322,34 @@ def triage_generator
309322
consents:,
310323
triages:,
311324
vaccination_records:,
312-
parents:
325+
parents:,
326+
sessions:,
327+
consent_notifications:
313328
)
314329
end
330+
331+
def sessions
332+
@sessions ||=
333+
patient_locations
334+
.reject { it.location.generic_clinic? }
335+
.flat_map { sessions_for(it) }
336+
.uniq
337+
end
338+
339+
def sessions_for(patient_location)
340+
patient_location
341+
.location
342+
.team_locations
343+
.select { it.academic_year == academic_year }
344+
.flat_map(&:sessions)
345+
.select { it.programme_types.include?(programme_type) }
346+
.select { session_in_patient_location_date_range?(it, patient_location) }
347+
.reject(&:completed?)
348+
end
349+
350+
def session_in_patient_location_date_range?(session, patient_location)
351+
return true if session.dates.empty?
352+
353+
session.dates.any? { |date| date.in?(patient_location.date_range) }
354+
end
315355
end

0 commit comments

Comments
 (0)