Skip to content

Commit

Permalink
feat: list old processed applications on the archive page (HL-1011)
Browse files Browse the repository at this point in the history
  • Loading branch information
EmiliaMakelaVincit committed Feb 12, 2024
1 parent 0c9626f commit 72ed278
Show file tree
Hide file tree
Showing 24 changed files with 1,912 additions and 642 deletions.
28 changes: 26 additions & 2 deletions backend/benefit/applications/api/v1/serializers/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from applications.benefit_aggregation import get_former_benefit_info
from applications.enums import (
ApplicationActions,
ApplicationBatchStatus,
ApplicationOrigin,
ApplicationStatus,
AttachmentRequirement,
Expand Down Expand Up @@ -187,6 +188,7 @@ class Meta:
"total_deminimis_amount",
"ahjo_status",
"changes",
"archived_for_applicant",
]
read_only_fields = [
"submitted_at",
Expand All @@ -211,6 +213,7 @@ class Meta:
"duration_in_months_rounded",
"total_deminimis_amount",
"changes",
"archived_for_applicant",
]
extra_kwargs = {
"company_name": {
Expand Down Expand Up @@ -436,6 +439,11 @@ class Meta:
),
)

archived_for_applicant = serializers.SerializerMethodField(
method_name="get_archived_for_applicant",
help_text=("Should be shown in the archive view for an applicant"),
)

def get_applicant_terms_approval_needed(self, obj):
return ApplicantTermsApproval.terms_approval_needed(obj)

Expand Down Expand Up @@ -1341,6 +1349,24 @@ def get_logged_in_user_company(self):
return Company.objects.all().order_by("name").first()
return get_company_from_request(self.context.get("request"))

def get_archived_for_applicant(self, application):
"""
Determine if the application is old enough and already handled so that it will
be shown in the archive section for the applicant.
Make sure any changes here are reflected in the filter set as well.
"""

if application.batch is None:
return False

return application.batch.status in [
ApplicationBatchStatus.DECIDED_ACCEPTED,
ApplicationBatchStatus.DECIDED_REJECTED,
ApplicationBatchStatus.SENT_TO_TALPA,
ApplicationBatchStatus.COMPLETED,
] and application.batch.decision_date < date.today() + relativedelta(days=-14)


