Skip to content

Commit

Permalink
Merge branch 'master' into LTD-1025-Track-application-amendments
Browse files Browse the repository at this point in the history
  • Loading branch information
saruniitr authored Sep 27, 2021
2 parents 1daa54c + e8bfdd7 commit 3d4c8c9
Show file tree
Hide file tree
Showing 16 changed files with 220 additions and 47 deletions.
24 changes: 24 additions & 0 deletions api/cases/migrations/0050_departmentsla.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 3.1.12 on 2021-09-24 12:39

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


class Migration(migrations.Migration):

dependencies = [
('teams', '0002_auto_20210924_1241'),
('cases', '0049_auto_20210322_1838'),
]

operations = [
migrations.CreateModel(
name='DepartmentSLA',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('sla_days', models.IntegerField()),
('case', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='department_slas', to='cases.case')),
('department', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='department_slas', to='teams.department')),
],
),
]
8 changes: 7 additions & 1 deletion api/cases/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from api.staticdata.statuses.enums import CaseStatusEnum
from api.staticdata.statuses.libraries.get_case_status import get_case_status_by_status
from api.staticdata.statuses.models import CaseStatus
from api.teams.models import Team
from api.teams.models import Team, Department
from api.users.models import (
BaseUser,
ExporterUser,
Expand Down Expand Up @@ -249,6 +249,12 @@ class CaseAssignmentSla(models.Model):
case = models.ForeignKey(Case, related_name="slas", on_delete=models.CASCADE)


class DepartmentSLA(models.Model):
sla_days = models.IntegerField()
department = models.ForeignKey(Department, on_delete=models.CASCADE, related_name="department_slas")
case = models.ForeignKey(Case, on_delete=models.CASCADE, related_name="department_slas")


class CaseReferenceCode(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
reference_number = models.IntegerField()
Expand Down
12 changes: 11 additions & 1 deletion api/cases/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from pytz import timezone as tz

from api.cases.enums import CaseTypeSubTypeEnum
from api.cases.models import Case, CaseAssignmentSla, CaseQueue
from api.cases.models import Case, CaseAssignmentSla, CaseQueue, DepartmentSLA
from api.cases.models import EcjuQuery
from api.common.dates import is_weekend, is_bank_holiday
from api.staticdata.statuses.enums import CaseStatusEnum
Expand Down Expand Up @@ -106,12 +106,22 @@ def update_cases_sla():
)
with transaction.atomic():
for assignment in CaseQueue.objects.filter(case__in=cases):
# Update team SLAs
try:
assignment_sla = CaseAssignmentSla.objects.get(queue=assignment.queue, case=assignment.case)
assignment_sla.sla_days += 1
assignment_sla.save()
except CaseAssignmentSla.DoesNotExist:
CaseAssignmentSla.objects.create(queue=assignment.queue, case=assignment.case, sla_days=1)
# Update department SLAs
department = assignment.queue.team.department
if department is not None:
try:
department_sla = DepartmentSLA.objects.get(department=department, case=assignment.case)
department_sla.sla_days += 1
department_sla.save()
except DepartmentSLA.DoesNotExist:
DepartmentSLA.objects.create(department=department, case=assignment.case, sla_days=1)

results = cases.select_for_update().update(
sla_days=F("sla_days") + 1, sla_remaining_days=F("sla_remaining_days") - 1, sla_updated_at=date,
Expand Down
32 changes: 30 additions & 2 deletions api/cases/tests/test_sla.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from unittest import mock
from unittest.mock import patch

import pytest
from django.conf import settings
from django.urls import reverse
from django.utils import timezone
Expand All @@ -20,9 +19,10 @@
SLA_UPDATE_CUTOFF_TIME,
HMRC_QUERY_TARGET_DAYS,
)
from api.cases.models import CaseAssignmentSla
from api.cases.models import CaseAssignmentSla, CaseQueue, DepartmentSLA
from api.staticdata.statuses.enums import CaseStatusEnum
from api.staticdata.statuses.libraries.get_case_status import get_case_status_by_status
from api.teams.models import Department
from test_helpers.clients import DataTestClient

HOUR_BEFORE_CUTOFF = time(SLA_UPDATE_CUTOFF_TIME.hour - 1, 0, 0)
Expand Down Expand Up @@ -572,3 +572,31 @@ def test_sla_not_update_for_terminal(
self.assertEqual(results, 0)
self.assertEqual(case.sla_days, 1)
self.assertEqual(case.sla_remaining_days, STANDARD_APPLICATION_TARGET_DAYS - 1)


class DepartmentSlaTests(DataTestClient):
@mock.patch("api.cases.tasks.is_weekend")
@mock.patch("api.cases.tasks.is_bank_holiday")
def test_department_sla_updated(
self, mock_is_weekend, mock_is_bank_holiday,
):
# The following is to ensure that this test doesn't fail on
# non-working days.
mock_is_weekend.return_value = False
mock_is_bank_holiday.return_value = False
# Create & submit an application
application = self.create_draft_standard_application(self.organisation)
case = self.submit_application(application)
_set_submitted_at(case, HOUR_BEFORE_CUTOFF)
# Assign the application to our team
CaseQueue.objects.create(case=case, queue=self.queue)
# Create a test department
test_department = Department(name="test")
test_department.save()
# In order to move the department SLA counter, we need to assign
# our team to a department
self.team.department = test_department
self.team.save()
update_cases_sla.now()
department_sla = DepartmentSLA.objects.get(department=test_department)
self.assertEqual(department_sla.sla_days, 1)
13 changes: 7 additions & 6 deletions api/compliance/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from typing import Optional

from django.utils import timezone
from django.conf import settings

from rest_framework.exceptions import ValidationError

from api.audit_trail.enums import AuditType
Expand Down Expand Up @@ -38,15 +40,14 @@ def get_compliance_site_case(pk):

def case_meets_conditions_for_compliance(case: Case):
if case.case_type.id == CaseTypeEnum.SIEL.id:
if not (
Good.objects.filter(
if settings.FEATURE_SIEL_COMPLIANCE_ENABLED:
if Good.objects.filter(
goods_on_application__application_id=case.id,
control_list_entries__rating__regex=COMPLIANCE_CASE_ACCEPTABLE_GOOD_CONTROL_CODES,
goods_on_application__licence__quantity__isnull=False,
).exists()
):
return False
return True
).exists():
return True
return False
elif case.case_type.id in [CaseTypeEnum.OIEL.id, CaseTypeEnum.OICL.id, *CaseTypeEnum.OPEN_GENERAL_LICENCE_IDS]:
return True
else:
Expand Down
22 changes: 20 additions & 2 deletions api/compliance/tests/test_compliance_creation.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from django.test import override_settings

from parameterized import parameterized

from api.applications.models import SiteOnApplication, GoodOnApplication
from api.applications.models import SiteOnApplication
from api.applications.tests.factories import GoodOnApplicationFactory
from api.cases.enums import CaseTypeEnum
from api.cases.models import CaseType
Expand All @@ -10,7 +12,6 @@
from api.licences.tests.factories import LicenceFactory, GoodOnLicenceFactory
from api.open_general_licences.tests.factories import OpenGeneralLicenceCaseFactory, OpenGeneralLicenceFactory
from api.organisations.tests.factories import SiteFactory
from api.staticdata.control_list_entries.factories import ControlListEntriesFactory
from test_helpers.clients import DataTestClient


Expand Down Expand Up @@ -55,6 +56,7 @@ def tests_siel_bad_control_code(self):
@parameterized.expand(
[("ML21", True), ("ML22", True), ("ML2", False), ("0D", True),]
)
@override_settings(FEATURE_SIEL_COMPLIANCE_ENABLED=True)
def tests_siel_good_control_code(self, control_code, exists):
case = self.create_standard_application_case(self.organisation)

Expand All @@ -72,6 +74,22 @@ def tests_siel_good_control_code(self, control_code, exists):

self.assertEqual(ComplianceSiteCase.objects.exists(), exists)

@override_settings(FEATURE_SIEL_COMPLIANCE_ENABLED=False)
def tests_siel_no_compliance_feature_flag_off(self):
case = self.create_standard_application_case(self.organisation)

good = GoodFactory(organisation=self.organisation, is_good_controlled=True, control_list_entries=["ML22"],)
GoodOnLicenceFactory(
good=GoodOnApplicationFactory(application=case, good=good),
licence=LicenceFactory(case=case),
quantity=100,
value=1,
)

generate_compliance_site_case(case)

self.assertEqual(ComplianceSiteCase.objects.exists(), False)

def test_multiple_cases_creates_one_compliance_case(self):
# Both cases uses the organisation primary site by default on creation
case = self.create_open_application_case(self.organisation)
Expand Down
4 changes: 4 additions & 0 deletions api/conf/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,3 +426,7 @@
LITE_OPS_EMAIL = env.str("LITE_OPS_EMAIL")
LITE_REPORTS_RECIPIENTS = env.list("LITE_REPORTS_RECIPIENTS", default=[])
LITE_REPORTS_EMAIL_TEMPLATE_ID = env.str("LITE_REPORTS_EMAIL_TEMPLATE_ID", default="reports-email-template_id")

# Controls whether a ComplianceSiteCase is automatically created when a SIEL licence is issued that has goods with certain control codes
# See LTD-1159
FEATURE_SIEL_COMPLIANCE_ENABLED = env.bool("FEATURE_SIEL_COMPLIANCE_ENABLED", False)
2 changes: 1 addition & 1 deletion api/data_workspace/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def test_queues(self):

def test_teams(self):
url = reverse("data_workspace:dw-teams-list")
expected_fields = ("id", "name", "part_of_ecju")
expected_fields = ("id", "name", "part_of_ecju", "is_ogd")
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
results = response.json()["results"]
Expand Down
35 changes: 12 additions & 23 deletions api/flags/tests/test_create_flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,14 @@ def test_gov_user_can_create_flags(self):
self.assertEqual(response_data["colour"], FlagColours.ORANGE)
self.assertEqual(response_data["label"], "This is label")
self.assertEqual(
response_data["team"], {"id": str(self.team.id), "name": self.team.name, "part_of_ecju": None},
response_data["team"],
{
"id": str(self.team.id),
"name": self.team.name,
"part_of_ecju": None,
"department": None,
"is_ogd": False,
},
)

@parameterized.expand(
Expand All @@ -49,11 +56,7 @@ def test_create_flag_failure(self, name):
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

def test_cannot_set_priority_to_less_than_0(self):
data = {
"name": "new flag",
"level": "Organisation",
"priority": -1,
}
data = {"name": "new flag", "level": "Organisation", "priority": -1}

response = self.client.post(self.url, data, **self.gov_headers)
response_data = response.json()
Expand All @@ -62,11 +65,7 @@ def test_cannot_set_priority_to_less_than_0(self):
self.assertIn(strings.Flags.ValidationErrors.PRIORITY_NEGATIVE, response_data["errors"]["priority"])

def test_cannot_set_priority_to_greater_than_100(self):
data = {
"name": "new flag",
"level": "Organisation",
"priority": 101,
}
data = {"name": "new flag", "level": "Organisation", "priority": 101}

response = self.client.post(self.url, data, **self.gov_headers)
response_data = response.json()
Expand All @@ -75,25 +74,15 @@ def test_cannot_set_priority_to_greater_than_100(self):
self.assertIn(strings.Flags.ValidationErrors.PRIORITY_TOO_LARGE, response_data["errors"]["priority"])

def test_cannot_create_flag_with_colour_and_no_label(self):
data = {
"name": "new flag",
"level": "Organisation",
"colour": FlagColours.ORANGE,
"label": "",
}
data = {"name": "new flag", "level": "Organisation", "colour": FlagColours.ORANGE, "label": ""}

response = self.client.post(self.url, data, **self.gov_headers)

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn(strings.Flags.ValidationErrors.LABEL_MISSING, response.json()["errors"]["label"])

def test_cannot_create_flag_without_blocks_finalising(self):
data = {
"name": "new flag",
"level": "Organisation",
"colour": FlagColours.ORANGE,
"label": "This is label",
}
data = {"name": "new flag", "level": "Organisation", "colour": FlagColours.ORANGE, "label": "This is label"}

response = self.client.post(self.url, data, **self.gov_headers)

Expand Down
27 changes: 27 additions & 0 deletions api/teams/migrations/0002_auto_20210924_1241.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by Django 3.1.12 on 2021-09-24 11:41

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


class Migration(migrations.Migration):

dependencies = [
('teams', '0001_squashed_0003_auto_20210325_0812'),
]

operations = [
migrations.CreateModel(
name='Department',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('name', models.TextField(unique=True)),
],
),
migrations.AddField(
model_name='team',
name='department',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='teams', to='teams.department'),
),
]
23 changes: 23 additions & 0 deletions api/teams/migrations/0003_auto_20210927_1429.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 3.1.12 on 2021-09-27 13:29

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [("teams", "0002_auto_20210924_1241")]

operations = [
migrations.AddField(
model_name="team",
name="is_ogd",
field=models.BooleanField(default=False, help_text="Whether the team is an Other Government Department"),
),
migrations.AlterField(
model_name="team",
name="part_of_ecju",
field=models.BooleanField(
default=None, help_text="Whether the team is part of Export Control Joint Unit", null=True
),
),
]
14 changes: 13 additions & 1 deletion api/teams/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
from django.db import models


class Department(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.TextField(unique=True)


class TeamManager(models.Manager):
def get_by_natural_key(self, name):
return self.get(name=name)
Expand All @@ -11,7 +16,14 @@ def get_by_natural_key(self, name):
class Team(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.TextField(default=None, unique=True)
part_of_ecju = models.BooleanField(default=None, null=True)
department = models.ForeignKey(
Department, null=True, blank=True, default=None, on_delete=models.SET_NULL, related_name="teams"
)
part_of_ecju = models.BooleanField(
default=None, null=True, help_text="Whether the team is part of Export Control Joint Unit"
)
# Note that certain teams can be OGDs *and* be part of ECJU, for example: FCDO
is_ogd = models.BooleanField(default=False, help_text="Whether the team is an Other Government Department")

objects = TeamManager()

Expand Down
Loading

0 comments on commit 3d4c8c9

Please sign in to comment.