Skip to content

Commit 21afa67

Browse files
committed
Enforce reason for continuing
If recent mammogram then require a reason for continuing to be provided before the appointment can proceed
1 parent 57be137 commit 21afa67

2 files changed

Lines changed: 202 additions & 0 deletions

File tree

manage_breast_screening/mammograms/tests/views/test_appointment_workflow_views.py

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
from datetime import date, datetime
2+
from datetime import timezone as tz
13
from unittest.mock import patch
24

35
import pytest
46
import statemachine
7+
import time_machine
58
from django.contrib import messages
69
from django.contrib.messages import get_messages
710
from django.urls import reverse
@@ -37,9 +40,13 @@
3740
AppointmentStatusNames,
3841
AppointmentWorkflowStepCompletion,
3942
)
43+
from manage_breast_screening.participants.models.reported_mammograms import (
44+
ParticipantReportedMammogram,
45+
)
4046
from manage_breast_screening.participants.tests.factories import (
4147
AppointmentFactory,
4248
MedicalInformationReviewFactory,
49+
ParticipantReportedMammogramFactory,
4350
)
4451
from manage_breast_screening.users.tests.factories import UserFactory
4552

@@ -290,6 +297,157 @@ def test_identity_confirmed_step_incomplete(
290297
),
291298
)
292299

300+
@pytest.mark.parametrize(
301+
"mammogram_kwargs",
302+
[
303+
{
304+
"date_type": ParticipantReportedMammogram.DateType.LESS_THAN_SIX_MONTHS,
305+
"reason_for_continuing": "reason for continuing",
306+
},
307+
{
308+
"date_type": ParticipantReportedMammogram.DateType.EXACT,
309+
"exact_date": date(2025, 8, 21),
310+
"reason_for_continuing": "reason for continuing",
311+
},
312+
{
313+
"date_type": ParticipantReportedMammogram.DateType.EXACT,
314+
"exact_date": date(2025, 7, 31),
315+
"reason_for_continuing": "reason for continuing",
316+
},
317+
{
318+
"date_type": ParticipantReportedMammogram.DateType.EXACT,
319+
"exact_date": date(2025, 2, 24),
320+
"reason_for_continuing": "reason for continuing",
321+
},
322+
{
323+
"date_type": ParticipantReportedMammogram.DateType.EXACT,
324+
"exact_date": date(2025, 2, 23),
325+
"reason_for_continuing": "reason for continuing",
326+
},
327+
{
328+
"date_type": ParticipantReportedMammogram.DateType.EXACT,
329+
"exact_date": date(2025, 2, 22),
330+
},
331+
{
332+
"date_type": ParticipantReportedMammogram.DateType.EXACT,
333+
"exact_date": date(2024, 8, 21),
334+
},
335+
{"date_type": ParticipantReportedMammogram.DateType.MORE_THAN_SIX_MONTHS},
336+
],
337+
)
338+
@time_machine.travel(datetime(2025, 8, 22, 10, tzinfo=tz.utc))
339+
def test_recent_mammogram_appointment_can_proceed(
340+
self, clinical_user_client, confirmed_identity_appointment, mammogram_kwargs
341+
):
342+
ParticipantReportedMammogramFactory.create(
343+
appointment=confirmed_identity_appointment,
344+
location_type=ParticipantReportedMammogram.LocationType.SAME_PROVIDER,
345+
**mammogram_kwargs,
346+
)
347+
response = clinical_user_client.http.get(
348+
reverse(
349+
"mammograms:record_medical_information",
350+
kwargs={"pk": confirmed_identity_appointment.pk},
351+
)
352+
)
353+
assert response.status_code == 200
354+
355+
@pytest.mark.parametrize(
356+
"mammogram_kwargs",
357+
[
358+
{"date_type": ParticipantReportedMammogram.DateType.LESS_THAN_SIX_MONTHS},
359+
{
360+
"date_type": ParticipantReportedMammogram.DateType.EXACT,
361+
"exact_date": date(2025, 8, 21),
362+
},
363+
{
364+
"date_type": ParticipantReportedMammogram.DateType.EXACT,
365+
"exact_date": date(2025, 7, 31),
366+
},
367+
{
368+
"date_type": ParticipantReportedMammogram.DateType.EXACT,
369+
"exact_date": date(2025, 2, 24),
370+
},
371+
{
372+
"date_type": ParticipantReportedMammogram.DateType.EXACT,
373+
"exact_date": date(2025, 2, 23),
374+
},
375+
],
376+
)
377+
@time_machine.travel(datetime(2025, 8, 22, 10, tzinfo=tz.utc))
378+
def test_recent_mammogram_appointment_should_not_proceed(
379+
self, clinical_user_client, confirmed_identity_appointment, mammogram_kwargs
380+
):
381+
recent_mammogram = ParticipantReportedMammogramFactory.create(
382+
appointment=confirmed_identity_appointment,
383+
location_type=ParticipantReportedMammogram.LocationType.SAME_PROVIDER,
384+
**mammogram_kwargs,
385+
)
386+
387+
response = clinical_user_client.http.get(
388+
reverse(
389+
"mammograms:record_medical_information",
390+
kwargs={"pk": confirmed_identity_appointment.pk},
391+
)
392+
)
393+
assertRedirects(
394+
response,
395+
reverse(
396+
"mammograms:appointment_should_not_proceed",
397+
kwargs={
398+
"appointment_pk": confirmed_identity_appointment.pk,
399+
"participant_reported_mammogram_pk": recent_mammogram.pk,
400+
},
401+
),
402+
)
403+
404+
@time_machine.travel(datetime(2025, 8, 22, 10, tzinfo=tz.utc))
405+
def test_multiple_recent_mammogram_appointment_should_not_proceed(
406+
self, clinical_user_client, confirmed_identity_appointment
407+
):
408+
"""
409+
Test that, when multiple recent mammograms are present,
410+
the user is redirected to the "appointment cannot proceed" page for the most recently
411+
added mammogram that is within 6 months and does not have a reason for continuing".
412+
"""
413+
414+
ParticipantReportedMammogramFactory.create(
415+
appointment=confirmed_identity_appointment,
416+
location_type=ParticipantReportedMammogram.LocationType.SAME_PROVIDER,
417+
date_type=ParticipantReportedMammogram.DateType.EXACT,
418+
exact_date=date(2025, 2, 23),
419+
)
420+
recent_mammogram = ParticipantReportedMammogramFactory.create(
421+
appointment=confirmed_identity_appointment,
422+
location_type=ParticipantReportedMammogram.LocationType.SAME_PROVIDER,
423+
date_type=ParticipantReportedMammogram.DateType.EXACT,
424+
exact_date=date(2025, 2, 24),
425+
)
426+
ParticipantReportedMammogramFactory.create(
427+
appointment=confirmed_identity_appointment,
428+
location_type=ParticipantReportedMammogram.LocationType.SAME_PROVIDER,
429+
date_type=ParticipantReportedMammogram.DateType.EXACT,
430+
exact_date=date(2025, 2, 25),
431+
reason_for_continuing="reason for continuing",
432+
)
433+
434+
response = clinical_user_client.http.get(
435+
reverse(
436+
"mammograms:record_medical_information",
437+
kwargs={"pk": confirmed_identity_appointment.pk},
438+
)
439+
)
440+
assertRedirects(
441+
response,
442+
reverse(
443+
"mammograms:appointment_should_not_proceed",
444+
kwargs={
445+
"appointment_pk": confirmed_identity_appointment.pk,
446+
"participant_reported_mammogram_pk": recent_mammogram.pk,
447+
},
448+
),
449+
)
450+
293451

