Skip to content

Commit

Permalink
Hl 1495 talpa instalment (#3524)
Browse files Browse the repository at this point in the history
* chore: refactor talpaCSV columns into own class

* feat: query for due instalments

* feat: use instalment.amount in talpa CSV

* feat: dont update statuses after talpa download

* feat: use instalments for talpa csv

* feat: update instalments in talpa CB
  • Loading branch information
rikuke authored Nov 11, 2024
1 parent af635c0 commit 761c642
Show file tree
Hide file tree
Showing 10 changed files with 388 additions and 184 deletions.
94 changes: 35 additions & 59 deletions backend/benefit/applications/api/v1/application_batch_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from django.utils.translation import gettext_lazy as _
from django_filters import rest_framework as filters
from django_filters.widgets import CSVWidget
from drf_spectacular.utils import extend_schema, OpenApiParameter
from drf_spectacular.utils import extend_schema
from rest_framework import filters as drf_filters, status
from rest_framework.decorators import action
from rest_framework.permissions import AllowAny
Expand All @@ -21,19 +21,16 @@
ApplicationBatchListSerializer,
ApplicationBatchSerializer,
)
from applications.enums import (
ApplicationBatchStatus,
ApplicationStatus,
ApplicationTalpaStatus,
)
from applications.enums import ApplicationBatchStatus, ApplicationStatus
from applications.exceptions import (
BatchCompletionDecisionDateError,
BatchCompletionRequiredFieldsError,
BatchTooManyDraftsError,
)
from applications.models import Application, ApplicationBatch
from applications.services.ahjo_integration import export_application_batch
from applications.services.applications_csv_report import ApplicationsCsvService
from applications.services.talpa_csv_service import TalpaCsvService
from calculator.enums import InstalmentStatus
from common.authentications import RobotBasicAuthentication
from common.permissions import BFIsHandler
from common.utils import get_request_ip_address
Expand Down Expand Up @@ -163,15 +160,6 @@ def export_batch(self, request, *args, **kwargs):
return response

