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

Add list and detail serializer to bed module #1447

Closed
177 changes: 174 additions & 3 deletions care/facility/api/serializers/bed.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,53 @@
from config.serializers import ChoiceField


class BedListSerializer(ModelSerializer):
id = UUIDField(source="external_id", read_only=True)
bed_type = ChoiceField(choices=BedTypeChoices)
is_occupied = BooleanField(default=False, read_only=True)

class Meta:
model = Bed
fields = ["id", "bed_type", "description", "name", "is_occupied"]


class BedDetailSerializer(BedListSerializer):
location_object = AssetLocationSerializer(source="location", read_only=True)

location = UUIDField(write_only=True, required=True)
facility = UUIDField(write_only=True, required=True)

class Meta:
model = Bed
exclude = ("deleted", "external_id", "assets")
read_only_fields = TIMESTAMP_FIELDS

def validate(self, attrs):
user = self.context["request"].user
if "location" in attrs and "facility" in attrs:
location = get_object_or_404(
AssetLocation.objects.filter(external_id=attrs["location"])
)
facility = get_object_or_404(
Facility.objects.filter(external_id=attrs["facility"])
)
facilities = get_facility_queryset(user)
if (not facilities.filter(id=location.facility.id).exists()) or (
not facilities.filter(id=facility.id).exists()
):
raise PermissionError()
del attrs["location"]
attrs["location"] = location
attrs["facility"] = facility
else:
raise ValidationError(
{"location": "Field is Required", "facility": "Field is Required"}
)
return super().validate(attrs)


class BedSerializer(ModelSerializer):
# Remove when issue #5492 is complete
id = UUIDField(source="external_id", read_only=True)
bed_type = ChoiceField(choices=BedTypeChoices)

Expand Down Expand Up @@ -82,7 +128,7 @@ class AssetBedSerializer(ModelSerializer):
id = UUIDField(source="external_id", read_only=True)

asset_object = AssetSerializer(source="asset", read_only=True)
bed_object = BedSerializer(source="bed", read_only=True)
bed_object = BedDetailSerializer(source="bed", read_only=True)

asset = UUIDField(write_only=True, required=True)
bed = UUIDField(write_only=True, required=True)
Expand Down Expand Up @@ -135,7 +181,7 @@ def validate(self, attrs):

class PatientAssetBedSerializer(ModelSerializer):
asset = AssetSerializer(read_only=True)
bed = BedSerializer(read_only=True)
bed = BedDetailSerializer(read_only=True)
patient = SerializerMethodField()

def get_patient(self, obj):
Expand All @@ -153,9 +199,134 @@ class Meta:


class ConsultationBedSerializer(ModelSerializer):
# Remove when issue #5492 is complete
id = UUIDField(source="external_id", read_only=True)

bed_object = BedDetailSerializer(source="bed", read_only=True)

consultation = ExternalIdSerializerField(
queryset=PatientConsultation.objects.all(), write_only=True, required=True
)
bed = ExternalIdSerializerField(
queryset=Bed.objects.all(), write_only=True, required=True
)

class Meta:
model = ConsultationBed
exclude = ("deleted", "external_id")
read_only_fields = TIMESTAMP_FIELDS