294452
@pytest.mark.django_db
295453
class TestUpsertImagesView:

manage_breast_screening/mammograms/views/appointment_workflow_views.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22
import time
33
from functools import cached_property
44
from urllib.parse import urlencode
5+
from datetime import date
56

7+
from dateutil.relativedelta import relativedelta
68
from django.contrib import messages
79
from django.contrib.auth.decorators import login_required, permission_required
810
from django.core.exceptions import ValidationError
911
from django.db import DatabaseError, IntegrityError, transaction
12+
from django.db.models import Q
1013
from django.forms import Form
1114
from django.http import Http404, HttpResponse, StreamingHttpResponse
1215
from django.shortcuts import redirect, render
@@ -66,6 +69,9 @@
6669
AppointmentStatusNames,
6770
AppointmentWorkflowStepCompletion,
6871
)
72+
from manage_breast_screening.participants.models.reported_mammograms import (
73+
ParticipantReportedMammogram,
74+
)
6975
from manage_breast_screening.participants.presenters import ParticipantPresenter
7076

7177
from ..forms import (
@@ -140,6 +146,44 @@ class ReviewMedicalInformationView(WorkflowSidebarMixin, FormView):
140146
template_name = "mammograms/record_medical_information.jinja"
141147
form_class = RecordMedicalInformationForm
142148

149+
def dispatch(self, request, *args, **kwargs):
150+
# Check for recent mammograms without a reason for continuing, which would indicate that the appointment should not proceed
151+
recent_mammogram = self._get_recent_mammogram_without_reason(self.appointment)
152+
if recent_mammogram:
153+
return redirect(
154+
reverse(
155+
"mammograms:appointment_should_not_proceed",
156+
kwargs={
157+
"appointment_pk": self.appointment.pk,
158+
"participant_reported_mammogram_pk": recent_mammogram.pk,
159+
},
160+
)
161+
)
162+
163+
return super().dispatch(request, *args, **kwargs) # type: ignore
164+
165+
def _get_recent_mammogram_without_reason(self, appointment):
166+
"""
167+
Returns the most recent reported mammogram for an appointment that occurred
168+
within the last 6 months and has no reason for continuing, or None if no
169+
such mammogram exists. Used to determine if an appointment should not proceed.
170+
"""
171+
six_months_ago = date.today() - relativedelta(months=6)
172+
return (
173+
appointment.reported_mammograms.filter(
174+
reason_for_continuing="",
175+
)
176+
.filter(
177+
Q(date_type=ParticipantReportedMammogram.DateType.LESS_THAN_SIX_MONTHS)
178+
| Q(
179+
date_type=ParticipantReportedMammogram.DateType.EXACT,
180+
exact_date__gt=six_months_ago,
181+
)
182+
)
183+
.order_by("-created_at")
184+
.first()
185+
)
186+
143187
def get_context_data(self, **kwargs):
144188
context = super().get_context_data(**kwargs)
145189
participant = self.participant

0 commit comments

Comments
 (0)