@extend_schema(
parameters=[
OpenApiParameter(
name="skip_update",
description="Skip updating the batch status",
required=False,
type=str,
enum=["0", "1"],
),
],
description="""Get application batches for the TALPA robot.
Set skip_update=1 to skip updating the batch status to SENT_TO_TALPA""",
methods=["GET"],
Expand All @@ -188,15 +176,23 @@ def talpa_export_batch(self, request, *args, **kwargs) -> HttpResponse:
"""
Export ApplicationBatch to CSV format for Talpa Robot
"""
skip_update = (
request.query_params.get("skip_update")
and request.query_params.get("skip_update") == "1"
)
# if instalments feature is enabled, get the applications based on instalments
# TODO Remove this when instalments feature is completed
if settings.PAYMENT_INSTALMENTS_ENABLED:
applications_for_csv = Application.objects.with_due_instalments(
InstalmentStatus.ACCEPTED
)
approved_batches = None
else:
# Else get the applications based on the batch
approved_batches = ApplicationBatch.objects.filter(
status=ApplicationBatchStatus.DECIDED_ACCEPTED
)
applications_for_csv = Application.objects.filter(
batch__in=approved_batches
).order_by("company__name", "application_number")

approved_batches = ApplicationBatch.objects.filter(
status=ApplicationBatchStatus.DECIDED_ACCEPTED
)
if approved_batches.count() == 0:
if applications_for_csv.count() == 0:
return Response(
{
"detail": _(
Expand All @@ -206,10 +202,8 @@ def talpa_export_batch(self, request, *args, **kwargs) -> HttpResponse:
},
status=status.HTTP_404_NOT_FOUND,
)
applications = Application.objects.filter(batch__in=approved_batches).order_by(
"company__name", "application_number"
)
csv_service = ApplicationsCsvService(applications, True)

csv_service = TalpaCsvService(applications_for_csv)
file_name = format_lazy(
_("TALPA export {date}"),
date=timezone.now().strftime("%Y%m%d_%H%M%S"),
Expand All @@ -223,37 +217,19 @@ def talpa_export_batch(self, request, *args, **kwargs) -> HttpResponse:
response["Content-Disposition"] = "attachment; filename={filename}.csv".format(
filename=file_name
)
if settings.TALPA_CALLBACK_ENABLED is False:
# for easier testing in the test environment do not update the batches as sent_to_talpa
# remove this when TALPA integration is ready for production
if not skip_update:
ip_address = get_request_ip_address(request)
try:
# Update all approved batches to SENT_TO_TALPA status in a single query
approved_batches.update(status=ApplicationBatchStatus.SENT_TO_TALPA)
# Update all applications in the approved batches to
# SUCCESSFULLY_SENT_TO_TALPA status and archived=True
for a in applications:
a.talpa_status = (
ApplicationTalpaStatus.SUCCESSFULLY_SENT_TO_TALPA
)
a.archived = True
a.save()

audit_logging.log(
AnonymousUser,
"",
Operation.READ,
a,
ip_address=ip_address,
additional_information="application csv data was downloaded by TALPA\
and it was marked as archived",
)

except Exception as e:
LOGGER.error(
f"An error occurred while updating batches after Talpa csv download: {e}"
)

ip_address = get_request_ip_address(request)

for a in applications_for_csv:
audit_logging.log(
AnonymousUser,
"",
Operation.READ,
a,
ip_address=ip_address,
additional_information="application csv data was downloaded by TALPA robot",
)

return response

@action(methods=["PATCH"], detail=False)
Expand Down
8 changes: 3 additions & 5 deletions backend/benefit/applications/api/v1/application_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -807,7 +807,8 @@ def batch_p2p_file(self, request) -> HttpResponse:
@transaction.atomic
def export_new_accepted_applications_csv_pdf(self, request) -> HttpResponse:
return self._csv_pdf_response(
self._create_application_batch(ApplicationStatus.ACCEPTED), True, True
self._create_application_batch(ApplicationStatus.ACCEPTED),
remove_quotes=True,
)

@action(methods=["GET"], detail=False)
Expand Down Expand Up @@ -877,7 +878,6 @@ def _export_filename_without_suffix():
def _csv_response(
self,
queryset: QuerySet[Application],
prune_data_for_talpa: bool = False,
remove_quotes: bool = False,
prune_sensitive_data: bool = True,
compact_list: bool = False,
Expand All @@ -891,7 +891,6 @@ def _csv_response(
else:
csv_service = ApplicationsCsvService(
queryset.order_by(self.APPLICATION_ORDERING),
prune_data_for_talpa,
prune_sensitive_data,
)
response = StreamingHttpResponse(
Expand All @@ -911,14 +910,13 @@ def _csv_response(
def _csv_pdf_response(
self,
queryset: QuerySet[Application],
prune_data_for_talpa: bool = False,
remove_quotes: bool = False,
) -> HttpResponse:
ordered_queryset = queryset.order_by(self.APPLICATION_ORDERING)
export_filename_without_suffix = self._export_filename_without_suffix()

csv_file = prepare_csv_file(
ordered_queryset, prune_data_for_talpa, export_filename_without_suffix
ordered_queryset, remove_quotes, export_filename_without_suffix
)

pdf_files: List[ExportFileInfo] = prepare_pdf_files(ordered_queryset)
Expand Down
136 changes: 110 additions & 26 deletions backend/benefit/applications/api/v1/talpa_integration_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from django.db import transaction
from django.utils import timezone
from rest_framework import status
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
Expand All @@ -12,6 +13,7 @@
from applications.api.v1.serializers.talpa_callback import TalpaCallbackSerializer
from applications.enums import ApplicationBatchStatus, ApplicationTalpaStatus
from applications.models import Application, ApplicationBatch
from calculator.enums import InstalmentStatus
from common.authentications import RobotBasicAuthentication
from common.utils import get_request_ip_address
from shared.audit_log import audit_logging
Expand Down Expand Up @@ -64,41 +66,81 @@ def _get_applications(self, application_numbers) -> Union[List[Application], Non
return None
return applications

def _get_applications_and_instalments(
self, application_numbers
) -> Union[List[Application], None]:
applications = Application.objects.with_due_instalments(
InstalmentStatus.ACCEPTED
).filter(application_number__in=application_numbers)

if not applications.exists() and application_numbers:
LOGGER.error(
f"No applications found with numbers: {application_numbers} for update after TALPA download"
)
return []
return applications

@transaction.atomic
def _handle_successful_applications(
self, application_numbers: list, ip_address: str
):
applications = self._get_applications(application_numbers)
self.update_application_and_related_batch(
applications,
ip_address,
ApplicationTalpaStatus.SUCCESSFULLY_SENT_TO_TALPA,
ApplicationBatchStatus.SENT_TO_TALPA,
"application was read succesfully by TALPA and archived",
is_archived=True,
)
if settings.PAYMENT_INSTALMENTS_ENABLED:
applications = self._get_applications_and_instalments(application_numbers)

self.do_status_updates_based_on_instalments(
applications=applications,
instalment_status=InstalmentStatus.PAID,
ip_address=ip_address,
log_message="instalment was read by TALPA and marked as paid",
is_success=True,
)
else:
applications = self._get_applications(application_numbers)
self.update_application_and_related_batch(
applications,
ip_address,
ApplicationTalpaStatus.SUCCESSFULLY_SENT_TO_TALPA,
ApplicationBatchStatus.SENT_TO_TALPA,
"application was read succesfully by TALPA and archived",
is_archived=True,
)

@transaction.atomic
def _handle_failed_applications(self, application_numbers: list, ip_address: str):
"""Update applications and related batch which could not be processed with status REJECTED_BY_TALPA"""
applications = self._get_applications(application_numbers)
self.update_application_and_related_batch(
applications,
ip_address,
ApplicationTalpaStatus.REJECTED_BY_TALPA,
ApplicationBatchStatus.REJECTED_BY_TALPA,
"application was rejected by TALPA",
)

@staticmethod
if settings.PAYMENT_INSTALMENTS_ENABLED:
applications = self._get_applications_and_instalments(application_numbers)

self.do_status_updates_based_on_instalments(
applications=applications,
instalment_status=InstalmentStatus.ERROR_IN_TALPA,
ip_address=ip_address,
log_message="there was an error and the instalment was not read by TALPA",
is_success=False,
)
else:
applications = self._get_applications(application_numbers)
self.update_application_and_related_batch(
applications,
ip_address,
ApplicationTalpaStatus.REJECTED_BY_TALPA,
ApplicationBatchStatus.REJECTED_BY_TALPA,
"application was rejected by TALPA",
)

def update_application_and_related_batch(
self,
applications: List[Application],
ip_address: str,
application_talpa_status: ApplicationTalpaStatus,
batch_status: ApplicationBatchStatus,
log_message: str,
is_archived: bool = False,
):
"""Update applications and related batch with given statuses and log the event"""
"""Update applications and related batch with given statuses and log the event.
This will be deprecated after the instalments feature is enabled for all applications.
"""
applications.update(
talpa_status=application_talpa_status,
archived=is_archived,
Expand All @@ -109,11 +151,53 @@ def update_application_and_related_batch(

for application in applications:
"""Add audit log entries for applications which were processed by TALPA"""
audit_logging.log(
AnonymousUser,
"",
Operation.READ,
application,
ip_address=ip_address,
additional_information=log_message,
self.write_to_audit_log(application, ip_address, log_message)

def do_status_updates_based_on_instalments(
self,
applications: List[Application],
instalment_status: InstalmentStatus,
ip_address: str,
log_message: str,
is_success: bool = False,
):
"""
After receiving the callback from Talpa, query the currently due instalments of the
successful applications and update the status of the instalments.
If the instalmentis 1/1 or 2/2, e.g the final instalment,
update the application status, batch status and set the application as archived
"""
for application in applications:
instalment = application.calculation.instalments.get(
status=InstalmentStatus.ACCEPTED,
due_date__lte=timezone.now().date(),
)
instalment.status = instalment_status
instalment.save()

if is_success:
if application.number_of_instalments == 1 or (
application.number_of_instalments == 2
and instalment.instalment_number == 2
):
self.update_after_all_instalments_are_sent(application)

"""Add audit log entries for applications which were processed by TALPA"""
self.write_to_audit_log(application, ip_address, log_message)

def update_after_all_instalments_are_sent(self, application: Application):
application.archived = True
application.talpa_status = ApplicationTalpaStatus.SUCCESSFULLY_SENT_TO_TALPA
application.save()
application.batch.status = ApplicationBatchStatus.SENT_TO_TALPA
application.batch.save()

def write_to_audit_log(self, application, ip_address, log_message):
audit_logging.log(
AnonymousUser,
"",
Operation.READ,
application,
ip_address=ip_address,
additional_information=log_message,
)
Loading

0 comments on commit 761c642

Please sign in to comment.