class ApplicantApplicationStatusChoiceField(serializers.ChoiceField):
"""
Expand All @@ -1349,8 +1375,6 @@ class ApplicantApplicationStatusChoiceField(serializers.ChoiceField):

STATUS_OVERRIDES = {
ApplicationStatus.RECEIVED: ApplicationStatus.HANDLING,
ApplicationStatus.ACCEPTED: ApplicationStatus.HANDLING,
ApplicationStatus.REJECTED: ApplicationStatus.HANDLING,
}

def to_representation(self, value):
Expand Down
56 changes: 54 additions & 2 deletions backend/benefit/applications/api/v1/views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import re
from datetime import date
from typing import List

from dateutil.relativedelta import relativedelta
from django.conf import settings
from django.core import exceptions
from django.db import transaction
Expand Down Expand Up @@ -61,6 +63,42 @@ class BaseApplicationFilter(filters.FilterSet):
" comma-separated list, such as 'status=draft,received'",
),
)
archived_for_applicant = filters.BooleanFilter(
method="_get_archived_for_applicant",
label=_("Displayed in the archive in the applicant view"),
)

def _get_archived_for_applicant(self, queryset, name, value: bool):
"""
Determine if the application is old enough and already handled so that it will
be shown in the archive section for the applicant.
Make sure any changes here are reflected in the serializer as well.
"""

application_statuses = [
ApplicationStatus.REJECTED,
ApplicationStatus.ACCEPTED,
]
batch_statuses = [
ApplicationBatchStatus.DECIDED_ACCEPTED,
ApplicationBatchStatus.DECIDED_REJECTED,
ApplicationBatchStatus.SENT_TO_TALPA,
ApplicationBatchStatus.COMPLETED,
]
archive_threshold = date.today() + relativedelta(days=-14)

query = {
"status__in": application_statuses,
"batch__isnull": False,
"batch__status__in": batch_statuses,
"batch__decision_date__lte": archive_threshold.isoformat(),
}

if value:
return queryset.filter(**query)
else:
return queryset.filter(~Q(**query))


class ApplicantApplicationFilter(BaseApplicationFilter):
Expand Down Expand Up @@ -161,7 +199,19 @@ def simplified_application_list(self, request):
context = self.get_serializer_context()
qs = self._get_simplified_queryset(request, context)
serializer = self.serializer_class(qs, many=True, context=context)
return Response(serializer.data, status=status.HTTP_200_OK)
data = serializer.data

# Sorting by encrypted fields has to be done after the data has been retrieved and decrypted
if request.query_params.get("order_by") in ["employee_name"]:
data = sorted(
data,
key=lambda item: (
item["employee"]["last_name"].lower(),
item["employee"]["first_name"].lower(),
),
)

return Response(data, status=status.HTTP_200_OK)

@action(
methods=("POST",),
Expand Down Expand Up @@ -214,7 +264,9 @@ def _get_simplified_queryset(self, request, context) -> QuerySet:
if exclude_batched:
qs = qs.filter(batch__isnull=True)

qs = qs.filter(archived=request.query_params.get("filter_archived") == "1")
user = self.request.user
if hasattr(user, "is_handler") and user.is_handler():
qs = qs.filter(archived=request.query_params.get("filter_archived") == "1")

return qs

Expand Down
9 changes: 9 additions & 0 deletions backend/benefit/applications/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ class ApplicationManager(models.Manager):
ApplicationStatus.CANCELLED,
]

COMPLETED_BATCH_STATUSES = [
ApplicationBatchStatus.DECIDED_ACCEPTED,
ApplicationBatchStatus.DECIDED_REJECTED,
ApplicationBatchStatus.SENT_TO_TALPA,
ApplicationBatchStatus.COMPLETED,
]

ARCHIVE_THRESHOLD = relativedelta(days=-14)

def _annotate_with_log_timestamp(self, qs, field_name, to_statuses):
subquery = (
ApplicationLogEntry.objects.filter(
Expand Down
2 changes: 2 additions & 0 deletions backend/benefit/applications/services/change_history.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ def _get_application_change_history_between_timestamps(
hist_employee_when_stop_editing = employee.history.as_of(ts_end)._history
except Employee.DoesNotExist:
return []
except Application.DoesNotExist:
return []

new_or_edited_attachments = Attachment.objects.filter(
Q(created_at__gte=ts_start) | Q(modified_at=ts_start),
Expand Down
66 changes: 64 additions & 2 deletions backend/benefit/applications/tests/test_applications_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import faker
import pytest
from dateutil.relativedelta import relativedelta
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import override_settings
from freezegun import freeze_time
Expand All @@ -26,6 +27,7 @@
from applications.api.v1.views import BaseApplicationViewSet
from applications.enums import (
AhjoStatus,
ApplicationBatchStatus,
ApplicationStatus,
ApplicationStep,
AttachmentType,
Expand Down Expand Up @@ -261,6 +263,66 @@ def test_applications_simple_list_filter(
assert response.status_code == 200


def test_applications_filter_archived_for_applicant(
api_client, mock_get_organisation_roles_and_create_company
):
recent_batch = ApplicationBatchFactory(
decision_date=date.today() - relativedelta(days=2),
status=ApplicationBatchStatus.COMPLETED,
)
old_batch = ApplicationBatchFactory(
decision_date=date.today() - relativedelta(days=15),
status=ApplicationBatchStatus.COMPLETED,
)
pending_batch = ApplicationBatchFactory(
decision_date=date.today() - relativedelta(days=15),
status=ApplicationBatchStatus.AWAITING_AHJO_DECISION,
)
company = mock_get_organisation_roles_and_create_company

apps = [
# Decided but not yet in a batch, should appear on main page
DecidedApplicationFactory(application_number=123450),
# Batch processed and decided 15 days ago, should appear on archive page
DecidedApplicationFactory(application_number=123451, batch=old_batch),
# Batch processed and decided 15 days ago and archived for handlers, should still appear on archive page
DecidedApplicationFactory(
application_number=123452, batch=old_batch, archived=True
),
# Batch processed and decided two days ago, should appear on main page
DecidedApplicationFactory(application_number=123453, batch=recent_batch),
# Batch decided 15 days ago but not yet fully processed, should appear on main page
DecidedApplicationFactory(application_number=123454, batch=pending_batch),
# Fresh application not yet decided, should appear on main page
ReceivedApplicationFactory(application_number=123455),
]

for app in apps:
app.company = company
app.save()

response = api_client.get(
reverse("v1:applicant-application-simplified-application-list"),
{"archived_for_applicant": "true", "order_by": "application_number"},
)

assert len(response.data) == 2
assert response.data[0]["id"] == str(apps[1].id)
assert response.data[1]["id"] == str(apps[2].id)
assert response.data[0]["archived_for_applicant"]
assert response.data[1]["archived_for_applicant"]

response = api_client.get(
reverse("v1:applicant-application-simplified-application-list"),
{"archived_for_applicant": "false", "order_by": "application_number"},
)
assert len(response.data) == 4
assert response.data[0]["id"] == str(apps[0].id)
assert response.data[1]["id"] == str(apps[3].id)
assert response.data[2]["id"] == str(apps[4].id)
assert response.data[3]["id"] == str(apps[5].id)


@pytest.mark.parametrize("url_func", [get_detail_url, get_handler_detail_url])
def test_application_single_read_unauthenticated(
anonymous_client, application, url_func
Expand All @@ -286,8 +348,8 @@ def test_application_single_read_unauthorized(
),
(ApplicationStatus.RECEIVED, ApplicationStatus.HANDLING),
(ApplicationStatus.HANDLING, ApplicationStatus.HANDLING),
(ApplicationStatus.ACCEPTED, ApplicationStatus.HANDLING),
(ApplicationStatus.REJECTED, ApplicationStatus.HANDLING),
(ApplicationStatus.ACCEPTED, ApplicationStatus.ACCEPTED),
(ApplicationStatus.REJECTED, ApplicationStatus.REJECTED),
(ApplicationStatus.CANCELLED, ApplicationStatus.CANCELLED),
],
)
Expand Down
Loading

0 comments on commit 72ed278

Please sign in to comment.