diff --git a/backend/benefit/applications/services/ahjo_integration.py b/backend/benefit/applications/services/ahjo_integration.py index 00be0e7ef2..e903e08ab2 100644 --- a/backend/benefit/applications/services/ahjo_integration.py +++ b/backend/benefit/applications/services/ahjo_integration.py @@ -1,10 +1,14 @@ +from datetime import datetime +import json import logging import os +import uuid import zipfile from collections import defaultdict from dataclasses import dataclass from io import BytesIO -from typing import List, Optional +from typing import List, LiteralString, Optional, Tuple, Union +from django.urls import reverse import jinja2 import pdfkit @@ -12,12 +16,15 @@ from django.conf import settings from django.core.exceptions import ObjectDoesNotExist from django.db.models import QuerySet +import urllib.request from applications.enums import ApplicationStatus -from applications.models import AhjoSetting, Application +from applications.models import AhjoSetting, Application, Attachment from applications.services.ahjo_authentication import AhjoConnector from applications.services.applications_csv_report import ApplicationsCsvService +from common.utils import encode_multipart_formdata, hash_file from companies.models import Company +from users.models import User @dataclass @@ -356,8 +363,7 @@ def export_application_batch(batch) -> bytes: return generate_zip(pdf_files) -def dummy_ahjo_request(): - """Dummy function for preliminary testing of Ahjo integration""" +def dummy_open_case_request(): ahjo_api_url = settings.AHJO_REST_API_URL try: ahjo_auth_code = AhjoSetting.objects.get(name="ahjo_code").data @@ -378,22 +384,195 @@ def dummy_ahjo_request(): except Exception as e: LOGGER.warning(f"Error retrieving access token: {e}") return + headers = { "Authorization": f"Bearer {ahjo_token.access_token}", + "Accept": "application/hal+json", + "X-CallbackURL": "https://localhost:8000/api/ahjo/callback", } - print(headers) + + application = ( + Application.objects.filter(status=ApplicationStatus.ACCEPTED) + .prefetch_related("attachments", "calculation", "company") + .first() + ) + + data = prepare_open_case_payload(application) + + json_data = json.dumps(data) + + # Prepare the form data + form_data, content_type = encode_multipart_formdata({'case': json_data}) + + headers['Content-Type'] = content_type + try: - response = requests.get( - f"{ahjo_api_url}/cases", headers=headers, timeout=connector.timout + request = urllib.request.Request( + f"{ahjo_api_url}/cases", + method='POST', + headers=headers, + data=form_data.encode('utf-8') ) - response.raise_for_status() - print(response.json()) + + with urllib.request.urlopen(request) as response: + response_data = response.read() + print(response.status) + print(response_data.decode('utf-8')) + + response.raise_for_status() + ## add new case opened status for attachment + # print(response.json()) except requests.exceptions.HTTPError as e: # Handle the HTTP error LOGGER.error(f"HTTP error occurred: {e}") except requests.exceptions.RequestException as e: # Handle the network error - LOGGER.errror(f"Network error occurred: {e}") + LOGGER.error(f"Network error occurred: {e}") except Exception as e: # Handle any other error LOGGER.error(f"Error occurred: {e}") + +# @dataclass +# class AhjoAgent: +# Role: str +# Name: str +# ID: str + + +# @dataclass +# class AhjoRecord: +# Title: str +# Type: str +# Acquired: datetime +# PublicityClass: str +# Language: str +# PersonalData: str +# Reference: str +# Documents: List[dict] +# Agents: List[AhjoAgent] + + +def prepare_open_case_dict(application: Application, case_records: List[dict]) -> dict: + 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_year}, \ +työnantaja {application.company_name}" + + contact_person = ( + application.company_contact_person_first_name + + " " + + application.company_contact_person_last_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": 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: + # 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": file_url, + } + + +def prepare_record( + record_title: str, + record_type: str, + acquired: datetime, + reference: Union[int, uuid.UUID], + documents: List[dict], + agent: User, + publicity_class: str = "Salassa pidettävä", +): + + 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"{agent.last_name}, {agent.first_name}", + "ID": "ahjo_id"#agent.ahjo_id, + } + ], + } + + +def prepare_case_records(application: Application) -> List[dict]: + case_records = [] + handler = application.calculation.handler + main_document_record = prepare_record( + "Hakemus", + "hakemus", + application.created_at.isoformat(), + application.application_number, + [], # Pdf version of the application goes here + 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: + case_records = prepare_case_records(application) + payload = prepare_open_case_dict(application, case_records) + return payload + # return json.dumps(payload, ensure_ascii=False)#.encode('utf-8') diff --git a/backend/benefit/applications/tests/test_ahjo_integration.py b/backend/benefit/applications/tests/test_ahjo_integration.py index 5cdbdc2145..67aa1d6488 100644 --- a/backend/benefit/applications/tests/test_ahjo_integration.py +++ b/backend/benefit/applications/tests/test_ahjo_integration.py @@ -25,10 +25,13 @@ generate_single_approved_file, generate_single_declined_file, REJECTED_TITLE, + prepare_record, + prepare_record_document_dict, ) from applications.tests.factories import ApplicationFactory, DecidedApplicationFactory from calculator.models import Calculation from calculator.tests.factories import PaySubsidyFactory +from common.utils import hash_file from companies.tests.factories import CompanyFactory from helsinkibenefit.tests.conftest import * # noqa from shared.common.tests.utils import normalize_whitespace @@ -410,3 +413,148 @@ def test_ahjo_callback_unauthorized_ip_not_allowed( response = ahjo_client.post(url, **auth_headers) assert response.status_code == 403 + +# def test_prepare_open_case_dict(decided_application): +# application = decided_application +# 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_year}, \ +# työnantaja {application.company_name}" + +# wanted = { +# "Title": f"{title}", +# "Acquired": f"{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": f"{application.application_number}", +# "Records": [], +# "Agents": [ +# { +# "Role": "sender_initiator", +# "CorporateName": f"{application.company.name}", +# "ContactPerson": f"{application.company_contact_person_first_name} \ +# {application.company_contact_person_last_name}", +# "Type": "External", +# "Email": f"{application.company_contact_person_email}", +# "AddressStreet": f"{application.company.street_address}", +# "AddressPostalCode": f"{application.company.postcode}", +# "AddressCity": f"{application.company.city}", +# } +# ], +# } + +# got = prepare_open_case_payload(application) + +# assert wanted == got + + +def test_prepare_record(decided_application): + application = decided_application + + record_title = "Hakemus" + record_type = ("hakemus",) + acquired = application.created_at.isoformat() + reference = application.application_number + documents = [] + agent = application.calculation.handler + publicity_class = "Salassa pidettävä" + + wanted = ( + { + "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": reference, + "Documents": documents, + "Agents": [ + { + "Role": "mainCreator", + "Name": f"{agent.last_name}, {agent.first_name}", + "ID": agent.id, + } + ], + }, + ) + + got = prepare_record( + record_title, + record_type, + application.created_at.isoformat(), + application.application_number, + [], + application.calculation.handler, + ) + + assert wanted == got + + +def test_prepare_record_document_dict(decided_application, settings): + settings.DEBUG = True + attachment = decided_application.attachments.first() + hash_value = hash_file(attachment.attachment_file) + file_url = reverse("ahjo_attachment_url", kwargs={"uuid": attachment.id}) + + want = { + "FileName": attachment.attachment_file.name, + "FormatName": attachment.content_type, + "HashAlgorithm": "sha256", + "HashValue": hash_value, + "FileURI": file_url, + } + + got = prepare_record_document_dict(attachment) + + assert want == got + + +# def test_prepare_case_records(decided_application, settings): +# settings.DEBUG = True +# application = decided_application +# handler_name = f"{application.calculation.handler.last_name}, {application.calculation.handler.first_name}" +# want = [ +# { +# "Title": "Hakemus", +# "Type": "hakemus", +# "Acquired": f"{application.created_at.isoformat()}", +# "PublicityClass": "Salassa pidettävä", +# "SecurityReasons": [ +# "JulkL (621/1999) 24.1 § 25 k" +# ], +# "Language": "fi", +# "PersonalData": "Sisältää erityisiä henkilötietoja", +# "Reference": f"{application.application_number}", +# "Documents": [], +# "Agents": [ +# { +# "Role": "mainCreator", +# "Name": f"{handler_name}", +# "ID": f"{application.calculation.handler.id}", +# } +# ], + +# } +# ] + +# got = prepare_case_records(application) + +# assert want == got + + +# def test_case_open_callback(decided_application): +# pass