diff --git a/edc_next_appointment/model_mixins.py b/edc_next_appointment/model_mixins.py index d97d0e8..6854205 100644 --- a/edc_next_appointment/model_mixins.py +++ b/edc_next_appointment/model_mixins.py @@ -48,6 +48,15 @@ class NextAppointmentCrfModelMixin(models.Model): ), ) + allow_create_interim = models.BooleanField( + verbose_name=_("Override date check?"), + default=False, + help_text=( + "Override date check to allow the EDC to create an interim appointment if the " + "date is within window period of the current appointment." + ), + ) + class Meta: abstract = True verbose_name = "Next Appointment" diff --git a/edc_next_appointment/modeladmin_mixins.py b/edc_next_appointment/modeladmin_mixins.py index e152982..31d42aa 100644 --- a/edc_next_appointment/modeladmin_mixins.py +++ b/edc_next_appointment/modeladmin_mixins.py @@ -12,7 +12,15 @@ class NextAppointmentModelAdminMixin: (None, {"fields": ("subject_visit", "report_datetime")}), ( "Integrated Clinic", - {"fields": ("health_facility", "appt_date", "info_source", "visitschedule")}, + { + "fields": ( + "info_source", + "health_facility", + "appt_date", + "allow_create_interim", + "visitschedule", + ) + }, ), crf_status_fieldset_tuple, audit_fieldset_tuple, diff --git a/edc_next_appointment/modelform_mixins.py b/edc_next_appointment/modelform_mixins.py index 88db0cb..bd5f437 100644 --- a/edc_next_appointment/modelform_mixins.py +++ b/edc_next_appointment/modelform_mixins.py @@ -14,22 +14,29 @@ class NextAppointmentModelFormMixin: + appt_date_fld = "appt_date" + visit_code_fld = "visitschedule" + def clean(self): cleaned_data = super().clean() self.validate_suggested_date_with_future_appointments() self.validate_suggested_visit_code() return cleaned_data + @property + def allow_create_interim(self) -> str: + return self.cleaned_data.get("allow_create_interim", False) + @property def suggested_date(self) -> date | None: - return self.cleaned_data.get("appt_date") + return self.cleaned_data.get(self.appt_date_fld) @property def suggested_visit_code(self) -> str | None: return getattr( - self.cleaned_data.get("visitschedule"), + self.cleaned_data.get(self.visit_code_fld), "visit_code", - self.cleaned_data.get("visitschedule"), + self.cleaned_data.get(self.visit_code_fld), ) def validate_suggested_date_with_future_appointments(self): @@ -48,7 +55,7 @@ def validate_suggested_date_with_future_appointments(self): ) raise forms.ValidationError( { - "appt_date": _( + self.appt_date_fld: _( "Invalid. Next visit report already submitted. Expected " "`%(dt)s`. See `%(visit_code)s`." ) @@ -69,7 +76,7 @@ def validate_suggested_date_with_future_appointments(self): date_format = convert_php_dateformat(settings.SHORT_DATE_FORMAT) raise forms.ValidationError( { - "appt_date": _( + self.appt_date_fld: _( "Invalid. Expected a date before appointment " "`%(visit_code)s` on " "%(dt_str)s." @@ -95,21 +102,10 @@ def validate_suggested_visit_code(self): raise_if_in_gap=False, ) except ScheduledVisitWindowError as e: - raise forms.ValidationError({"appt_date": str(e)}) + raise forms.ValidationError({self.appt_date_fld: str(e)}) if not appointment: raise forms.ValidationError( - {"appt_date": _("Invalid. Must be within the followup period.")} - ) - elif appointment == subject_visit.appointment: - raise forms.ValidationError( - { - "appt_date": ( - _( - "Invalid. Cannot be within window period " - "of current appointment." - ) - ) - } + {self.appt_date_fld: _("Invalid. Must be within the followup period.")} ) if ( @@ -119,7 +115,7 @@ def validate_suggested_visit_code(self): date_format = convert_php_dateformat(settings.SHORT_DATE_FORMAT) raise forms.ValidationError( { - "visitschedule": _( + self.visit_code_fld: _( "Expected %(visit_code)s using %(dt_str)s from above." ) % { @@ -128,6 +124,29 @@ def validate_suggested_visit_code(self): } } ) + if appointment == subject_visit.appointment: + if self.allow_create_interim: + pass + else: + raise forms.ValidationError( + { + self.appt_date_fld: ( + _( + "Invalid. Cannot be within window period " + "of the current appointment." + ) + ) + } + ) + elif self.allow_create_interim: + raise forms.ValidationError( + { + "allow_create_interim": _( + "Cannot override if date is not within window period " + "of the current appointment." + ) + } + ) @staticmethod def as_datetime(dt: date) -> datetime: diff --git a/edc_next_appointment/tests/tests/test_next_appointment.py b/edc_next_appointment/tests/tests/test_next_appointment.py index e504038..8c84450 100644 --- a/edc_next_appointment/tests/tests/test_next_appointment.py +++ b/edc_next_appointment/tests/tests/test_next_appointment.py @@ -5,7 +5,7 @@ from dateutil.relativedelta import relativedelta from django.contrib.auth.models import User from django.core.exceptions import ObjectDoesNotExist -from django.test import TestCase, override_settings, tag +from django.test import TestCase, override_settings from edc_appointment.constants import SKIPPED_APPT from edc_appointment.models import Appointment from edc_constants.constants import NOT_APPLICABLE, PATIENT @@ -74,7 +74,6 @@ def test_ok(self): subject_visit_model_cls = get_related_visit_model_cls() subject_visit_model_cls.objects.create(appointment=appointment, reason=SCHEDULED) - @tag("1") @override_settings( EDC_APPOINTMENT_ALLOW_SKIPPED_APPT_USING={ "next_appointment_app.nextappointment": ("appt_date", "visitschedule")