Skip to content

ft-close-incident-reports: update incident report transitional state #322

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

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
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
85 changes: 85 additions & 0 deletions api/tests/test_asset_status_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@
from unittest.mock import patch

# Third-Party Imports
from django.apps import apps
from django.contrib.auth import get_user_model
from rest_framework import status
from rest_framework.reverse import reverse
from rest_framework.test import APIClient

# App Imports
from api.tests import APIBaseTestCase
from core import constants
from core.models import Asset

User = get_user_model()
Expand Down Expand Up @@ -141,3 +145,84 @@ def test_authenticated_user_delete_not_allowed(self, mock_verify_id_token):
)
self.assertEqual(response.data, {"detail": 'Method "DELETE" not allowed.'})
self.assertEqual(response.status_code, 405)


class TestTransitionStateUpdateFromAssetStatusAPIModification(APIBaseTestCase):
"""
Test Transition state modification when asset status changes from API modification
"""

def setUp(self):
self.incident_report = apps.get_model(
"core", "AssetIncidentReport"
).objects.create(asset=self.asset)

def allocate_asset(self):
"""
Allocate asset to update status to allocated
:return:
"""
data = {"asset": self.asset.id, "current_assignee": self.asset_assignee.id}
response = client.post(
self.allocations_urls,
data,
HTTP_AUTHORIZATION="Token {}".format(self.token_user),
)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)

def get_transition_state(self):
url = reverse(
"state-transitions-detail", kwargs={"pk": str(self.incident_report.id)}
)
print(url)
response = client.get(
f"{url}/", HTTP_AUTHORIZATION="Token {}".format(self.admin_user)
)
return response.json()

@patch("api.authentication.auth.verify_id_token")
def test_update_asset_status_from_allocated_to_lost(self, mock_verify_id_token):
mock_verify_id_token.return_value = {"email": self.user.email}
self.allocate_asset()
data = {"asset": self.asset.id, "current_status": constants.LOST}
client.post(
self.asset_status_urls,
data=data,
HTTP_AUTHORIZATION="Token {}".format(self.token_user),
)
transition_state = apps.get_model("core", "StateTransition").objects.get(
asset_incident_report=self.incident_report
)
self.assertEqual(transition_state.incident_report_state, constants.CLOSED)

@patch("api.authentication.auth.verify_id_token")
def test_update_asset_status_from_allocated_to_damaged(self, mock_verify_id_token):
mock_verify_id_token.return_value = {"email": self.user.email}
self.allocate_asset()
data = {"asset": self.asset.id, "current_status": constants.DAMAGED}
client.post(
self.asset_status_urls,
data=data,
HTTP_AUTHORIZATION="Token {}".format(self.token_user),
)
transition_state = apps.get_model("core", "StateTransition").objects.get(
asset_incident_report=self.incident_report
)
self.assertEqual(transition_state.incident_report_state, constants.CLOSED)

@patch("api.authentication.auth.verify_id_token")
def test_update_asset_status_from_allocated_to_available(
self, mock_verify_id_token
):
mock_verify_id_token.return_value = {"email": self.user.email}
self.allocate_asset()
data = {"asset": self.asset.id, "current_status": constants.AVAILABLE}
client.post(
self.asset_status_urls,
data=data,
HTTP_AUTHORIZATION="Token {}".format(self.token_user),
)
transition_state = apps.get_model("core", "StateTransition").objects.get(
asset_incident_report=self.incident_report
)
self.assertEqual(transition_state.incident_report_state, constants.CLOSED)
1 change: 1 addition & 0 deletions core/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
default_app_config = 'core.apps.CoreConfig'
3 changes: 3 additions & 0 deletions core/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@

class CoreConfig(AppConfig):
name = "core"

def ready(self):
import core.signals # noqa
2 changes: 2 additions & 0 deletions core/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,14 @@
INTERNAL_ASSESSMENT = "internal assessment"
EXTERNAL_ASSESSMENT = "external assessment"
OUT_FOR_REPAIR = "out for repair"
CLOSED = "closed"

REPORT_STATE_OPTIONS = (
(NEWLY_REPORTED, "newly reported"),
(INTERNAL_ASSESSMENT, "internal assessment"),
(EXTERNAL_ASSESSMENT, "external assessment"),
(OUT_FOR_REPAIR, "out for repair"),
(CLOSED, "closed"),
)

