Skip to content

Commit

Permalink
feat: add a command for opening cases in ahjo (#2724)
Browse files Browse the repository at this point in the history
* feat: add a command for opening cases in ahjo

* fix: refactored delete request and tests

* feat: add applications with ahjo status in seeder

* fix: failing test, line length, error handling
  • Loading branch information
rikuke authored Jan 31, 2024
1 parent 40cb66f commit 776fbff
Show file tree
Hide file tree
Showing 7 changed files with 319 additions and 47 deletions.
110 changes: 110 additions & 0 deletions backend/benefit/applications/management/commands/open_cases_in_ahjo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import logging
import time
from typing import List

from django.core.exceptions import ImproperlyConfigured
from django.core.management.base import BaseCommand

from applications.enums import AhjoStatus as AhjoStatusEnum
from applications.models import Application
from applications.services.ahjo_authentication import AhjoToken
from applications.services.ahjo_integration import (
create_status_for_application,
get_applications_for_open_case,
get_token,
send_open_case_request_to_ahjo,
)

LOGGER = logging.getLogger(__name__)


class Command(BaseCommand):
help = "Send a request to Ahjo to open cases for applications"

def add_arguments(self, parser):
parser.add_argument(
"--number",
type=int,
default=50,
help="Number of applications to send open case requests for",
)

parser.add_argument(
"--dry-run",
action="store_true",
help="Run the command without making actual changes",
)

def handle(self, *args, **options):
try:
ahjo_auth_token = get_token()
except ImproperlyConfigured as e:
LOGGER.error(f"Failed to get auth token from Ahjo: {e}")
return

number_to_process = options["number"]
dry_run = options["dry_run"]

applications = get_applications_for_open_case()

if not applications:
self.stdout.write("No applications to process")
return

applications = applications[:number_to_process]

if dry_run:
self.stdout.write(
f"Would send open case requests for {len(applications)} applications to Ahjo"
)
return

self.run_requests(applications[:number_to_process], ahjo_auth_token)

def run_requests(self, applications: List[Application], ahjo_auth_token: AhjoToken):
start_time = time.time()
successful_applications = []

self.stdout.write(
f"Sending request to Ahjo to open cases for {len(applications)} applications"
)

for application in applications:
if not application.calculation.handler.ad_username:
raise ImproperlyConfigured(
f"No ad_username set for the handler for Ahjo open case request for application {application.id}."
)
sent_application, response_text = send_open_case_request_to_ahjo(
application, ahjo_auth_token.access_token
)
if sent_application:
successful_applications.append(sent_application)
self._handle_succesfully_opened_application(
sent_application, response_text
)

self.stdout.write(
f"Sent open case requests for {len(successful_applications)} applications to Ahjo"
)
end_time = time.time()
elapsed_time = end_time - start_time
self.stdout.write(
f"Submitting {len(successful_applications)} open case requests took {elapsed_time} seconds to run."
)

def _handle_succesfully_opened_application(
self, application: Application, response_text: str
):
"""Create Ahjo status for application and set Ahjo case guid"""
create_status_for_application(
application, AhjoStatusEnum.REQUEST_TO_OPEN_CASE_SENT
)
# The guid is returned in the response text in text format {guid}, so remove brackets here
response_text = response_text.replace("{", "").replace("}", "")
application.ahjo_case_guid = response_text
application.save()

self.stdout.write(
f"Successfully submitted open case request for application {application.id} to Ahjo, \
received GUID: {response_text}"
)
24 changes: 22 additions & 2 deletions backend/benefit/applications/management/commands/seed.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,17 @@
from django.utils import timezone

from applications.enums import (
AhjoStatus as AhjoStatusEnum,
ApplicationBatchStatus,
ApplicationOrigin,
ApplicationStatus,
)
from applications.models import Application, ApplicationBasis, ApplicationBatch
from applications.models import (
AhjoStatus,
Application,
ApplicationBasis,
ApplicationBatch,
)
from applications.tests.factories import (
AdditionalInformationNeededApplicationFactory,
ApplicationBatchFactory,
Expand Down Expand Up @@ -65,6 +71,7 @@ def clear_applications():
ApplicationBatch.objects.all().delete()

ApplicationBasis.objects.all().delete()
AhjoStatus.objects.all().delete()
Terms.objects.all().delete()
User.objects.filter(last_login=None).exclude(username="admin").delete()

Expand All @@ -87,7 +94,13 @@ def _create_batch(
apps = []
for _ in range(number):
if proposal_for_decision == ApplicationStatus.ACCEPTED:
apps.append(DecidedApplicationFactory())
app = DecidedApplicationFactory()
apps.append(app)
AhjoStatus.objects.create(
status=AhjoStatusEnum.SUBMITTED_BUT_NOT_SENT_TO_AHJO,
application=app,
)

elif proposal_for_decision == ApplicationStatus.REJECTED:
apps.append(RejectedApplicationFactory())
batch.applications.set(apps)
Expand All @@ -114,6 +127,13 @@ def _create_batch(
random_datetime = f.past_datetime(tzinfo=pytz.UTC)
application = factory(application_origin=application_origin)
application.created_at = random_datetime

if factory == HandlingApplicationFactory:
AhjoStatus.objects.create(
status=AhjoStatusEnum.SUBMITTED_BUT_NOT_SENT_TO_AHJO,
application=application,
)

application.save()

application.log_entries.all().update(created_at=random_datetime)
Expand Down
88 changes: 57 additions & 31 deletions backend/benefit/applications/services/ahjo_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
from collections import defaultdict
from dataclasses import dataclass
from io import BytesIO
from typing import List, Optional
from typing import List, Optional, Tuple, Union

import jinja2
import pdfkit
import requests
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
from django.core.files.base import ContentFile
from django.db.models import QuerySet
from django.db.models import F, OuterRef, QuerySet, Subquery
from django.urls import reverse

from applications.enums import (
Expand All @@ -24,7 +24,7 @@
AttachmentType,
)
from applications.models import AhjoSetting, AhjoStatus, Application, Attachment
from applications.services.ahjo_authentication import AhjoConnector
from applications.services.ahjo_authentication import AhjoConnector, AhjoToken
from applications.services.ahjo_payload import prepare_open_case_payload
from applications.services.applications_csv_report import ApplicationsCsvService
from applications.services.generate_application_summary import (
Expand Down Expand Up @@ -386,25 +386,19 @@ def generate_pdf_summary_as_attachment(application: Application) -> Attachment:
return attachment


def get_token() -> str:
def get_token() -> Union[AhjoToken, None]:
"""Get the access token from Ahjo Service."""
try:
ahjo_auth_code = AhjoSetting.objects.get(name="ahjo_code").data
LOGGER.info(f"Retrieved auth code: {ahjo_auth_code}")
connector = AhjoConnector()

if not connector.is_configured():
LOGGER.warning("AHJO connector is not configured")
return
return connector.get_access_token(ahjo_auth_code["code"])
except ObjectDoesNotExist:
LOGGER.error(
except AhjoSetting.DoesNotExist:
raise ImproperlyConfigured(
"Error: Ahjo auth code not found in database. Please set the 'ahjo_code' setting."
)
return
except Exception as e:
LOGGER.warning(f"Error retrieving access token: {e}")
return
connector = AhjoConnector()

if not connector.is_configured():
raise ImproperlyConfigured("AHJO connector is not configured")
return connector.get_access_token(ahjo_auth_code["code"])


def prepare_headers(
Expand Down Expand Up @@ -445,26 +439,46 @@ def create_status_for_application(application: Application, status: AhjoStatusEn
AhjoStatus.objects.create(application=application, status=status)


def get_applications_for_open_case() -> QuerySet[Application]:
"""Query applications which have their latest AhjoStatus relation as SUBMITTED_BUT_NOT_SENT_TO_AHJO
and are in the HANDLING state, so they will have a handler."""
latest_ahjo_status_subquery = AhjoStatus.objects.filter(
application=OuterRef("pk")
).order_by("-created_at")

applications = (
Application.objects.annotate(
latest_ahjo_status_id=Subquery(latest_ahjo_status_subquery.values("id")[:1])
)
.filter(
status=ApplicationStatus.HANDLING,
ahjo_status__id=F("latest_ahjo_status_id"),
ahjo_status__status=AhjoStatusEnum.SUBMITTED_BUT_NOT_SENT_TO_AHJO,
)
.prefetch_related("attachments", "calculation", "company")
)

return applications


def send_request_to_ahjo(
request_type: AhjoRequestType,
headers: dict,
application: Application,
data: dict = {},
timeout: int = 10,
):
) -> Union[Tuple[Application, str], None]:
"""Send a request to Ahjo."""
headers["Content-Type"] = "application/json"

url_base = f"{settings.AHJO_REST_API_URL}/cases"

if request_type == AhjoRequestType.OPEN_CASE:
method = "POST"
status = AhjoStatusEnum.REQUEST_TO_OPEN_CASE_SENT
api_url = url_base
data = json.dumps(data)
elif request_type == AhjoRequestType.DELETE_APPLICATION:
method = "DELETE"
status = AhjoStatusEnum.DELETE_REQUEST_SENT
api_url = f"{url_base}/{application.ahjo_case_id}"

try:
Expand All @@ -474,39 +488,43 @@ def send_request_to_ahjo(
response.raise_for_status()

if response.ok:
create_status_for_application(application, status)
LOGGER.info(
f"Request for application {application.id} to Ahjo was successful."
)
return application, response.text

except requests.exceptions.HTTPError as e:
LOGGER.error(
f"HTTP error occurred while sending a {request_type} request to Ahjo: {e}"
f"HTTP error occurred while sending a {request_type} request for application {application.id} to Ahjo: {e}"
)
raise
except requests.exceptions.RequestException as e:
LOGGER.error(
f"Network error occurred while sending a {request_type} request to Ahjo: {e}"
f"HTTP error occurred while sending a {request_type} request for application {application.id} to Ahjo: {e}"
)
raise
except Exception as e:
LOGGER.error(
f"Error occurred while sending request a {request_type} to Ahjo: {e}"
f"HTTP error occurred while sending a {request_type} request for application {application.id} to Ahjo: {e}"
)
raise


def open_case_in_ahjo(application_id: uuid.UUID):
def send_open_case_request_to_ahjo(
application: Application, ahjo_auth_token: str
) -> Union[Tuple[Application, str], None]:
"""Open a case in Ahjo."""
try:
application = get_application_for_ahjo(application_id)
ahjo_token = get_token()
headers = prepare_headers(
ahjo_token.access_token, application.id, AhjoRequestType.OPEN_CASE
ahjo_auth_token, application, AhjoRequestType.OPEN_CASE
)

pdf_summary = generate_pdf_summary_as_attachment(application)
data = prepare_open_case_payload(application, pdf_summary)
send_request_to_ahjo(AhjoRequestType.OPEN_CASE, headers, data, application)

return send_request_to_ahjo(
AhjoRequestType.OPEN_CASE, headers, application, data
)
except ObjectDoesNotExist as e:
LOGGER.error(f"Object not found: {e}")
except ImproperlyConfigured as e:
Expand All @@ -518,10 +536,18 @@ def delete_application_in_ahjo(application_id: uuid.UUID):
try:
application = get_application_for_ahjo(application_id)
ahjo_token = get_token()
token = ahjo_token.access_token

headers = prepare_headers(
ahjo_token.access_token, application.id, AhjoRequestType.DELETE_APPLICATION
token, application, AhjoRequestType.DELETE_APPLICATION
)
send_request_to_ahjo(AhjoRequestType.DELETE_APPLICATION, headers, application)
application, _ = send_request_to_ahjo(
AhjoRequestType.DELETE_APPLICATION, headers, application
)
if application:
create_status_for_application(
application, AhjoStatusEnum.DELETE_REQUEST_SENT
)
except ObjectDoesNotExist as e:
LOGGER.error(f"Object not found: {e}")
except ImproperlyConfigured as e:
Expand Down
16 changes: 16 additions & 0 deletions backend/benefit/applications/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,22 @@ def decided_application(mock_get_organisation_roles_and_create_company):
)


@pytest.fixture
def multiple_decided_applications(mock_get_organisation_roles_and_create_company):
with factory.Faker.override_default_locale("fi_FI"):
return DecidedApplicationFactory.create_batch(
5, company=mock_get_organisation_roles_and_create_company
)


@pytest.fixture
def multiple_handling_applications(mock_get_organisation_roles_and_create_company):
with factory.Faker.override_default_locale("fi_FI"):
return HandlingApplicationFactory.create_batch(
5, company=mock_get_organisation_roles_and_create_company
)


@pytest.fixture
def application_batch():
with factory.Faker.override_default_locale("fi_FI"):
Expand Down
Loading

0 comments on commit 776fbff

Please sign in to comment.