def validate(self, attrs):
user = self.context["request"].user
if "consultation" in attrs and "bed" in attrs and "start_date" in attrs:
bed = attrs["bed"]
facilities = get_facility_queryset(user)
permitted_consultations = get_consultation_queryset(user)
consultation = get_object_or_404(
permitted_consultations.filter(id=attrs["consultation"].id)
)
if not facilities.filter(id=bed.facility.id).exists():
raise PermissionError()
if consultation.facility.id != bed.facility.id:
raise ValidationError(
{"consultation": "Should be in the same facility as the bed"}
)
start_date = attrs["start_date"]
end_date = attrs.get("end_date", None)
existing_qs = ConsultationBed.objects.filter(
consultation=consultation, bed=bed
)
qs = ConsultationBed.objects.filter(consultation=consultation)
# Validations based of the latest entry
if qs.exists():
latest_qs = qs.latest("id")
if latest_qs.bed == bed:
raise ValidationError({"bed": "Bed is already in use"})
if start_date < latest_qs.start_date:
raise ValidationError(
{
"start_date": "Start date cannot be before the latest start date"
}
)
if end_date and end_date < latest_qs.start_date:
raise ValidationError(
{"end_date": "End date cannot be before the latest start date"}
)
existing_qs = ConsultationBed.objects.filter(consultation=consultation)
# Conflict checking logic
if existing_qs.filter(start_date__gt=start_date).exists():
raise ValidationError({"start_date": "Cannot create conflicting entry"})
if end_date:
if existing_qs.filter(
start_date__gt=end_date, end_date__lt=end_date
).exists():
raise ValidationError(
{"end_date": "Cannot create conflicting entry"}
)
else:
raise ValidationError(
{
"consultation": "Field is Required",
"bed": "Field is Required",
"start_date": "Field is Required",
}
)
return super().validate(attrs)

def create(self, validated_data):
consultation = validated_data["consultation"]
bed = validated_data["bed"]

if not consultation.patient.is_active:
raise ValidationError(
{"patient:": ["Patient is already discharged from CARE"]}
)

occupied_beds = ConsultationBed.objects.filter(end_date__isnull=True)

if occupied_beds.filter(bed=bed).exists():
raise ValidationError({"bed:": ["Bed already in use by patient"]})

occupied_beds.filter(consultation=consultation).update(
end_date=validated_data["start_date"]
)

# This needs better logic, when an update occurs and the latest bed is no longer the last bed consultation relation added.
obj = super().create(validated_data)
consultation.current_bed = obj
consultation.save(update_fields=["current_bed"])
return obj


class BareMinimumAssetLocationSerializer(ModelSerializer):
class Meta:
model = AssetLocation
fields = ["id", "name"]


class BareMinimumBedSerializer(ModelSerializer):
location_object = BareMinimumAssetLocationSerializer(
source="location", read_only=True
)

class Meta:
model = Bed
fields = ["id", "name", "location_object"]


class ConsulationBedListSerializer(ModelSerializer):
id = UUIDField(source="external_id", read_only=True)

bed_object = BedSerializer(source="bed", read_only=True)
bed_object = BareMinimumBedSerializer(source="bed", read_only=True)

class Meta:
model = ConsultationBed
fields = ["id", "bed_object", "start_date", "end_date"]
read_only_fields = TIMESTAMP_FIELDS


class ConsultationBedDetailSerializer(ConsulationBedListSerializer):
bed_object = BedDetailSerializer(source="bed", read_only=True)