REQUIRES_REPAIR = "requires repair"
Expand Down
1 change: 0 additions & 1 deletion core/models/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,6 @@ class Meta:
verbose_name_plural = "State Transitions"

def save(self, *args, **kwargs):

# admin updates asset_status if damaged
if self.asset_state_from_report == "Damaged":
AssetStatus.objects.create(
Expand Down
43 changes: 43 additions & 0 deletions core/signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Third-Party Imports
from django.db.models.signals import post_save
from django.dispatch import receiver

# App Imports
from core import constants
from core.models import AssetIncidentReport, AssetStatus, StateTransition


@receiver(post_save, sender=AssetStatus)
def update_transition_state(**kwargs):
"""
update the transition state to CLOSED when an Asset status is changed from ALLOCATED to
either (AVAILABLE,DAMAGED,LOST)
:param kwargs:
:return None:
"""
instance = kwargs.get("instance") # the instance of the incident report being saved
valid_previous_asset_types = (constants.ALLOCATED,)
valid_current_asset_types = (constants.DAMAGED, constants.LOST, constants.AVAILABLE)

# previous_instance = AssetStatus.objects.filter(id=instance.id).latest('created_at')
current_asset_status = instance.current_status
previous_asset_status = instance.previous_status

try:
if previous_asset_status in valid_previous_asset_types:
if current_asset_status in valid_current_asset_types:
# get the latest incident report
# filter out all results that dont have a recorded created_at date
latest_incident_report = AssetIncidentReport.objects.filter(
asset=instance.asset, created_at__isnull=False
).latest('created_at')
# get the transition state associated with the incident report
transition_state = StateTransition.objects.get_or_create(
asset_incident_report=latest_incident_report
)
transition_state = transition_state[0]
# update the transition state
transition_state.incident_report_state = constants.CLOSED
transition_state.save()
except AssetIncidentReport.DoesNotExist:
pass
150 changes: 95 additions & 55 deletions core/tests/test_state_transition_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@

# App Imports
from core import constants
from core.models import AssetIncidentReport, StateTransition
from core.tests import CoreBaseTestCase


class StateTransitionModelTest(CoreBaseTestCase):
"Tests state trensition model"
"""Tests state transition model"""

def test_create_incident_report_creates_new_state_transition(self):
incident_report_count = AssetIncidentReport.objects.count()
state_transition_count = StateTransition.objects.count()
AssetIncidentReport.objects.create(
incident_report_count = apps.get_model(
"core", "AssetIncidentReport"
).objects.count()
state_transition_count = apps.get_model(
"core", "StateTransition"
).objects.count()
apps.get_model("core", "AssetIncidentReport").objects.create(
asset=self.test_asset,
incident_type="Loss",
incident_location="44",
Expand All @@ -24,67 +27,104 @@ def test_create_incident_report_creates_new_state_transition(self):
police_abstract_obtained="Yes",
submitted_by=self.user,
)
self.assertEqual(AssetIncidentReport.objects.count(), incident_report_count + 1)
self.assertEqual(StateTransition.objects.count(), state_transition_count + 1)
self.assertEqual(
apps.get_model("core", "AssetIncidentReport").objects.count(),
incident_report_count + 1,
)
self.assertEqual(
apps.get_model("core", "StateTransition").objects.count(),
state_transition_count + 1,
)


class TestStateTransitionNotifications(CoreBaseTestCase):
class TestTransitionStateUpdateFromAssetStatusModification(CoreBaseTestCase):
"""
Test notifications are created when Transition states are modified appropriately
Test updating the transition state to CLOSED when an Asset status is changed from ALLOCATED to
either (AVAILABLE,DAMAGED,LOST)
"""

Notifications = apps.get_model("core", "Notifications")
def setUp(self):
self.asset = apps.get_model("core", "Asset").objects.create(
asset_code="IC0019009",
serial_number="SN001000098",
model_number=self.test_assetmodel,
purchase_date="2019-07-10",
)
self.incident_report = apps.get_model(
"core", "AssetIncidentReport"
).objects.create(asset=self.asset)
self.transition_state = apps.get_model(
"core", "StateTransition"
).objects.get_or_create(asset_incident_report_id=self.incident_report.id)
self.transition_state = self.transition_state[0]

def test_notification_created_when_Transition_state_is_created(self):
state_transition_count = StateTransition.objects.count()
notifications_count = self.Notifications.objects.filter(
target=self.user
).count()
incident = AssetIncidentReport.objects.create(
asset=self.test_asset,
incident_type="Loss",
incident_location="44",
incident_description="Mugging",
injuries_sustained="Black eye",
loss_of_property="Laptop",
witnesses="Omosh wa mtura",
police_abstract_obtained="Yes",
submitted_by=self.user,
def test_update_asset_status_from_allocated_to_lost(self):
status = (
apps.get_model("core", "AssetStatus")
.objects.filter(asset=self.asset)
.latest('created_at')
)
self.assertEqual(StateTransition.objects.count(), state_transition_count + 1)
self.assertEqual(
self.Notifications.objects.filter(target=incident.submitted_by).count(),
notifications_count + 1,

# allocate asset to a user
apps.get_model("core", "AllocationHistory").objects.create(
asset=self.asset, current_assignee=self.asset_assignee2
)

def test_notification_created_when_transition_state_is_updated(self):
incident = AssetIncidentReport.objects.create(
asset=self.test_asset,
incident_type="Loss",
incident_location="44",
incident_description="Mugging",
injuries_sustained="Black eye",
loss_of_property="Laptop",
witnesses="Omosh wa mtura",
police_abstract_obtained="Yes",
submitted_by=self.user,
# update status to lost
status.current_status = constants.LOST
status.save()

transition_state = apps.get_model("core", "StateTransition").objects.get(
asset_incident_report=self.incident_report
)
# state_transition_count = StateTransition.objects.count()
notifications_count = self.Notifications.objects.filter(
target=self.user
).count()
transition_state = StateTransition.objects.get(asset_incident_report=incident)
transition_state.incident_report_state = constants.INTERNAL_ASSESSMENT
transition_state.save()
new_notification = self.Notifications.objects.filter(target=self.user).latest(
'created_at'

self.assertEqual(transition_state.incident_report_state, constants.CLOSED)

def test_update_asset_status_from_allocated_to_damaged(self):
status = (
apps.get_model("core", "AssetStatus")
.objects.filter(asset=self.asset)
.latest('created_at')
)

self.assertEqual(
self.Notifications.objects.filter(target=incident.submitted_by).count(),
notifications_count + 1,
# allocate asset to a user
apps.get_model("core", "AllocationHistory").objects.create(
asset=self.asset, current_assignee=self.asset_assignee2
)
self.assertEqual(
new_notification.title,
constants.INCIDENT_REPORT_STATUS_UPDATED_NOTIFICATION_TITLE,

# update status to DAMAGED
status.current_status = constants.DAMAGED
status.save()

transition_state = apps.get_model("core", "StateTransition").objects.get(
asset_incident_report=self.incident_report
)

self.assertEqual(transition_state.incident_report_state, constants.CLOSED)

def test_update_asset_status_from_allocated_to_available(self):
status = (
apps.get_model("core", "AssetStatus")
.objects.filter(asset=self.asset)
.latest('created_at')
)
self.assertEqual(status.current_status, constants.AVAILABLE)
# allocate asset to a user
apps.get_model("core", "AllocationHistory").objects.create(
asset=self.asset, current_assignee=self.asset_assignee2
)
status = (
apps.get_model("core", "AssetStatus")
.objects.filter(asset=self.asset)
.latest('created_at')
)
self.assertEqual(status.current_status, constants.ALLOCATED)

# update status to AVAILABLE
status.current_status = constants.AVAILABLE
status.save()

transition_state = apps.get_model("core", "StateTransition").objects.get(
asset_incident_report=self.incident_report
)
self.assertEqual(transition_state.incident_report_state, constants.CLOSED)
4 changes: 2 additions & 2 deletions settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@

SECRET_KEY = config("SECRET_KEY")

DATABASES = {"default": dj_database_url.config()}
# DATABASES = {"default": dj_database_url.config()}
# When starting the application, at least in development if the following error occurs
# """django.core.exceptions.ImproperlyConfigured: settings.DATABASES is improperly configured. Please supply the ENGINE
# value. Check settings documentation for more details. """
# Please uncomment the following instead
# DATABASES = {"default": dj_database_url.config(default=config('DATABASE_URL'))}
DATABASES = {"default": dj_database_url.config(default=config('DATABASE_URL'))}

ALLOWED_HOSTS = config("HOST_IP", cast=Csv())
# Application definition
Expand Down