diff --git a/backend/benefit/applications/services/ahjo_client.py b/backend/benefit/applications/services/ahjo_client.py index d01230ef57..a089ecbfe7 100644 --- a/backend/benefit/applications/services/ahjo_client.py +++ b/backend/benefit/applications/services/ahjo_client.py @@ -279,7 +279,7 @@ def send_request_to_ahjo( ) return None, None - def handle_http_error(self, e: requests.exceptions.HTTPError) -> Tuple[None, dict]: + def handle_http_error(self, e: requests.exceptions.HTTPError) -> None: """Handle HTTP errors that occur when sending a request to Ahjo. Also log any validation errors received from Ahjo. """ @@ -292,13 +292,10 @@ def handle_http_error(self, e: requests.exceptions.HTTPError) -> Tuple[None, dic if hasattr(self._request, "application"): application_number = self._request.application.application_number - error_message = f"A HTTP error occurred while sending {self._request} for application \ - {application_number} to Ahjo: {e}" + error_message = self.format_error_message(e, application_number) else: - error_message = ( - f"A HTTP error occurred while sending {self._request} to Ahjo: {e}" - ) + error_message = self.format_error_message(e) if error_json: error_message += f" Error message: {error_json}" @@ -307,3 +304,11 @@ def handle_http_error(self, e: requests.exceptions.HTTPError) -> Tuple[None, dic status.save() LOGGER.error(error_message) + + def format_error_message( + self, + e: Union[requests.exceptions.HTTPError, requests.exceptions.RequestException], + application_number: Union[int, None] = None, + ) -> str: + return f"A HTTP or network error occurred while sending {self.request} for application \ + {application_number} to Ahjo: {e}" diff --git a/backend/benefit/applications/services/ahjo_error_writer.py b/backend/benefit/applications/services/ahjo_error_writer.py new file mode 100644 index 0000000000..0f756db14c --- /dev/null +++ b/backend/benefit/applications/services/ahjo_error_writer.py @@ -0,0 +1,13 @@ +from applications.models import Application + + +class AhjoErrorWriter: + @staticmethod + def write_error_to_ahjo_status(application: Application, error: str) -> None: + latest_ahjo_status = application.ahjo_status.latest() + latest_ahjo_status.error_from_ahjo = { + "id": "NO_ID", + "context": f"{error}", + "message": "Ahjo-pyynnössä tapahtui virhe, mutta Ahjo ei palauttanut tarkempia tietoja.", + } + latest_ahjo_status.save() diff --git a/backend/benefit/applications/services/ahjo_integration.py b/backend/benefit/applications/services/ahjo_integration.py index 4477a25bb5..50a2e62ddf 100644 --- a/backend/benefit/applications/services/ahjo_integration.py +++ b/backend/benefit/applications/services/ahjo_integration.py @@ -14,6 +14,8 @@ from django.core.files.base import ContentFile from django.db.models import QuerySet from django.urls import reverse +from lxml import etree +from lxml.etree import XMLSchemaParseError, XMLSyntaxError from applications.enums import ( AhjoRequestType, @@ -42,6 +44,7 @@ AhjoSubscribeDecisionRequest, AhjoUpdateRecordsRequest, ) +from applications.services.ahjo_error_writer import AhjoErrorWriter from applications.services.ahjo_payload import ( prepare_attachment_records_payload, prepare_decision_proposal_payload, @@ -551,6 +554,12 @@ def send_new_attachment_records_to_ahjo( return result, response_text +class DecisionProposalError(Exception): + """Custom exception for errors in XML generation.""" + + pass + + def send_decision_proposal_to_ahjo( application: Application, ahjo_token: AhjoToken ) -> Union[Tuple[Application, str], None]: @@ -568,19 +577,35 @@ def send_decision_proposal_to_ahjo( delete_existing_xml_attachments(application) - decision_xml = generate_application_attachment( - application, AttachmentType.DECISION_TEXT_XML, decision - ) - secret_xml = generate_application_attachment( - application, AttachmentType.DECISION_TEXT_SECRET_XML - ) + try: + decision_xml = generate_application_attachment( + application, AttachmentType.DECISION_TEXT_XML, decision + ) + secret_xml = generate_application_attachment( + application, AttachmentType.DECISION_TEXT_SECRET_XML + ) - data = prepare_decision_proposal_payload( - application=application, - decision_xml=decision_xml, - decision_text=decision, - secret_xml=secret_xml, - ) + data = prepare_decision_proposal_payload( + application=application, + decision_xml=decision_xml, + decision_text=decision, + secret_xml=secret_xml, + ) + except XMLSchemaParseError as e: + AhjoErrorWriter.write_error_to_ahjo_status(application, str(e)) + return (None, None) + except XMLSyntaxError as e: + AhjoErrorWriter.write_error_to_ahjo_status(application, str(e)) + return (None, None) + except etree.DocumentInvalid as e: + AhjoErrorWriter.write_error_to_ahjo_status(application, str(e)) + return (None, None) + except DecisionProposalError as e: + LOGGER.error( + f"Error in generating decision proposal payload\ + for application {application.application_number}: {e}" + ) + return (None, None) response, response_text = ahjo_client.send_request_to_ahjo(data) return response, response_text diff --git a/backend/benefit/applications/services/ahjo_xml_builder.py b/backend/benefit/applications/services/ahjo_xml_builder.py index a53914a20b..31473651ff 100644 --- a/backend/benefit/applications/services/ahjo_xml_builder.py +++ b/backend/benefit/applications/services/ahjo_xml_builder.py @@ -1,4 +1,5 @@ import copy +import logging import os from dataclasses import dataclass from datetime import date @@ -24,6 +25,8 @@ AhjoXMLString = str +LOGGER = logging.getLogger(__name__) + class AhjoXMLBuilder: def __init__(self, application: Application) -> None: @@ -60,14 +63,20 @@ def validate_against_schema(self, xml_string: str, xsd_string: str) -> bool: ) # This will raise an exception if the document is not valid return True # Return True if no exception was raised except XMLSchemaParseError as e: - print(f"Schema Error: {e}") + LOGGER.error( + f"Decision proposal XML Schema Error for application {self.application.application_number}: {e}" + ) raise except XMLSyntaxError as e: - print(f"XML Error: {e}") + LOGGER.error( + f"Decision proposal XML Syntax Error for application {self.application.application_number}: {e}" + ) raise except etree.DocumentInvalid as e: - print(f"Validation Error: {e}") - return False # Return False if the document is invalid + LOGGER.error( + f"Decision proposal Validation Error for application {self.application.application_number}: {e}" + ) + return False # Return False if the document is invalid class AhjoPublicXMLBuilder(AhjoXMLBuilder):