consultation = ExternalIdSerializerField(
queryset=PatientConsultation.objects.all(), write_only=True, required=True
Expand Down
4 changes: 2 additions & 2 deletions care/facility/api/serializers/patient_consultation.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from care.abdm.utils.api_call import AbdmGateway
from care.facility.api.serializers import TIMESTAMP_FIELDS
from care.facility.api.serializers.bed import ConsultationBedSerializer
from care.facility.api.serializers.bed import ConsultationBedDetailSerializer
from care.facility.api.serializers.daily_round import DailyRoundSerializer
from care.facility.api.serializers.facility import FacilityBasicInfoSerializer
from care.facility.models import (
Expand Down Expand Up @@ -92,7 +92,7 @@ class PatientConsultationSerializer(serializers.ModelSerializer):
created_by = UserBaseMinimumSerializer(read_only=True)
last_daily_round = DailyRoundSerializer(read_only=True)

current_bed = ConsultationBedSerializer(read_only=True)
current_bed = ConsultationBedDetailSerializer(read_only=True)

bed = ExternalIdSerializerField(queryset=Bed.objects.all(), required=False)

Expand Down
20 changes: 16 additions & 4 deletions care/facility/api/viewsets/bed.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@

from care.facility.api.serializers.bed import (
AssetBedSerializer,
BedSerializer,
ConsultationBedSerializer,
BedDetailSerializer,
BedListSerializer,
ConsulationBedListSerializer,
ConsultationBedDetailSerializer,
PatientAssetBedSerializer,
)
from care.facility.models.bed import AssetBed, Bed, ConsultationBed
Expand Down Expand Up @@ -52,13 +54,18 @@ class BedViewSet(
.select_related("facility", "location")
.order_by("-created_date")
)
serializer_class = BedSerializer
serializer_class = BedDetailSerializer
lookup_field = "external_id"
filter_backends = (filters.DjangoFilterBackend, drf_filters.SearchFilter)
permission_classes = [IsAuthenticated]
search_fields = ["name"]
filterset_class = BedFilter

def get_serializer_class(self):
if self.action == "list":
return BedListSerializer
return self.serializer_class

def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
Expand Down Expand Up @@ -214,11 +221,16 @@ class ConsultationBedViewSet(
.prefetch_related("assets")
.order_by("-created_date")
)
serializer_class = ConsultationBedSerializer
serializer_class = ConsultationBedDetailSerializer
filter_backends = (filters.DjangoFilterBackend,)
filterset_class = ConsultationBedFilter
lookup_field = "external_id"

def get_serializer_class(self):
if self.action == "list":
return ConsulationBedListSerializer
return self.serializer_class

def get_queryset(self):
user = self.request.user
queryset = self.queryset
Expand Down
92 changes: 92 additions & 0 deletions care/facility/tests/test_bed_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from enum import Enum

from rest_framework import status
from rest_framework.test import APIRequestFactory, APITestCase
from rest_framework_simplejwt.tokens import RefreshToken

from care.facility.tests.mixins import TestClassMixin
from care.utils.tests.test_base import TestBase


class ExpectedBedRetrieveKeys(Enum):
ID = "id"
BED_TYPE = "bed_type"
LOCATION_OBJECT = "location_object"
IS_OCCUPIED = "is_occupied"
CREATED_DATE = "created_date"
MODIFIED_DATE = "modified_date"
NAME = "name"
DESCRIPTION = "description"
META = "meta"


class ExpectedLocationObjectKeys(Enum):
ID = "id"
FACILITY = "facility"
CREATED_DATE = "created_date"
MODIFIED_DATE = "modified_date"
NAME = "name"
DESCRIPTION = "description"
LOCATION_TYPE = "location_type"


class ExpectedFacilityKeys(Enum):
ID = "id"
NAME = "name"


class ExpectedBedListKeys(Enum):
ID = "id"
BED_TYPE = "bed_type"
DESCRIPTION = "description"
NAME = "name"
IS_OCCUPIED = "is_occupied"


class BedTestCase(TestBase, TestClassMixin, APITestCase):
def setUp(self):
self.factory = APIRequestFactory()
self.bed = self.create_bed()

refresh_token = RefreshToken.for_user(self.user)
self.client.credentials(
HTTP_AUTHORIZATION=f"Bearer {refresh_token.access_token}"
)

def tes_bed_retrieve(self):
bedId = self.bed.external_id
response = self.client.get(f"/api/v1/bed/{bedId}/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = response.json()

self.assertCountEqual(
data.keys(), [item.value for item in ExpectedBedRetrieveKeys]
)

location_object_content = data["location_object"]

if location_object_content is not None:
self.assertCountEqual(
location_object_content.keys(),
[item.value for item in ExpectedLocationObjectKeys],
)

facility_content = location_object_content["facility"]

if facility_content is not None:
self.assertCountEqual(
facility_content.keys(), [item.value for item in ExpectedFacilityKeys]
)

def test_bed_list(self):
response = self.client.get("/api/v1/bed/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = response.json()

results = data["results"]
self.assertIsInstance(results, list)

for bed in results:
self.assertCountEqual(
bed.keys(), [item.value for item in ExpectedBedListKeys]
)
Loading