Skip to content

Commit

Permalink
Merge pull request #1787 from uktrade/LTD-4624_Add_property_open_work…
Browse files Browse the repository at this point in the history
…ing_days

Add property open_working_days
  • Loading branch information
hnryjmes authored Feb 1, 2024
2 parents 17d6eee + 5dfc294 commit aa268d7
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 1 deletion.
8 changes: 8 additions & 0 deletions api/cases/helpers.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from datetime import timedelta

from api.common.dates import is_bank_holiday, is_weekend
from api.cases.enums import CaseTypeReferenceEnum
from api.staticdata.statuses.enums import CaseStatusEnum
from api.users.models import GovUser, GovNotification
Expand Down Expand Up @@ -73,3 +76,8 @@ def can_set_status(case, status):
return False

return True


def working_days_in_range(start_date, end_date):
dates_in_range = [start_date + timedelta(n) for n in range((end_date - start_date).days)]
return len([date for date in dates_in_range if (not is_bank_holiday(date) and not is_weekend(date))])
8 changes: 7 additions & 1 deletion api/cases/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
AdviceLevel,
EnforcementXMLEntityTypes,
)
from api.cases.helpers import working_days_in_range
from api.cases.libraries.reference_code import generate_reference_code
from api.cases.managers import CaseManager, CaseReferenceCodeManager, AdviceManager
from api.common.models import TimestampableModel, CreatedAt
Expand Down Expand Up @@ -627,7 +628,6 @@ def is_query_closed(self):

@queryable_property
def is_manually_closed(self):

if self.responded_by_user and self.responded_by_user.type == UserType.INTERNAL:
return True
else:
Expand All @@ -638,6 +638,12 @@ def is_manually_closed(self):
def is_query_closed(self, lookup, value):
return ~Q(responded_at__isnull=value)

@property
def open_working_days(self):
start_date = self.created_at
end_date = self.responded_at if self.responded_at else timezone.now()
return working_days_in_range(start_date=start_date, end_date=end_date)

notifications = GenericRelation(ExporterNotification, related_query_name="ecju_query")

def save(self, *args, **kwargs):
Expand Down
1 change: 1 addition & 0 deletions api/cases/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,7 @@ class Meta:
"documents",
"is_query_closed",
"is_manually_closed",
"open_working_days",
)

def get_raised_by_user_name(self, instance):
Expand Down
123 changes: 123 additions & 0 deletions api/cases/tests/test_case_ecju_queries.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from unittest import mock
from datetime import timedelta

from django.urls import reverse
from django.utils import timezone
from api.cases.tests.factories import EcjuQueryFactory
from faker import Faker
from parameterized import parameterized
from rest_framework import status
from freezegun import freeze_time

from api.applications.models import BaseApplication
from api.audit_trail.enums import AuditType
Expand Down Expand Up @@ -224,6 +226,127 @@ def test_ecju_query_open_query_count_responded_return_zero(self):
self.assertEqual(status.HTTP_200_OK, response.status_code)
self.assertEqual(0, response_data["count"])

@parameterized.expand(
[
(
{
"year": 2022,
"month": 11,
"day": 30,
"hour": 9,
"minute": 50,
"tzinfo": timezone.utc,
},
291,
),
(
{
"year": 2023,
"month": 12,
"day": 15,
"hour": 13,
"minute": 37,
"tzinfo": timezone.utc,
},
28,
),
(
{
"year": 2024,
"month": 1,
"day": 1,
"hour": 12,
"minute": 30,
"tzinfo": timezone.utc,
},
19,
),
({"year": 2024, "month": 1, "day": 22, "hour": 15, "minute": 40, "tzinfo": timezone.utc}, 5),
]
)
@freeze_time("2024-01-29 15:00:00")
def test_ecju_query_shows_correct_open_working_days_for_open_query(
self, created_at_datetime_kwargs, expected_working_days
):
case = self.create_standard_application_case(self.organisation)
created_at_datetime = timezone.datetime(**created_at_datetime_kwargs)
ecju_query = EcjuQueryFactory(
question="this is the question",
response=None,
responded_at=None,
case=case,
created_at=created_at_datetime,
)

