diff --git a/care/facility/api/serializers/asset.py b/care/facility/api/serializers/asset.py index d52c46fe34..430e2028c8 100644 --- a/care/facility/api/serializers/asset.py +++ b/care/facility/api/serializers/asset.py @@ -28,7 +28,11 @@ StatusChoices, UserDefaultAssetLocation, ) -from care.users.api.serializers.user import UserBaseMinimumSerializer +from care.users.api.serializers.user import ( + UserAssignedSerializer, + UserBaseMinimumSerializer, +) +from care.users.models import User from care.utils.assetintegration.hl7monitor import HL7MonitorAsset from care.utils.assetintegration.onvif import OnvifAsset from care.utils.assetintegration.ventilator import VentilatorAsset @@ -41,6 +45,12 @@ class AssetLocationSerializer(ModelSerializer): facility = FacilityBareMinimumSerializer(read_only=True) id = UUIDField(source="external_id", read_only=True) + duty_staff_objects = UserAssignedSerializer( + many=True, + read_only=True, + source="duty_staff", + ) + def validate_middleware_address(self, value): value = (value or "").strip() if not value: @@ -76,6 +86,38 @@ class Meta: read_only_fields = TIMESTAMP_FIELDS +class AssetLocationDutyStaffSerializer(ModelSerializer): + duty_staff_objects = UserAssignedSerializer( + many=True, + read_only=True, + source="duty_staff", + ) + + class Meta: + model = AssetLocation + fields = ( + "duty_staff_objects", + "duty_staff", + ) + + def validate(self, attrs): + duty_staff = attrs.get("duty_staff", []) + if not duty_staff: + raise ValidationError({"duty_staff": "Duty staff cannot be empty"}) + + duty_staff_ids = [user.id for user in duty_staff] + facility_id = self.instance.facility.id + + users = User.objects.filter(id__in=duty_staff_ids) + for user in users: + if user.home_facility.id != facility_id: + raise ValidationError( + {"duty_staff": "Only Home Facility Doctors and Staffs are allowed"} + ) + + return super().validate(attrs) + + class AssetBareMinimumSerializer(ModelSerializer): id = UUIDField(source="external_id", read_only=True) diff --git a/care/facility/api/viewsets/asset.py b/care/facility/api/viewsets/asset.py index ae807ac347..393b626867 100644 --- a/care/facility/api/viewsets/asset.py +++ b/care/facility/api/viewsets/asset.py @@ -30,6 +30,7 @@ from care.facility.api.serializers.asset import ( AssetAvailabilitySerializer, + AssetLocationDutyStaffSerializer, AssetLocationSerializer, AssetSerializer, AssetServiceSerializer, @@ -86,6 +87,11 @@ class AssetLocationViewSet( filter_backends = (drf_filters.SearchFilter,) search_fields = ["name"] + def get_serializer_class(self): + if self.action == "duty_staff": + return AssetLocationDutyStaffSerializer + return super().get_serializer_class() + def get_serializer_context(self): facility = self.get_facility() context = super().get_serializer_context() @@ -118,6 +124,22 @@ def get_facility(self): def perform_create(self, serializer): serializer.save(facility=self.get_facility()) + @extend_schema(tags=["asset_location"]) + @action(methods=["POST"], detail=True) + def duty_staff(self, request, facility_external_id, external_id): + """ + Endpoint for assigning staffs to asset location + """ + + asset = self.get_object() + serializer = AssetLocationDutyStaffSerializer(asset, data=request.data) + serializer.is_valid(raise_exception=True) + + staffs = serializer.validated_data["duty_staff"] + asset.duty_staff.set(staffs) + + return Response(self.get_serializer(asset).data) + class AssetFilter(filters.FilterSet): facility = filters.UUIDFilter(field_name="current_location__facility__external_id") diff --git a/care/facility/api/viewsets/facility_users.py b/care/facility/api/viewsets/facility_users.py index 578f326849..5d7213d13e 100644 --- a/care/facility/api/viewsets/facility_users.py +++ b/care/facility/api/viewsets/facility_users.py @@ -17,6 +17,9 @@ class UserFilter(filters.FilterSet): choices=[(key, key) for key in User.TYPE_VALUE_MAP], coerce=lambda role: User.TYPE_VALUE_MAP[role], ) + home_facility = filters.UUIDFilter( + field_name="home_facility__external_id", lookup_expr="exact" + ) class Meta: model = User diff --git a/care/facility/migrations/0393_assetlocation_duty_staff.py b/care/facility/migrations/0393_assetlocation_duty_staff.py new file mode 100644 index 0000000000..2242bdb607 --- /dev/null +++ b/care/facility/migrations/0393_assetlocation_duty_staff.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.5 on 2023-10-26 14:45 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("facility", "0392_alter_dailyround_consciousness_level"), + ] + + operations = [ + migrations.AddField( + model_name="assetlocation", + name="duty_staff", + field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/care/facility/models/asset.py b/care/facility/models/asset.py index bbdf39f957..2d63e54706 100644 --- a/care/facility/models/asset.py +++ b/care/facility/models/asset.py @@ -46,6 +46,11 @@ class RoomType(enum.Enum): Facility, on_delete=models.PROTECT, null=False, blank=False ) + duty_staff = models.ManyToManyField( + User, + blank=True, + ) + middleware_address = models.CharField( null=True, blank=True, default=None, max_length=200 ) diff --git a/care/facility/tests/test_asset_location_api.py b/care/facility/tests/test_asset_location_api.py index b859823c1b..56ea5729f6 100644 --- a/care/facility/tests/test_asset_location_api.py +++ b/care/facility/tests/test_asset_location_api.py @@ -75,3 +75,31 @@ def test_create_asset_location_invalid_middleware(self): self.assertEqual( response.data["middleware_address"][0].code, "invalid_domain_name" ) + + def test_assign_duty_staff(self): + # creating sample doctor and staff + + created_users = [] + user_data = [ + ("doctor1", 15), + ("staff1", 10), + ] + + for user_name, user_type in user_data: + created_user = self.create_user( + user_name, + self.district, + home_facility=self.facility, + user_type=user_type, + ) + created_users.append(created_user) + + data = {"duty_staff": [user.id for user in created_users]} + + response = self.client.post( + f"/api/v1/facility/{self.facility.external_id}/asset_location/{self.asset_location.external_id}/duty_staff/", + data, + ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.json()["duty_staff_objects"]), 2)