Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hl 970 open ahjo case #2519

Merged
merged 11 commits into from
Dec 7, 2023
4 changes: 4 additions & 0 deletions backend/benefit/applications/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,10 @@ def total_deminimis_amount(self):
total += deminimis_aid.amount
return total

@property
def contact_person(self):
return f"{self.company_contact_person_first_name} {self.company_contact_person_last_name}"

def get_log_entry_field(self, to_statuses, field_name):
if (
log_entry := self.log_entries.filter(to_status__in=to_statuses)
Expand Down
15 changes: 7 additions & 8 deletions backend/benefit/applications/services/ahjo_authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import requests
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist

from applications.models import AhjoSetting

Expand All @@ -17,8 +17,7 @@ class AhjoToken:


class AhjoConnector:
def __init__(self, requests_module: requests.Session = requests) -> None:
self.requests_module: requests = requests_module
def __init__(self) -> None:
self.token_url: str = settings.AHJO_TOKEN_URL
self.client_id: str = settings.AHJO_CLIENT_ID
self.client_secret: str = settings.AHJO_CLIENT_SECRET
Expand All @@ -29,7 +28,7 @@ def __init__(self, requests_module: requests.Session = requests) -> None:
self.headers: Dict[str, str] = {
"Content-Type": "application/x-www-form-urlencoded",
}
self.timout: int = 10
self.timeout: int = 10

def is_configured(self) -> bool:
"""Check if all required config options are set"""
Expand All @@ -56,7 +55,7 @@ def get_new_token(self, auth_code: str) -> AhjoToken:
this is only used when getting the initial token or when the token has expired.
"""
if not auth_code:
raise Exception("No auth code")
raise ImproperlyConfigured("no auth code configured")
payload = {
"client_id": self.client_id,
"client_secret": self.client_secret,
Expand All @@ -72,7 +71,7 @@ def refresh_token(self) -> AhjoToken:
"""
token = self.get_token_from_db()
if not token.refresh_token:
raise Exception("No refresh token")
raise ImproperlyConfigured("No refresh token configured")

payload = {
"client_id": self.client_id,
Expand All @@ -85,8 +84,8 @@ def refresh_token(self) -> AhjoToken:

def do_token_request(self, payload: Dict[str, str]) -> AhjoToken:
# Make the POST request
response = self.requests_module.post(
self.token_url, headers=self.headers, data=payload, timeout=self.timout
response = requests.post(
self.token_url, headers=self.headers, data=payload, timeout=self.timeout
)

# Check if the request was successful
Expand Down
108 changes: 85 additions & 23 deletions backend/benefit/applications/services/ahjo_integration.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import json
import logging
import os
import uuid
import zipfile
from collections import defaultdict
from dataclasses import dataclass
Expand All @@ -10,12 +12,14 @@
import pdfkit
import requests
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
from django.db.models import QuerySet
from django.urls import reverse

from applications.enums import ApplicationStatus
from applications.models import AhjoSetting, Application
from applications.enums import AhjoStatus as AhjoStatusEnum, ApplicationStatus
from applications.models import AhjoSetting, AhjoStatus, Application
from applications.services.ahjo_authentication import AhjoConnector
from applications.services.ahjo_payload import prepare_open_case_payload
from applications.services.applications_csv_report import ApplicationsCsvService
from companies.models import Company

Expand Down Expand Up @@ -358,44 +362,102 @@ def export_application_batch(batch) -> bytes:
return generate_zip(pdf_files)


def dummy_ahjo_request():
"""Dummy function for preliminary testing of Ahjo integration"""
ahjo_api_url = settings.AHJO_REST_API_URL
def get_token() -> str:
"""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(
"Error: Ahjo auth code not found in database. Please set the 'ahjo_code' setting."
)
return

connector = AhjoConnector(requests)

if not connector.is_configured():
LOGGER.warning("AHJO connector is not configured")
return
try:
ahjo_token = connector.get_access_token(ahjo_auth_code["code"])
except Exception as e:
LOGGER.warning(f"Error retrieving access token: {e}")
return
headers = {
"Authorization": f"Bearer {ahjo_token.access_token}",


def prepare_headers(access_token: str, application_uuid: uuid) -> dict:
"""Prepare the headers for the Ahjo request."""
url = reverse("ahjo_callback_url", kwargs={"uuid": str(application_uuid)})

return {
"Authorization": f"Bearer {access_token}",
"Accept": "application/hal+json",
"X-CallbackURL": f"{settings.API_BASE_URL}{url}",
}
print(headers)


def get_application_for_ahjo(id: uuid) -> Optional[Application]:
"""Get the first accepted application."""
application = (
Application.objects.filter(pk=id, status=ApplicationStatus.ACCEPTED)
.prefetch_related("attachments", "calculation", "company")
.first()
)
if not application:
raise ObjectDoesNotExist("No applications found for Ahjo request.")
# Check that the handler has an ad_username set, if not, ImproperlyConfigured
if not application.calculation.handler.ad_username:
raise ImproperlyConfigured(
"No ad_username set for the handler for Ahjo request."
)
return application


def create_status_for_application(application: Application):
"""Create a new AhjoStatus for the application."""
AhjoStatus.objects.create(
application=application, status=AhjoStatusEnum.REQUEST_TO_OPEN_CASE_SENT
)


def do_ahjo_request_with_json_payload(
url: str, headers: dict, data: dict, application: Application, timeout: int = 10
):
headers["Content-Type"] = "application/json"

json_data = json.dumps(data)

try:
response = requests.get(
f"{ahjo_api_url}/cases", headers=headers, timeout=connector.timout
response = requests.post(
f"{url}/cases", headers=headers, timeout=timeout, data=json_data
)
response.raise_for_status()
print(response.json())

if response.ok:
create_status_for_application(application)
LOGGER.info(
f"Open case for application {application.id} Request to Ahjo was successful."
)

except requests.exceptions.HTTPError as e:
# Handle the HTTP error
LOGGER.error(f"HTTP error occurred: {e}")
LOGGER.error(f"HTTP error occurred while sending request to Ahjo: {e}")
except requests.exceptions.RequestException as e:
# Handle the network error
LOGGER.errror(f"Network error occurred: {e}")
LOGGER.error(f"Network error occurred while sending request to Ahjo: {e}")
except Exception as e:
# Handle any other error
LOGGER.error(f"Error occurred: {e}")
LOGGER.error(f"Error occurred while sending request to Ahjo: {e}")


def open_case_in_ahjo(application_id: uuid):
"""Open a case in Ahjo."""
try:
application = get_application_for_ahjo(application_id)
ahjo_api_url = settings.AHJO_REST_API_URL
ahjo_token = get_token()
headers = prepare_headers(ahjo_token.access_token, application.id)
data = prepare_open_case_payload(application)
do_ahjo_request_with_json_payload(ahjo_api_url, headers, data, application)
except ObjectDoesNotExist as e:
LOGGER.error(f"Object not found: {e}")
except ImproperlyConfigured as e:
LOGGER.error(f"Improperly configured: {e}")
134 changes: 134 additions & 0 deletions backend/benefit/applications/services/ahjo_payload.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import uuid
from datetime import datetime
from typing import List, Union

from django.conf import settings
from django.urls import reverse

from applications.models import Application, Attachment
from common.utils import hash_file
from users.models import User


def _prepare_top_level_dict(application: Application, case_records: List[dict]) -> dict:
"""Prepare the dictionary that is sent to Ahjo"""
application_date = application.created_at.isoformat()
application_year = application.created_at.year
title = f"Avustuksen myöntäminen, työllisyyspalvelut, \
työnantajan Helsinki-lisä vuonna {application.created_at.year}, \
työnantaja {application.company_name}"

case_dict = {
"Title": title,
"Acquired": application_date,
"ClassificationCode": "02 05 01 00",
"ClassificationTitle": "Kunnan myöntämät avustukset",
"Language": "fi",
"PublicityClass": "Julkinen",
"InternalTitle": f"Avustuksen myöntäminen, työllisyyspalvelut, \
työnantajan Helsinki-lisä vuonna {application_year}, \
työnantaja {application.company_name}",
"Subjects": [
{"Subject": "Helsinki-lisät", "Scheme": "hki-yhpa"},
{"Subject": "kunnan myöntämät avustukset", "Scheme": "hki-yhpa"},
{"Subject": "työnantajat", "Scheme": "hki-yhpa"},
{"Subject": "työllisyydenhoito"},
],
"PersonalData": "Sisältää erityisiä henkilötietoja",
"Reference": application.application_number,
"Records": case_records,
"Agents": [
{
"Role": "sender_initiator",
"CorporateName": application.company.name,
"ContactPerson": application.contact_person,
"Type": "ExterOnal",
"Email": application.company_contact_person_email,
"AddressStreet": application.company.street_address,
"AddressPostalCode": application.company.postcode,
"AddressCity": application.company.city,
}
],
}
return case_dict


def _prepare_record_document_dict(attachment: Attachment) -> dict:
"""Prepare a documents dict for a record"""
# If were running in mock mode, use the local file URI
file_url = reverse("ahjo_attachment_url", kwargs={"uuid": attachment.id})
hash_value = hash_file(attachment.attachment_file)
return {
"FileName": f"{attachment.attachment_file.name}",
"FormatName": f"{attachment.content_type}",
"HashAlgorithm": "sha256",
"HashValue": hash_value,
"FileURI": f"{settings.API_BASE_URL}{file_url}",
}


def _prepare_record(
record_title: str,
record_type: str,
acquired: datetime,
reference: Union[int, uuid.UUID],
documents: List[dict],
handler: User,
publicity_class: str = "Salassa pidettävä",
):
"""Prepare a single record dict for Ahjo."""

return {
"Title": record_title,
"Type": record_type,
"Acquired": acquired,
"PublicityClass": publicity_class,
"SecurityReasons": ["JulkL (621/1999) 24.1 § 25 k"],
"Language": "fi",
"PersonalData": "Sisältää erityisiä henkilötietoja",
"Reference": str(reference),
"Documents": documents,
"Agents": [
{
"Role": "mainCreator",
"Name": f"{handler.last_name}, {handler.first_name}",
"ID": handler.ad_username,
}
],
}


def _prepare_case_records(application: Application) -> List[dict]:
"""Prepare the list of case records"""
case_records = []
handler = application.calculation.handler
main_document_record = _prepare_record(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So many parameters. Maybe using them named would make easier to know what's what?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try to make it more readable.

"Hakemus",
"hakemus",
application.created_at.isoformat(),
application.application_number,
[], # TODO Pdf version of the application goes here with prepare_record()
handler,
)

case_records.append(main_document_record)

for attachment in application.attachments.all():
document_record = _prepare_record(
"Hakemuksen Liite",
"liite",
attachment.created_at.isoformat(),
attachment.id,
[_prepare_record_document_dict(attachment)],
handler,
)
case_records.append(document_record)

return case_records


def prepare_open_case_payload(application: Application) -> dict:
"Prepare the complete dictionary payload that is sent to Ahjo"
case_records = _prepare_case_records(application)
payload = _prepare_top_level_dict(application, case_records)
return payload
Loading
Loading