Skip to content

Commit

Permalink
Merge branch 'master' into hcx_refactors
Browse files Browse the repository at this point in the history
  • Loading branch information
vigneshhari authored Aug 21, 2023
2 parents 57783f9 + 914633c commit 5dfbf47
Show file tree
Hide file tree
Showing 11 changed files with 269 additions and 39 deletions.
1 change: 1 addition & 0 deletions .github/workflows/deployment-branch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
- abdm
- abdm-m2
- hcx-communications
- fix-hcx
paths-ignore:
- "docs/**"

Expand Down
129 changes: 98 additions & 31 deletions care/facility/api/serializers/bed.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from django.db import transaction
from django.db.models import Q
from django.shortcuts import get_object_or_404
from rest_framework.exceptions import ValidationError
from rest_framework.serializers import (
BooleanField,
IntegerField,
ListField,
ModelSerializer,
SerializerMethodField,
UUIDField,
Expand All @@ -11,11 +14,17 @@
from care.facility.api.serializers import TIMESTAMP_FIELDS
from care.facility.api.serializers.asset import AssetLocationSerializer, AssetSerializer
from care.facility.models.asset import Asset, AssetLocation
from care.facility.models.bed import AssetBed, Bed, ConsultationBed
from care.facility.models.bed import (
AssetBed,
Bed,
ConsultationBed,
ConsultationBedAsset,
)
from care.facility.models.facility import Facility
from care.facility.models.patient import PatientRegistration
from care.facility.models.patient_base import BedTypeChoices
from care.facility.models.patient_consultation import PatientConsultation
from care.utils.assetintegration.asset_classes import AssetClasses
from care.utils.queryset.consultation import get_consultation_queryset
from care.utils.queryset.facility import get_facility_queryset
from care.utils.serializer.external_id_field import ExternalIdSerializerField
Expand Down Expand Up @@ -85,21 +94,28 @@ class Meta:
def validate(self, attrs):
user = self.context["request"].user
if "asset" in attrs and "bed" in attrs:
asset = get_object_or_404(Asset.objects.filter(external_id=attrs["asset"]))
bed = get_object_or_404(Bed.objects.filter(external_id=attrs["bed"]))
asset: Asset = get_object_or_404(
Asset.objects.filter(external_id=attrs["asset"])
)
bed: Bed = get_object_or_404(Bed.objects.filter(external_id=attrs["bed"]))
facilities = get_facility_queryset(user)
if (
not facilities.filter(id=asset.current_location.facility.id).exists()
) or (not facilities.filter(id=bed.facility.id).exists()):
raise PermissionError()
if asset.asset_class not in [
AssetClasses.HL7MONITOR.name,
AssetClasses.ONVIF.name,
]:
raise ValidationError({"asset": "Asset is not a monitor or camera"})
attrs["asset"] = asset
attrs["bed"] = bed
if asset.current_location.facility.id != bed.facility.id:
raise ValidationError(
{"asset": "Should be in the same facility as the bed"}
)
if (
asset.asset_class in ["HL7MONITOR", "VENTILATOR"]
asset.asset_class == AssetClasses.HL7MONITOR.name
and AssetBed.objects.filter(
bed=bed, asset__asset_class=asset.asset_class
).exists()
Expand Down Expand Up @@ -147,6 +163,9 @@ class ConsultationBedSerializer(ModelSerializer):
queryset=Bed.objects.all(), write_only=True, required=True
)

assets = ListField(child=UUIDField(), required=False, write_only=True)
assets_objects = AssetSerializer(source="assets", many=True, read_only=True)

class Meta:
model = ConsultationBed
exclude = ("deleted", "external_id")
Expand All @@ -157,27 +176,45 @@ def validate(self, attrs):
if "consultation" in attrs and "bed" in attrs and "start_date" in attrs:
bed = attrs["bed"]
facilities = get_facility_queryset(user)
if not facilities.filter(id=bed.facility_id).exists():
raise PermissionError()

permitted_consultations = get_consultation_queryset(user)
consultation = get_object_or_404(
consultation: PatientConsultation = 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:
if not consultation.patient.is_active:
raise ValidationError(
{"patient:": ["Patient is already discharged from CARE"]}
)

if consultation.facility_id != bed.facility_id:
raise ValidationError(
{"consultation": "Should be in the same facility as the bed"}
)

previous_consultation_bed = consultation.current_bed
if (
previous_consultation_bed
and previous_consultation_bed.bed == bed
and set(
previous_consultation_bed.assets.order_by(
"external_id"
).values_list("external_id", flat=True)
)
== set(attrs.get("assets", []))
):
raise ValidationError(
{"consultation": "These set of bed and assets are already assigned"}
)

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(
{
Expand All @@ -188,8 +225,11 @@ def validate(self, attrs):
raise ValidationError(
{"end_date": "End date cannot be before the latest start date"}
)
existing_qs = ConsultationBed.objects.filter(consultation=consultation)

# Conflict checking logic
existing_qs = ConsultationBed.objects.filter(bed=bed).exclude(
consultation=consultation
)
if existing_qs.filter(start_date__gt=start_date).exists():
raise ValidationError({"start_date": "Cannot create conflicting entry"})
if end_date:
Expand All @@ -199,6 +239,7 @@ def validate(self, attrs):
raise ValidationError(
{"end_date": "Cannot create conflicting entry"}
)

else:
raise ValidationError(
{
Expand All @@ -211,24 +252,50 @@ def validate(self, 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)
with transaction.atomic():
ConsultationBed.objects.filter(
end_date__isnull=True, consultation=consultation
).update(end_date=validated_data["start_date"])
if assets_ids := validated_data.pop("assets", None):
assets = (
Asset.objects.filter(
Q(assigned_consultation_beds__isnull=True)
| Q(assigned_consultation_beds__end_date__isnull=False),
external_id__in=assets_ids,
current_location__facility=consultation.facility_id,
)
.exclude(
asset_class__in=[
AssetClasses.HL7MONITOR.name,
AssetClasses.ONVIF.name,
]
)
.values_list("external_id", flat=True)
)
not_found_assets = list(set(assets_ids) - set(assets))
if not_found_assets:
raise ValidationError(
f"Some assets are not available - {' ,'.join(not_found_assets)}"
)
obj: ConsultationBed = super().create(validated_data)
if assets_ids:
asset_objects = Asset.objects.filter(external_id__in=assets_ids).only(
"id"
)
ConsultationBedAsset.objects.bulk_create(
[
ConsultationBedAsset(consultation_bed=obj, asset=asset)
for asset in asset_objects
]
)

if occupied_beds.filter(bed=bed).exists():
raise ValidationError({"bed:": ["Bed already in use by patient"]})
consultation.current_bed = obj
consultation.save(update_fields=["current_bed"])
return obj

occupied_beds.filter(consultation=consultation).update(
end_date=validated_data["start_date"]
)
def update(self, instance: ConsultationBed, validated_data) -> ConsultationBed:
# assets once linked are not allowed to be changed
validated_data.pop("assets", None)

# 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
return super().update(instance, validated_data)
30 changes: 30 additions & 0 deletions care/facility/api/viewsets/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,36 @@ class AssetFilter(filters.FilterSet):
status = CareChoiceFilter(choice_dict=inverse_asset_status)
is_working = filters.BooleanFilter()
qr_code_id = filters.CharFilter(field_name="qr_code_id", lookup_expr="icontains")
in_use_by_consultation = filters.BooleanFilter(
method="filter_in_use_by_consultation",
distinct=True,
)
is_permanent = filters.BooleanFilter(method="filter_is_permanent", distinct=True)

def filter_in_use_by_consultation(self, queryset, _, value):
if value:
return queryset.filter(assigned_consultation_beds__end_date__isnull=True)
else:
return queryset.filter(
Q(assigned_consultation_beds__isnull=True)
| Q(assigned_consultation_beds__end_date__isnull=False)
)

def filter_is_permanent(self, queryset, _, value):
if value:
return queryset.filter(
asset_class__in=[
AssetClasses.ONVIF.name,
AssetClasses.HL7MONITOR.name,
]
)
else:
return queryset.exclude(
asset_class__in=[
AssetClasses.ONVIF.name,
AssetClasses.HL7MONITOR.name,
]
)


class AssetPublicViewSet(GenericViewSet):
Expand Down
1 change: 1 addition & 0 deletions care/facility/api/viewsets/bed.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ class ConsultationBedViewSet(
queryset = (
ConsultationBed.objects.all()
.select_related("consultation", "bed")
.prefetch_related("assets")
.order_by("-created_date")
)
serializer_class = ConsultationBedSerializer
Expand Down
4 changes: 4 additions & 0 deletions care/facility/api/viewsets/patient_consultation.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ def get_queryset(self):
"assigned_to__skills",
queryset=Skill.objects.filter(userskill__deleted=False),
),
"current_bed",
"current_bed__bed",
"current_bed__assets",
"current_bed__assets__current_location",
)
if self.request.user.is_superuser:
return self.queryset
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Generated by Django 4.2.2 on 2023-08-16 12:44

import uuid

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("facility", "0377_merge_20230809_0009"),
]

operations = [
migrations.CreateModel(
name="ConsultationBedAsset",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"external_id",
models.UUIDField(db_index=True, default=uuid.uuid4, unique=True),
),
(
"created_date",
models.DateTimeField(auto_now_add=True, db_index=True, null=True),
),
(
"modified_date",
models.DateTimeField(auto_now=True, db_index=True, null=True),
),
("deleted", models.BooleanField(db_index=True, default=False)),
(
"asset",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT, to="facility.asset"
),
),
(
"consultation_bed",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
to="facility.consultationbed",
),
),
],
options={
"abstract": False,
},
),
migrations.AddField(
model_name="consultationbed",
name="assets",
field=models.ManyToManyField(
related_name="assigned_consultation_beds",
through="facility.ConsultationBedAsset",
to="facility.asset",
),
),
]
14 changes: 14 additions & 0 deletions care/facility/models/bed.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,17 @@ class ConsultationBed(BaseModel):
start_date = models.DateTimeField(null=False, blank=False)
end_date = models.DateTimeField(null=True, blank=True, default=None)
meta = JSONField(default=dict, blank=True)
assets = models.ManyToManyField(
Asset, through="ConsultationBedAsset", related_name="assigned_consultation_beds"
)


class ConsultationBedAsset(BaseModel):
consultation_bed = models.ForeignKey(
ConsultationBed,
on_delete=models.PROTECT,
)
asset = models.ForeignKey(
Asset,
on_delete=models.PROTECT,
)
Loading

0 comments on commit 5dfbf47

Please sign in to comment.