Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

486-transfer-patient #552

Merged
merged 9 commits into from
Feb 4, 2025
85 changes: 82 additions & 3 deletions project/npda/forms/patient_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
# django imports
from django.apps import apps
from django.core.exceptions import ValidationError
from httpx import HTTPError

from project.constants.leave_pdu_reasons import LEAVE_PDU_REASONS

from ...constants.styles.form_styles import *
from ..models import Patient
from ..models import Patient, Transfer
from ..validators import not_in_the_future_validator
from .external_patient_validators import validate_patient_sync

Expand Down Expand Up @@ -50,6 +51,11 @@ def to_python(self, value):

class PatientForm(forms.ModelForm):

date_leaving_service = forms.DateField(required=False, widget=DateInput())
reason_leaving_service = forms.ChoiceField(
required=False, choices=LEAVE_PDU_REASONS
)

class Meta:
model = Patient
fields = [
Expand Down Expand Up @@ -84,6 +90,20 @@ class Meta:
"gp_practice_postcode": forms.TextInput(attrs={"class": TEXT_INPUT}),
}

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk:
try:
patient_transfer = Transfer.objects.filter(patient=self.instance).get()
self.fields["date_leaving_service"].initial = (
patient_transfer.date_leaving_service
)
self.fields["reason_leaving_service"].initial = (
patient_transfer.reason_leaving_service
)
except Transfer.DoesNotExist:
pass

def clean_date_of_birth(self):
date_of_birth = self.cleaned_data["date_of_birth"]

Expand Down Expand Up @@ -113,6 +133,20 @@ def clean_death_date(self):

return death_date

def clean_date_leaving_service(self):
date_leaving_service = self.cleaned_data["date_leaving_service"]
if date_leaving_service == "":
return None
elif date_leaving_service is not None:
not_in_the_future_validator(date_leaving_service)
return date_leaving_service

def clean_reason_leaving_service(self):
reason_leaving_service = self.cleaned_data["reason_leaving_service"]
if reason_leaving_service == "":
return None
return reason_leaving_service

def handle_async_validation_result(self, key):
value = getattr(self.async_validation_results, key)

Expand All @@ -123,13 +157,46 @@ def handle_async_validation_result(self, key):

def clean(self):
cleaned_data = self.cleaned_data

date_of_birth = cleaned_data.get("date_of_birth")
diagnosis_date = cleaned_data.get("diagnosis_date")
death_date = cleaned_data.get("death_date")
gp_practice_ods_code = cleaned_data.get("gp_practice_ods_code")
gp_practice_postcode = cleaned_data.get("gp_practice_postcode")

reason_leaving_service = cleaned_data.get("reason_leaving_service")
date_leaving_service = cleaned_data.get("date_leaving_service")
if date_leaving_service and not reason_leaving_service:
self.add_error(
"reason_leaving_service",
ValidationError(
"You must provide a reason for leaving the Paediatric Diabetes Unit"
),
)
if reason_leaving_service and not date_leaving_service:
self.add_error(
"date_leaving_service",
ValidationError(
"You must provide a date for leaving the Paediatric Diabetes Unit"
),
)
if date_leaving_service is not None and date_of_birth is not None:
if date_leaving_service < date_of_birth:
self.add_error(
"date_leaving_service",
ValidationError(
"'Date Leaving Service' cannot be before 'Date of Birth'"
),
)

if date_leaving_service is not None and diagnosis_date is not None:
if date_leaving_service < diagnosis_date:
self.add_error(
"date_leaving_service",
ValidationError(
"'Date Leaving Service' cannot be before 'Date of Diabetes Diagnosis'"
),
)

