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

fix(asset_location): added duty_staff endpoint #1689

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions care/facility/api/serializers/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@
StatusChoices,
UserDefaultAssetLocation,
)
from care.users.api.serializers.user import UserBaseMinimumSerializer
from care.users.api.serializers.user import (
UserAssignedSerializer,
UserBaseMinimumSerializer,
)
from care.utils.assetintegration.hl7monitor import HL7MonitorAsset
from care.utils.assetintegration.onvif import OnvifAsset
from care.utils.assetintegration.ventilator import VentilatorAsset
Expand All @@ -42,6 +45,11 @@ class AssetLocationSerializer(ModelSerializer):
facility = FacilityBareMinimumSerializer(read_only=True)
id = UUIDField(source="external_id", read_only=True)
location_type = ChoiceField(choices=AssetLocation.RoomTypeChoices)
users = serializers.SerializerMethodField()

def get_users(self, obj):
users = obj.users.filter(assetlocationdutystaff__deleted=False)
return UserAssignedSerializer(users, many=True, read_only=True).data

def validate_middleware_address(self, value):
value = (value or "").strip()
Expand Down Expand Up @@ -130,7 +138,10 @@ def update(self, instance, validated_data):
"type": "object",
"properties": {
"hostname": {"type": "string"},
"source": {"type": "string", "enum": ["asset", "location", "facility"]},
"source": {
"type": "string",
"enum": ["asset", "location", "facility"],
},
},
"nullable": True,
}
Expand Down Expand Up @@ -214,7 +225,9 @@ def create(self, validated_data):
asset_instance = super().create(validated_data)
if last_serviced_on or note:
asset_service = AssetService(
asset=asset_instance, serviced_on=last_serviced_on, note=note
asset=asset_instance,
serviced_on=last_serviced_on,
note=note,
)
asset_service.save()
asset_instance.last_service = asset_service
Expand Down
82 changes: 81 additions & 1 deletion care/facility/api/viewsets/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,14 @@
Asset,
AssetAvailabilityRecord,
AssetLocation,
AssetLocationDutyStaff,
AssetService,
AssetTransaction,
ConsultationBedAsset,
UserDefaultAssetLocation,
)
from care.facility.models.asset import AssetTypeChoices, StatusChoices
from care.users.api.serializers.user import UserBaseMinimumSerializer
from care.users.models import User
from care.utils.assetintegration.asset_classes import AssetClasses
from care.utils.assetintegration.base import BaseAssetIntegration
Expand Down Expand Up @@ -118,6 +120,82 @@
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: AssetLocation = self.get_object()
duty_staff = request.data.get("duty_staff")

if not duty_staff:
return Response(status=status.HTTP_204_NO_CONTENT)

Check warning on line 134 in care/facility/api/viewsets/asset.py

View check run for this annotation

Codecov / codecov/patch

care/facility/api/viewsets/asset.py#L134

Added line #L134 was not covered by tests

query = AssetLocationDutyStaff.objects.filter(
asset_location=asset, user__id=duty_staff, deleted=False
)

if query.exists():
raise ValidationError(

Check warning on line 141 in care/facility/api/viewsets/asset.py

View check run for this annotation

Codecov / codecov/patch

care/facility/api/viewsets/asset.py#L141

Added line #L141 was not covered by tests
{"duty_staff": "Staff already assigned to the location"}
)

user = User.objects.filter(id=duty_staff, home_facility=asset.facility)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be performed first, we need to check if a use exists before doing anything else, we also need to ensure that the id is an integer or perform some validation on it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use first() here itself. no need for 2 queries.

if not user.exists():
raise ValidationError(

Check warning on line 147 in care/facility/api/viewsets/asset.py

View check run for this annotation

Codecov / codecov/patch

care/facility/api/viewsets/asset.py#L147

Added line #L147 was not covered by tests
{"duty_staff": "Staff does not belong to the facility"}
)

AssetLocationDutyStaff.objects.create(
asset_location=asset, user=user.first(), created_by=request.user
)

return Response(status=status.HTTP_201_CREATED)

@extend_schema(tags=["asset_location"])
@duty_staff.mapping.get
def duty_staff_get(self, request, facility_external_id, external_id):
"""
Endpoint for getting staffs from asset location
"""

asset: AssetLocation = self.get_object()

Check warning on line 164 in care/facility/api/viewsets/asset.py

View check run for this annotation

Codecov / codecov/patch

care/facility/api/viewsets/asset.py#L164

Added line #L164 was not covered by tests

duty_staff = User.objects.filter(

Check warning on line 166 in care/facility/api/viewsets/asset.py

View check run for this annotation

Codecov / codecov/patch

care/facility/api/viewsets/asset.py#L166

Added line #L166 was not covered by tests
id__in=AssetLocationDutyStaff.objects.filter(
asset_location=asset, deleted=False
).values_list("user__id", flat=True)
)

return Response(

Check warning on line 172 in care/facility/api/viewsets/asset.py

View check run for this annotation

Codecov / codecov/patch

care/facility/api/viewsets/asset.py#L172

Added line #L172 was not covered by tests
UserBaseMinimumSerializer(duty_staff, many=True).data,
status=status.HTTP_200_OK,
)