url = reverse("cases:case_ecju_queries", kwargs={"pk": case.id})

response = self.client.get(url, **self.gov_headers)
response_data = response.json()

assert response_data["ecju_queries"][0]["open_working_days"] == expected_working_days

@parameterized.expand(
[
(
{
"year": 2022,
"month": 11,
"day": 30,
"hour": 9,
"minute": 50,
"tzinfo": timezone.utc,
},
365,
252,
),
(
{
"year": 2023,
"month": 12,
"day": 15,
"hour": 13,
"minute": 37,
"tzinfo": timezone.utc,
},
30,
18,
),
({"year": 2024, "month": 1, "day": 22, "hour": 15, "minute": 40, "tzinfo": timezone.utc}, 7, 5),
(
{
"year": 2024,
"month": 1,
"day": 28,
"hour": 12,
"minute": 6,
"tzinfo": timezone.utc,
},
1,
0,
),
],
)
def test_ecju_query_shows_correct_open_working_days_for_closed_query(
self, created_at_datetime_kwargs, calendar_days, expected_working_days
):
case = self.create_standard_application_case(self.organisation)
created_at_datetime = timezone.datetime(**created_at_datetime_kwargs)
responded_at_datetime = created_at_datetime + timedelta(days=calendar_days)
ecju_query = EcjuQueryFactory(
question="this is the question",
response="some response text",
responded_at=responded_at_datetime,
case=case,
created_at=created_at_datetime,
)

url = reverse("cases:case_ecju_queries", kwargs={"pk": case.id})

response = self.client.get(url, **self.gov_headers)
response_data = response.json()

assert response_data["ecju_queries"][0]["open_working_days"] == expected_working_days


class ECJUQueriesCreateTest(DataTestClient):
@parameterized.expand([ECJUQueryType.ECJU, ECJUQueryType.PRE_VISIT_QUESTIONNAIRE, ECJUQueryType.COMPLIANCE_ACTIONS])
Expand Down
53 changes: 53 additions & 0 deletions api/cases/tests/test_helpers.py
Original file line number Diff line number Diff line change
@@ -1 +1,54 @@
# TODO; test notify_ecju_query in total isolation

import datetime
from parameterized import parameterized

from api.cases.helpers import working_days_in_range


@parameterized.expand(
[
(
{
"year": 2022,
"month": 11,
"day": 30,
"hour": 9,
"minute": 50,
"tzinfo": datetime.timezone.utc,
},
365,
252,
),
(
{
"year": 2023,
"month": 12,
"day": 15,
"hour": 13,
"minute": 37,
"tzinfo": datetime.timezone.utc,
},
30,
18,
),
({"year": 2024, "month": 1, "day": 22, "hour": 15, "minute": 40, "tzinfo": datetime.timezone.utc}, 7, 5),
(
{
"year": 2024,
"month": 1,
"day": 28,
"hour": 12,
"minute": 6,
"tzinfo": datetime.timezone.utc,
},
1,
0,
),
],
)
def test_working_days_in_range(created_at_datetime_kwargs, calendar_days, expected_working_days):
start_date = datetime.datetime(**created_at_datetime_kwargs)
end_date = start_date + datetime.timedelta(days=calendar_days)

assert working_days_in_range(start_date, end_date) == expected_working_days
9 changes: 9 additions & 0 deletions api/common/dates.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ def is_weekend(date):


def working_days_in_range(start_date, end_date):
"""
This function does not work as intended and should not be used, please see
LTD-4628 for more information.
This is because of the predicate used in the list
comprehension, which incorrectly returns True early. An example fix is to replace the list comprehension
with `[date for date in dates_in_range if (not is_bank_holiday(date) and not is_weekend(date))]`
but this should be done and released at the same time as the other fixes in LTD-4628.
"""
dates_in_range = [start_date + timedelta(n) for n in range((end_date - start_date).days)]
return len([date for date in dates_in_range if not is_bank_holiday(date) or not is_weekend(date)])

Expand Down

0 comments on commit aa268d7

Please sign in to comment.