this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Different current location approach

+94 -43
+48 -33
care/emr/api/viewsets/location.py
··· 32 32 FacilityLocationRetrieveSpec, 33 33 FacilityLocationUpdateSpec, 34 34 FacilityLocationWriteSpec, 35 - LocationAvailabilityStatusChoices, 36 35 LocationEncounterAvailabilityStatusChoices, 37 36 ) 38 37 from care.facility.models import Facility ··· 40 39 from care.utils.lock import Lock 41 40 42 41 42 + class AvailabilityFilter(filters.BooleanFilter): 43 + def filter(self, qs, value): 44 + if value is None: 45 + return qs 46 + if value: 47 + return qs.filter(current_encounter__isnull=True) 48 + return qs.filter(current_encounter__isnull=False) 49 + 50 + 43 51 class FacilityLocationFilter(filters.FilterSet): 44 52 parent = filters.UUIDFilter(field_name="parent__external_id") 45 53 name = filters.CharFilter(field_name="name", lookup_expr="icontains") 46 54 mode = filters.CharFilter(field_name="mode", lookup_expr="iexact") 47 - availability_status = filters.CharFilter( 48 - field_name="availability_status", lookup_expr="iexact" 49 - ) 55 + form = filters.CharFilter(field_name="form", lookup_expr="iexact") 50 56 operational_status = filters.CharFilter( 51 57 field_name="operational_status", lookup_expr="iexact" 52 58 ) 53 59 status = filters.CharFilter(field_name="status", lookup_expr="iexact") 60 + available = AvailabilityFilter(field_name="available") 54 61 55 62 56 63 class FacilityLocationViewSet(EMRModelViewSet): ··· 283 290 all_encounters = all_encounters.exclude( 284 291 id=active_location_encounter.encounter_id 285 292 ) 293 + location.current_encounter = active_location_encounter.encounter 294 + else: 295 + location.current_encounter = None 286 296 all_encounters.update(current_location=None) 287 - 288 - def reset_location_availability_status(self, location): 289 - """ 290 - Reset location availability status to the right status. 291 - """ 292 - if FacilityLocationEncounter.objects.filter( 293 - location=location, 294 - status=LocationEncounterAvailabilityStatusChoices.active.value, 295 - ).exists(): 296 - location.availability_status = ( 297 - LocationAvailabilityStatusChoices.unavailable.value 298 - ) 299 - else: 300 - location.availability_status = ( 301 - LocationAvailabilityStatusChoices.available.value 302 - ) 303 - location.save(update_fields=["availability_status"]) 297 + location.save(update_fields=["current_encounter"]) 304 298 305 299 def authorize_create(self, instance): 306 300 facility = self.get_facility_obj() ··· 326 320 self._validate_data(instance) 327 321 super().perform_create(instance) 328 322 self.reset_encounter_location_association(location) 329 - self.reset_location_availability_status(location) 330 323 331 324 def perform_update(self, instance): 332 325 location = self.get_location_obj() ··· 335 328 self._validate_data(instance, self.get_object()) 336 329 super().perform_update(instance) 337 330 self.reset_encounter_location_association(location) 338 - self.reset_location_availability_status(location) 339 331 340 332 def perform_destroy(self, instance): 341 333 super().perform_destroy(instance) 342 334 self.reset_encounter_location_association(instance.location) 343 - self.reset_location_availability_status(instance.location) 344 335 345 336 def _validate_data(self, instance, model_obj=None): # noqa PLR0912 346 337 """ ··· 351 342 raise ValidationError("Cannot assign encounters to location kind") 352 343 353 344 start_datetime = instance.start_datetime 354 - base_qs = FacilityLocationEncounter.objects.filter(location=location) 345 + base_qs = FacilityLocationEncounter.objects.all() 346 + if model_obj: 347 + encounter = model_obj.encounter 348 + else: 349 + encounter = instance.encounter 350 + if not isinstance(instance.encounter, Encounter): 351 + encounter = get_object_or_404(Encounter, external_id=encounter) 355 352 if model_obj: 356 353 # Validate if the current dates are not in conflict with other dates 357 354 base_qs = base_qs.exclude(id=model_obj.id) 358 - 359 355 status = instance.status or model_obj.status 360 356 if instance.end_datetime is not None: 361 357 end_datetime = instance.end_datetime ··· 385 381 386 382 # Ensure that there is no conflict in the schedule 387 383 if end_datetime: 388 - if base_qs.filter( 389 - start_datetime__lte=end_datetime, end_datetime__gte=start_datetime 390 - ).exists(): 384 + if ( 385 + base_qs.filter(location=location) 386 + .filter( 387 + start_datetime__lte=end_datetime, end_datetime__gte=start_datetime 388 + ) 389 + .exists() 390 + ): 391 391 raise ValidationError("Conflict in schedule") 392 - elif base_qs.filter(start_datetime__gte=start_datetime).exists(): 392 + elif ( 393 + base_qs.filter(location=location) 394 + .filter(start_datetime__gte=start_datetime) 395 + .exists() 396 + ): 393 397 raise ValidationError("Conflict in schedule") 394 398 395 399 # Ensure that there is no other association at this point 396 400 if ( 397 401 status == LocationEncounterAvailabilityStatusChoices.active.value 398 - and base_qs.filter( 399 - status=LocationEncounterAvailabilityStatusChoices.active.value 400 - ).exists() 402 + and base_qs.filter(location=location) 403 + .filter(status=LocationEncounterAvailabilityStatusChoices.active.value) 404 + .exists() 401 405 ): 402 406 raise ValidationError( 403 407 "Another active encounter already exists for this location" 408 + ) 409 + 410 + # Ensure that there is no other active location on the encounter 411 + if ( 412 + status == LocationEncounterAvailabilityStatusChoices.active.value 413 + and base_qs.filter(encounter=encounter) 414 + .filter(status=LocationEncounterAvailabilityStatusChoices.active.value) 415 + .exists() 416 + ): 417 + raise ValidationError( 418 + "Another active location already exists for this encounter" 404 419 ) 405 420 406 421 # Don't allow changes to the status once the status has reached completed
+23
care/emr/migrations/0017_remove_facilitylocation_availability_status_and_more.py
··· 1 + # Generated by Django 5.1.4 on 2025-02-19 14:30 2 + 3 + import django.db.models.deletion 4 + from django.db import migrations, models 5 + 6 + 7 + class Migration(migrations.Migration): 8 + 9 + dependencies = [ 10 + ('emr', '0016_allergyintolerance_copied_from'), 11 + ] 12 + 13 + operations = [ 14 + migrations.RemoveField( 15 + model_name='facilitylocation', 16 + name='availability_status', 17 + ), 18 + migrations.AddField( 19 + model_name='facilitylocation', 20 + name='current_encounter', 21 + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='emr.encounter'), 22 + ), 23 + ]
+3 -3
care/emr/models/location.py
··· 12 12 status = models.CharField(max_length=255) 13 13 operational_status = models.CharField(max_length=255) 14 14 name = models.CharField(max_length=255) 15 - availability_status = models.CharField( 16 - max_length=25 17 - ) # Populated from FacilityLocationEncounter 18 15 description = models.CharField(max_length=255) 19 16 mode = models.CharField(max_length=255) 20 17 location_type = models.JSONField(default=dict, null=True, blank=True) ··· 32 29 root_location = models.ForeignKey( 33 30 "self", on_delete=models.CASCADE, related_name="root", null=True, blank=True 34 31 ) 32 + current_encounter = models.ForeignKey( 33 + Encounter, on_delete=models.SET_NULL, null=True, blank=True, default=None 34 + ) # Populated from FacilityLocationEncounter 35 35 cache_expiry_days = 15 36 36 37 37 def get_parent_json(self):
+14 -7
care/emr/resources/location/spec.py
··· 17 17 completed = "completed" 18 18 19 19 20 - class LocationAvailabilityStatusChoices(str, Enum): 21 - available = "available" 22 - unavailable = "unavailable" 23 - 24 - 25 20 class StatusChoices(str, Enum): 26 21 active = "active" 27 22 inactive = "inactive" ··· 62 57 63 58 class FacilityLocationBaseSpec(EMRResource): 64 59 __model__ = FacilityLocation 65 - __exclude__ = ["parent", "facility", "organizations", "root_location"] 60 + __exclude__ = [ 61 + "parent", 62 + "facility", 63 + "organizations", 64 + "root_location", 65 + "current_encounter", 66 + ] 66 67 67 68 id: UUID4 | None = None 68 69 ··· 101 102 obj.parent = FacilityLocation.objects.get(external_id=self.parent) 102 103 else: 103 104 obj.parent = None 104 - obj.availability_status = LocationAvailabilityStatusChoices.available.value 105 105 106 106 107 107 class FacilityLocationListSpec(FacilityLocationSpec): ··· 109 109 mode: str 110 110 has_children: bool 111 111 availability_status: str 112 + current_encounter: dict | None = None 112 113 113 114 @classmethod 114 115 def perform_extra_serialization(cls, mapping, obj): 116 + from care.emr.resources.encounter.spec import EncounterListSpec 117 + 115 118 mapping["id"] = obj.external_id 116 119 mapping["parent"] = obj.get_parent_json() 120 + if obj.current_encounter: 121 + mapping["current_encounter"] = EncounterListSpec.serialize( 122 + obj.current_encounter 123 + ).to_json() 117 124 118 125 119 126 class FacilityLocationRetrieveSpec(FacilityLocationListSpec):
+6
care/emr/tests/test_location_api.py
··· 626 626 # First request should pass 627 627 response = self.client.post(self.base_url, data=data, format="json") 628 628 self.assertEqual(response.status_code, 200) 629 + self.assertEqual( 630 + FacilityLocation.objects.get( 631 + external_id=self.location["id"] 632 + ).current_encounter.id, 633 + self.encounter.id, 634 + ) 629 635 # Second request with the same time should fail 630 636 response = self.client.post(self.base_url, data=data, format="json") 631 637 self.assertEqual(response.status_code, 400)