@extend_schema(tags=["asset_location"])
@duty_staff.mapping.delete
def duty_staff_delete(self, request, facility_external_id, external_id):
"""
Endpoint for removing staffs from asset location
"""

asset: AssetLocation = self.get_object()

if "duty_staff" not in request.data:
raise ValidationError({"duty_staff": "Staff is required"})

Check warning on line 187 in care/facility/api/viewsets/asset.py

View check run for this annotation

Codecov / codecov/patch

care/facility/api/viewsets/asset.py#L187

Added line #L187 was not covered by tests

duty_staff = request.data.get("duty_staff")
if not duty_staff:
raise ValidationError({"duty_staff": "Staff is required"})

Check warning on line 191 in care/facility/api/viewsets/asset.py

View check run for this annotation

Codecov / codecov/patch

care/facility/api/viewsets/asset.py#L191

Added line #L191 was not covered by tests

AssetLocationDutyStaff.objects.filter(
asset_location=asset, user__id=duty_staff
).update(deleted=True)

return Response(status=status.HTTP_204_NO_CONTENT)


class AssetFilter(filters.FilterSet):
facility = filters.UUIDFilter(field_name="current_location__facility__external_id")
Expand Down Expand Up @@ -260,7 +338,9 @@
queryset = self.filter_queryset(self.get_queryset()).values(*mapping.keys())
pretty_mapping = Asset.CSV_MAKE_PRETTY.copy()
return render_to_csv_response(
queryset, field_header_map=mapping, field_serializer_map=pretty_mapping
queryset,
field_header_map=mapping,
field_serializer_map=pretty_mapping,
)

return super(AssetViewSet, self).list(request, *args, **kwargs)
Expand Down
3 changes: 3 additions & 0 deletions care/facility/api/viewsets/facility_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Generated by Django 4.2.5 on 2023-11-17 13:25

import uuid

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


class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("facility", "0397_truncate_discharge_time"),
]

operations = [
migrations.CreateModel(
name="AssetLocationDutyStaff",
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_location",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="facility.assetlocation",
),
),
(
"created_by",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="+",
to=settings.AUTH_USER_MODEL,
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"abstract": False,
},
),
migrations.AddField(
model_name="assetlocation",
name="users",
field=models.ManyToManyField(
blank=True,
related_name="duty_staff",
through="facility.AssetLocationDutyStaff",
to=settings.AUTH_USER_MODEL,
),
),
]
12 changes: 12 additions & 0 deletions care/facility/migrations/0403_merge_20231215_0845.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Generated by Django 4.2.5 on 2023-12-15 03:15

from django.db import migrations


class Migration(migrations.Migration):
dependencies = [
("facility", "0398_assetlocationdutystaff_assetlocation_users"),
("facility", "0402_patientconsultation_new_discharge_reason"),
]

operations = []
42 changes: 40 additions & 2 deletions care/facility/models/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,43 @@
Facility, on_delete=models.PROTECT, null=False, blank=False
)

users = models.ManyToManyField(
User,
through="AssetLocationDutyStaff",
related_name="duty_staff",
through_fields=("asset_location", "user"),
blank=True,
)

middleware_address = models.CharField(
null=True, blank=True, default=None, max_length=200
)


class AssetLocationDutyStaff(BaseModel):
asset_location = models.ForeignKey(
AssetLocation, on_delete=models.CASCADE, null=False, blank=False
)
user = models.ForeignKey(User, on_delete=models.CASCADE, null=False, blank=False)

created_by = models.ForeignKey(
User,
on_delete=models.PROTECT,
null=False,
blank=False,
related_name="+",
)

def __str__(self):
return f"{self.user} under {self.asset_location.name}"

Check warning on line 78 in care/facility/models/asset.py

View check run for this annotation

Codecov / codecov/patch

care/facility/models/asset.py#L78

Added line #L78 was not covered by tests

CSV_MAPPING = {
"asset_location__name": "Asset Location Name",
"user__username": "User",
"created_by__username": "Assigned By",
}


class AssetType(enum.Enum):
INTERNAL = 50
EXTERNAL = 100
Expand Down Expand Up @@ -80,7 +112,11 @@
choices=AssetTypeChoices, default=AssetType.INTERNAL.value
)
asset_class = models.CharField(
choices=AssetClassChoices, default=None, null=True, blank=True, max_length=20
choices=AssetClassChoices,
default=None,
null=True,
blank=True,
max_length=20,
)
status = models.IntegerField(choices=StatusChoices, default=Status.ACTIVE.value)
current_location = models.ForeignKey(
Expand All @@ -91,7 +127,9 @@
serial_number = models.CharField(max_length=1024, blank=True, null=True)
warranty_details = models.TextField(null=True, blank=True, default="") # Deprecated
meta = JSONField(
default=dict, blank=True, validators=[JSONFieldSchemaValidator(ASSET_META)]
default=dict,
blank=True,
validators=[JSONFieldSchemaValidator(ASSET_META)],
)
# Vendor Details
vendor_name = models.CharField(max_length=1024, blank=True, null=True)
Expand Down
Loading