if diagnosis_date is not None and date_of_birth is not None:
if diagnosis_date < date_of_birth:
self.add_error(
Expand Down Expand Up @@ -195,5 +262,17 @@ def save(self, commit=True):

if commit:
self.instance.save()
if Transfer.objects.filter(patient=self.instance).exists():
patient_transfer = Transfer.objects.get(patient=self.instance)
patient_transfer.date_leaving_service = self.cleaned_data[
"date_leaving_service"
]
patient_transfer.reason_leaving_service = self.cleaned_data[
"reason_leaving_service"
]
patient_transfer.previous_pz_code = (
patient_transfer.paediatric_diabetes_unit.pz_code
) # set previous_pz_code to the current PZ code
patient_transfer.save()

return self.instance
1 change: 1 addition & 0 deletions project/npda/forms/visit_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ def __init__(self, *args, **kwargs):
super(VisitForm, self).__init__(*args, **kwargs)
for field_name, field in self.fields.items():
model_field = Visit._meta.get_field(field_name)

if hasattr(model_field, "category"):
field.category = model_field.category

Expand Down
17 changes: 17 additions & 0 deletions project/npda/models/patient.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,20 @@ def age(self, today_date=None):
if today_date is None:
today_date = self.get_todays_date()
return stringify_time_elapsed(self.date_of_birth, today_date)

def is_in_transfer_in_the_last_year(self):
"""
Returns True if the patient is in transfer
"""
current_audit_year = date.today().year
if date.today().month < 4:
current_audit_year -= 1

audit_year_start = date(current_audit_year, 4, 1)
audit_year_end = date(current_audit_year + 1, 3, 31)

if self.paediatric_diabetes_units.filter(
date_leaving_service__range=(audit_year_start, audit_year_end),
).exists():
return True
return False
131 changes: 91 additions & 40 deletions project/npda/templates/npda/patient_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,59 +7,110 @@
<form id="update-form" method="post" action="">
{% csrf_token %}
{% for field in form %}
<div class="md:flex md:items-center mb-6">
<div class="md:w-1/3">
<label for="{{ field.id_for_label }}"
class="block text-gray-700 font-bold md:text-center mb-1 md:mb-0 pr-4">
<small>{{ field.label }}</small>
</label>
{% if field|field_is_not_related_to_transfer %}
<div class="md:flex md:items-center mb-6">
<div class="md:w-1/3">
<label for="{{ field.id_for_label }}"
class="block text-gray-700 font-bold md:text-center mb-1 md:mb-0 pr-4">
<small>{{ field.label }}</small>
</label>
</div>
<div class="md:w-2/3">
{% if field.field.widget|is_select and field.id_for_label != 'id_reason_leaving_service' %}
<select id="{{ field.id_for_label }}"
name="{{ field.html_name }}"
class="select rcpch-select {% if not can_alter_this_audit_year_submission or not can_use_questionnaire %}opacity-40 pointer-events-none{% endif %}">
{% for choice in field.field.choices %}
<option value="{{ choice.0 }}"
{% if field.value|stringformat:'s' == choice.0|stringformat:'s' %} selected="true" {% endif %}>
{{ choice.1 }}
</option>
{% endfor %}
</select>
{% else %}
<input id="{{ field.id_for_label }}"
name="{{ field.html_name }}"
class="input rcpch-input-text {% if not can_alter_this_audit_year_submission or not can_use_questionnaire %}opacity-40 pointer-events-none{% endif %}"
{% if field.field.widget|is_dateinput %} type="date" {% else %} type="text" {% endif %}
{% if field.value %}value="{{ field.value|stringformat:'s' }}"{% endif %}>
{% endif %}
{% for error in field.errors %}
<div role="alert" class="alert alert-error py-1 my-0 rounded-none">
<svg xmlns="http://www.w3.org/2000/svg"
class="stroke-current shrink-0 h-6 w-6"
fill="none"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span>{{ error|escape }}</span>
</div>
{% endfor %}
</div>
</div>
<div class="md:w-2/3">
{% if field.field.widget|is_select %}
<select id="{{ field.id_for_label }}"
name="{{ field.html_name }}"
class="select rcpch-select {% if not can_alter_this_audit_year_submission or not can_use_questionnaire %}opacity-40 pointer-events-none{% endif %}">
{% for choice in field.field.choices %}
<option value="{{ choice.0 }}"
{% if field.value|stringformat:'s' == choice.0|stringformat:'s' %} selected="true" {% endif %}>
{{ choice.1 }}
</option>
{% endfor %}
</select>
{% else %}
<input id="{{ field.id_for_label }}"
name="{{ field.html_name }}"
class="input rcpch-input-text {% if not can_alter_this_audit_year_submission or not can_use_questionnaire %}opacity-40 pointer-events-none{% endif %}"
{% if field.field.widget|is_dateinput %} type="date" {% else %} type="text" {% endif %}
{% if field.value %}value="{{ field.value|stringformat:'s' }}"{% endif %}>
{% endif %}
{% for error in field.errors %}
<div role="alert" class="alert alert-error py-1 my-0 rounded-none">
<svg xmlns="http://www.w3.org/2000/svg"
class="stroke-current shrink-0 h-6 w-6"
fill="none"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span>{{ error|escape }}</span>
</div>
{% endif %}
{% endfor %}
<div class="collapse collapse-plus bg-base-200 py-2 mb-2">
<input type="checkbox"
{% if form.date_leaving_service.value or form.reason_leaving_service.value %}checked{% endif %} />
<div class="collapse-title text-xl font-medium">
Transfer Patient to Another PDU
</div>
<div class="collapse-content">
<select id="{{ form.reason_leaving_service.id_for_label }}"
name="{{ form.reason_leaving_service.html_name }}"
class="select rcpch-select mb-2 {% if not can_alter_this_audit_year_submission or not can_use_questionnaire %}opacity-40 pointer-events-none{% endif %}">
<option value="">Select a reason</option>
{% for choice in form.reason_leaving_service.field.choices %}
<option value={{ choice.0 }} {% if form.reason_leaving_service.value == choice.0 %} selected="true" {% endif %}>
{{ choice.1 }}
</option>
{% endfor %}
</div>
</select>
{% for error in form.reason_leaving_service.errors %}
<div role="alert" class="alert alert-error py-1 my-0 rounded-none">
<svg xmlns="http://www.w3.org/2000/svg"
class="stroke-current shrink-0 h-6 w-6"
fill="none"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span>{{ error|escape }}</span>
</div>
{% endfor %}
<input id="{{ form.date_leaving_service.id_for_label }}"
name="{{ form.date_leaving_service.html_name }}"
class="input rcpch-input-text mb-2 {% if not can_alter_this_audit_year_submission or not can_use_questionnaire %}opacity-40 pointer-events-none{% endif %}"
type="date"
{% if form.date_leaving_service.value %}value="{{ form.date_leaving_service.value|stringformat:'s' }}"{% endif %}>
{% for error in form.date_leaving_service.errors %}
<div role="alert" class="alert alert-error py-1 my-0 rounded-none">
<svg xmlns="http://www.w3.org/2000/svg"
class="stroke-current shrink-0 h-6 w-6"
fill="none"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span>{{ error|escape }}</span>
</div>
{% endfor %}
</div>
{% endfor %}
</div>
<div class="flex justify-end">
<a role="button"
class="btn rcpch-light-blue-btn"
href='{% url 'patients' %}'>Back to list</a>
<button type="submit"
value="Submit"
value="submit"
name="action"
class="btn rcpch-light-blue-btn {% if not can_alter_this_audit_year_submission or not can_use_questionnaire %}opacity-40 pointer-events-none{% endif %}">
{{ button_title }}
</button>
</div>
{% if form_method == 'update' and perms.npda.delete_patient %}
<a class="bg-rcpch_red text-white font-semibold hover:text-white py-2.5 px-3 mt-20 border border-rcpch_red hover:bg-rcpch_red_dark_tint hover:border-rcpch_red_dark_tint {% if not can_alter_this_audit_year_submission or not can_use_questionnaire %}opacity-40 pointer-events-none{% endif %}"
href="{% url 'patient-delete' patient_id %}">Delete</a>
<button type="submit"
name="action"
class="rcpch-danger-btn {% if not can_alter_this_audit_year_submission or not can_use_questionnaire %}opacity-40 pointer-events-none{% endif %}"
href="{% url 'patient-delete' patient_id %}">Delete</button>
{% endif %}
</form>
</div>
Expand Down
14 changes: 6 additions & 8 deletions project/npda/templates/npda/visit_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
{% block content %}
<div class="flex justify-center bg-white py-8">
<div class="max-w-full lg:max-w-[75%] px-2 bg-white font-montserrat">
<strong>
{{ title }} - NHS Number {{ nhs_number }}
</strong>
<strong>{{ title }} - NHS Number {{ nhs_number }}</strong>
<form id="update-form"
method="post"
{% if form_method == "create" %} action="{% url 'visit-create' patient_id %}" {% else %} action="{% url 'visit-update' patient_id=patient_id pk=visit.pk %}" {% endif %}>
Expand All @@ -23,10 +21,10 @@
</div>
<div class="flex space-between md:w-2/3">
{% if field.id_for_label == "id_visit_date" %}
<input type="date" id="{{ field.id_for_label }}" name="{{ field.html_name }}" class="input rcpch-input-text {% if not can_alter_this_audit_year_submission or not can_use_questionnaire %}opacity-40 pointer-events-none{% endif %}" {% if field.value %}value={{ field.value|stringformat:'s' }}{% endif %}>
<input type="date" id="{{ field.id_for_label }}" name="{{ field.html_name }}" class="input rcpch-input-text {% if not can_alter_this_audit_year_submission or not can_use_questionnaire or form.patient.is_in_transfer_in_the_last_year %} opacity-40 pointer-events-none {% endif %}" {% if field.value %}value={{ field.value|stringformat:'s' }}{% endif %}>
<button type='button'
_="on click set #{{ field.id_for_label }}'s value to '{% today_date %}'"
class="btn rcpch-btn bg-rcpch_light_blue border border-rcpch_light_blue hover:bg-rcpch_strong_blue hover:border-rcpch_strong_blue {% if not can_alter_this_audit_year_submission or not can_use_questionnaire %}opacity-40 pointer-events-none{% endif %}">
class="btn rcpch-btn bg-rcpch_light_blue border border-rcpch_light_blue hover:bg-rcpch_strong_blue hover:border-rcpch_strong_blue {% if not can_alter_this_audit_year_submission or not can_use_questionnaire or form.patient.is_in_transfer_in_the_last_year %}opacity-40 pointer-events-none{% endif %}">
Today
</button>
{% endif %}
Expand All @@ -47,13 +45,13 @@
<div class="flex justify-end">
<button type="submit"
value="Submit"
class="btn rcpch-btn bg-rcpch_light_blue border border-rcpch_light_blue hover:bg-rcpch_strong_blue hover:border-rcpch_strong_blue {% if not can_alter_this_audit_year_submission or not can_use_questionnaire %}opacity-40 pointer-events-none{% endif %}">
class="btn rcpch-btn bg-rcpch_light_blue border border-rcpch_light_blue hover:bg-rcpch_strong_blue hover:border-rcpch_strong_blue {% if not can_alter_this_audit_year_submission or not can_use_questionnaire or form.patient.is_in_transfer_in_the_last_year %}opacity-40 pointer-events-none{% endif %}">
{{ button_title }}
</button>
<a class="btn rcpch-btn bg-rcpch_yellow_light_tint1 border border-rcpch_yellow_light_tint1 hover:bg-rcpch_yellow hover:border-rcpch_yellow"
href="{% url 'patient_visits' patient_id=patient_id %}">Cancel</a>
{% if form_method == 'update' and perms.npda.delete_visit %}
<a class="btn rcpch-btn bg-rcpch_red text-white font-semibold hover:text-white py-2.5 px-3 border border-rcpch_red hover:bg-rcpch_red_dark_tint hover:border-rcpch_red_dark_tint {% if not can_alter_this_audit_year_submission or not can_use_questionnaire %}opacity-40 pointer-events-none{% endif %}"
<a class="btn rcpch-btn bg-rcpch_red text-white font-semibold hover:text-white py-2.5 px-3 border border-rcpch_red hover:bg-rcpch_red_dark_tint hover:border-rcpch_red_dark_tint {% if not can_alter_this_audit_year_submission or not can_use_questionnaire or form.patient.is_in_transfer_in_the_last_year %}opacity-40 pointer-events-none{% endif %}"
href="{% url 'visit-delete' patient_id visit.pk %}">Delete</a>
{% endif %}
</div>
Expand Down Expand Up @@ -82,7 +80,7 @@
} else {
tab.checked = false;
}
}
}
} else {
anchor = document.querySelector('a[data-has-errors]');
}
Expand Down
Loading