diff --git a/backend/benefit/applications/services/applications_csv_report.py b/backend/benefit/applications/services/applications_csv_report.py index f29683e93b..48c18e8b70 100644 --- a/backend/benefit/applications/services/applications_csv_report.py +++ b/backend/benefit/applications/services/applications_csv_report.py @@ -1,6 +1,6 @@ import decimal import logging -from datetime import datetime +from datetime import date, datetime from typing import List from django.conf import settings @@ -15,6 +15,7 @@ get_organization_type, nested_queryset_attr, ) +from calculator.models import Instalment LOGGER = logging.getLogger(__name__) @@ -110,16 +111,16 @@ def __init__(self, applications, prune_sensitive_data=False): def query_instalment_by_number( self, application: Application, number: int - ) -> decimal.Decimal: + ) -> Instalment: """Return the actual payable amount of the currently accepted and due instalment""" try: instalment = application.calculation.instalments.get( due_date__lte=timezone.now().date(), instalment_number=number, ) - return instalment.amount_paid + return instalment except ObjectDoesNotExist: - LOGGER.error( + LOGGER.info( f"Valid payable Instalment not found for application {application.application_number}" ) except MultipleObjectsReturned: @@ -128,11 +129,48 @@ def query_instalment_by_number( {application.application_number}, there should be only one" ) - def get_instalment_1(self, application: Application) -> decimal.Decimal: - return self.query_instalment_by_number(application, 1) + def get_instalment_1_amount(self, application: Application) -> decimal.Decimal: + """ + Return the amount that was granted for the first instalment. + """ + instalment = self.query_instalment_by_number(application, 1) + if instalment and instalment.amount: + return instalment.amount + else: + return None - def get_instalment_2(self, application: Application) -> decimal.Decimal: - return self.query_instalment_by_number(application, 2) + def get_instalment_2_amount_after_recoveries( + self, application: Application + ) -> decimal.Decimal: + """ + Return the actual amount that is payable on the second instalment, + after possible alterations have been deductet. + """ + instalment = self.query_instalment_by_number(application, 2) + if instalment and instalment.amount_after_recoveries: + return instalment.amount_after_recoveries + else: + return None + + def get_instalment_2_amount(self, application: Application) -> decimal.Decimal: + """ + Return the amount that was granted for the second instalment. + """ + instalment = self.query_instalment_by_number(application, 2) + if instalment and instalment.amount: + return instalment.amount + else: + return None + + def get_instalment_2_due_date(self, application: Application) -> date: + """ + Return the due date of the second instalment. + """ + instalment = self.query_instalment_by_number(application, 2) + if instalment: + return instalment.due_date + else: + return "" @property def CSV_COLUMNS(self) -> List[CsvColumn]: @@ -326,10 +364,24 @@ def CSV_COLUMNS(self) -> List[CsvColumn]: if settings.PAYMENT_INSTALMENTS_ENABLED: columns.append( - csv_default_column("Maksuerä 1", self.get_instalment_1), + csv_default_column( + "Myönnetty maksuerä 1", self.get_instalment_1_amount + ), + ) + columns.append( + csv_default_column( + "Myönnetty maksuerä 2", self.get_instalment_2_amount + ), + ) + columns.append( + csv_default_column( + "Maksettu maksuerä 2", self.get_instalment_2_amount_after_recoveries + ), ) columns.append( - csv_default_column("Maksuerä 2", self.get_instalment_2), + csv_default_column( + "Maksuerän 2 maksupäivä", self.get_instalment_2_due_date + ), ) # Include all the application rows in the same line for easier processing for idx in range(self.MAX_AHJO_ROWS): diff --git a/backend/benefit/applications/services/applications_power_bi_csv_report.py b/backend/benefit/applications/services/applications_power_bi_csv_report.py index d77a7acda9..b18fabce3a 100644 --- a/backend/benefit/applications/services/applications_power_bi_csv_report.py +++ b/backend/benefit/applications/services/applications_power_bi_csv_report.py @@ -117,10 +117,24 @@ def CSV_COLUMNS(self): if settings.PAYMENT_INSTALMENTS_ENABLED: columns.append( - csv_default_column("Maksuerä 1", self.get_instalment_1), + csv_default_column( + "Myönnetty maksuerä 1", self.get_instalment_1_amount + ), ) columns.append( - csv_default_column("Maksuerä 2", self.get_instalment_2), + csv_default_column( + "Myönnetty maksuerä 2", self.get_instalment_2_amount + ), + ) + columns.append( + csv_default_column( + "Maksettu maksuerä 2", self.get_instalment_2_amount_after_recoveries + ), + ) + columns.append( + csv_default_column( + "Maksuerän 2 maksupäivä", self.get_instalment_2_due_date + ), ) return columns diff --git a/backend/benefit/applications/services/talpa_csv_service.py b/backend/benefit/applications/services/talpa_csv_service.py index 6d6c158d15..5724158daf 100644 --- a/backend/benefit/applications/services/talpa_csv_service.py +++ b/backend/benefit/applications/services/talpa_csv_service.py @@ -30,7 +30,7 @@ def get_relevant_instalment_amount( status=InstalmentStatus.ACCEPTED, due_date__lte=timezone.now().date(), ) - return instalment.amount_paid + return instalment.amount_after_recoveries except ObjectDoesNotExist: LOGGER.error( f"Valid payable Instalment not found for application {application.application_number}" @@ -40,7 +40,8 @@ def get_relevant_instalment_amount( f"Multiple payable Instalments found for application \ {application.application_number}, there should be only one" ) - return application.calculation.calculated_benefit_amount + else: + return application.calculation.calculated_benefit_amount @property def CSV_COLUMNS(self): diff --git a/backend/benefit/applications/tests/conftest.py b/backend/benefit/applications/tests/conftest.py index 92a116e01f..6070d46cc1 100755 --- a/backend/benefit/applications/tests/conftest.py +++ b/backend/benefit/applications/tests/conftest.py @@ -181,7 +181,7 @@ def applications_csv_service_with_one_application(applications_csv_service): @pytest.fixture -def pruned_applications_csv_service(): +def talpa_applications_csv_service(): # retrieve the objects through the default manager so that annotations are added application1 = DecidedApplicationFactory(application_number=100001) application2 = DecidedApplicationFactory(application_number=100002) @@ -194,9 +194,7 @@ def pruned_applications_csv_service(): @pytest.fixture -def pruned_applications_csv_service_with_one_application( - applications_csv_service, application_batch -): +def talpa_applications_csv_service_with_one_application(application_batch): application1 = application_batch.applications.all().first() return TalpaCsvService(Application.objects.filter(pk=application1.pk), True) diff --git a/backend/benefit/applications/tests/test_applications_report.py b/backend/benefit/applications/tests/test_applications_report.py index 4fce68ed25..e6a8440f70 100644 --- a/backend/benefit/applications/tests/test_applications_report.py +++ b/backend/benefit/applications/tests/test_applications_report.py @@ -603,14 +603,14 @@ def test_write_application_alterations_csv_file( (True,), ], ) -def test_pruned_applications_csv_output( - pruned_applications_csv_service_with_one_application, instalments_enabled, settings +def test_talpa_applications_csv_output( + talpa_applications_csv_service_with_one_application, instalments_enabled, settings ): settings.PAYMENT_INSTALMENTS_ENABLED = instalments_enabled instalment_amount = decimal.Decimal("123.45") application = ( - pruned_applications_csv_service_with_one_application.get_applications()[0] + talpa_applications_csv_service_with_one_application.get_applications()[0] ) if instalments_enabled: application.calculation.instalments.all().delete() @@ -624,7 +624,7 @@ def test_pruned_applications_csv_output( ) csv_lines = split_lines_at_semicolon( - pruned_applications_csv_service_with_one_application.get_csv_string() + talpa_applications_csv_service_with_one_application.get_csv_string() ) # Assert that there are 18 column headers in the pruned CSV assert len(csv_lines[0]) == 18 diff --git a/backend/benefit/applications/tests/test_talpa_integration.py b/backend/benefit/applications/tests/test_talpa_integration.py index 83c05d7d00..d276cbb03d 100644 --- a/backend/benefit/applications/tests/test_talpa_integration.py +++ b/backend/benefit/applications/tests/test_talpa_integration.py @@ -36,25 +36,60 @@ def test_talpa_lines(applications_csv_service): ) -def test_talpa_csv_cell_list_lines_generator(pruned_applications_csv_service): +def test_talpa_csv_cell_list_lines_generator(talpa_applications_csv_service): check_csv_cell_list_lines_generator( - pruned_applications_csv_service, expected_row_count_with_header=3 + talpa_applications_csv_service, expected_row_count_with_header=3 ) -def test_talpa_csv_string_lines_generator(pruned_applications_csv_service): +def test_talpa_csv_string_lines_generator(talpa_applications_csv_service): check_csv_string_lines_generator( - pruned_applications_csv_service, expected_row_count_with_header=3 + talpa_applications_csv_service, expected_row_count_with_header=3 ) -def test_talpa_csv_output(pruned_applications_csv_service_with_one_application): +@pytest.mark.parametrize( + "instalments_enabled, number_of_instalments", + [ + (False, 1), + (True, 1), + (True, 2), + ], +) +def test_talpa_csv_output( + talpa_applications_csv_service_with_one_application, + instalments_enabled, + number_of_instalments, + settings, +): + settings.PAYMENT_INSTALMENTS_ENABLED = instalments_enabled + application = ( + talpa_applications_csv_service_with_one_application.applications.first() + ) + application.calculation.instalments.all().delete() + + if instalments_enabled: + for i in range(number_of_instalments): + status = InstalmentStatus.ACCEPTED + due_date = datetime.now(timezone.utc).date() + if i == 1: + status = InstalmentStatus.WAITING + due_date = timezone.now() + timedelta(days=181) + + Instalment.objects.create( + calculation=application.calculation, + amount=decimal.Decimal("123.45"), + instalment_number=i + 1, + status=status, + due_date=due_date, + ) + csv_lines = split_lines_at_semicolon( - pruned_applications_csv_service_with_one_application.get_csv_string() + talpa_applications_csv_service_with_one_application.get_csv_string() ) # BOM at the beginning of the file assert csv_lines[0][0] == '\ufeff"Hakemusnumero"' - csv_columns = iter(pruned_applications_csv_service_with_one_application.CSV_COLUMNS) + csv_columns = iter(talpa_applications_csv_service_with_one_application.CSV_COLUMNS) next(csv_columns, None) # Skip the first element for idx, col in enumerate(csv_columns, start=1): @@ -62,33 +97,43 @@ def test_talpa_csv_output(pruned_applications_csv_service_with_one_application): assert ( int(csv_lines[1][0]) - == pruned_applications_csv_service_with_one_application.applications.first().application_number + == talpa_applications_csv_service_with_one_application.applications.first().application_number ) + if instalments_enabled: + wanted_instalment = application.calculation.instalments.get( + status=InstalmentStatus.ACCEPTED, + due_date__lte=timezone.now().date(), + ) + assert ( + decimal.Decimal(csv_lines[1][8]) + == wanted_instalment.amount_after_recoveries + ) + def test_talpa_csv_non_ascii_characters( - pruned_applications_csv_service_with_one_application, + talpa_applications_csv_service_with_one_application, ): application = ( - pruned_applications_csv_service_with_one_application.applications.first() + talpa_applications_csv_service_with_one_application.applications.first() ) application.company_name = "test äöÄÖtest" application.save() csv_lines = split_lines_at_semicolon( - pruned_applications_csv_service_with_one_application.get_csv_string() + talpa_applications_csv_service_with_one_application.get_csv_string() ) assert csv_lines[1][3] == '"test äöÄÖtest"' # string is quoted -def test_talpa_csv_delimiter(pruned_applications_csv_service_with_one_application): +def test_talpa_csv_delimiter(talpa_applications_csv_service_with_one_application): application = ( - pruned_applications_csv_service_with_one_application.applications.first() + talpa_applications_csv_service_with_one_application.applications.first() ) application.company_name = "test;12" application.save() assert ( ';"test;12";' - in pruned_applications_csv_service_with_one_application.get_csv_string() + in talpa_applications_csv_service_with_one_application.get_csv_string() ) @@ -100,13 +145,13 @@ def test_talpa_csv_delimiter(pruned_applications_csv_service_with_one_applicatio ], ) def test_talpa_csv_decimal( - pruned_applications_csv_service_with_one_application, + talpa_applications_csv_service_with_one_application, settings, instalments_enabled, ): settings.PAYMENT_INSTALMENTS_ENABLED = instalments_enabled application = ( - pruned_applications_csv_service_with_one_application.applications.first() + talpa_applications_csv_service_with_one_application.applications.first() ) if instalments_enabled: application.calculation.instalments.all().delete() @@ -123,34 +168,34 @@ def test_talpa_csv_decimal( application.calculation.save() csv_lines = split_lines_at_semicolon( - pruned_applications_csv_service_with_one_application.get_csv_string() + talpa_applications_csv_service_with_one_application.get_csv_string() ) assert csv_lines[1][8] == "123.45" -def test_talpa_csv_date(pruned_applications_csv_service_with_one_application): +def test_talpa_csv_date(talpa_applications_csv_service_with_one_application): application = ( - pruned_applications_csv_service_with_one_application.get_applications().first() + talpa_applications_csv_service_with_one_application.get_applications().first() ) now = datetime.now(timezone.utc) application.batch.decision_date = now application.batch.save() csv_lines = split_lines_at_semicolon( - pruned_applications_csv_service_with_one_application.get_csv_string() + talpa_applications_csv_service_with_one_application.get_csv_string() ) assert csv_lines[1][12] == f'"{now.strftime("%Y-%m-%d")}"' def test_write_talpa_csv_file( - pruned_applications_csv_service_with_one_application, tmp_path + talpa_applications_csv_service_with_one_application, tmp_path ): application = ( - pruned_applications_csv_service_with_one_application.applications.first() + talpa_applications_csv_service_with_one_application.applications.first() ) application.company_name = "test äöÄÖtest" application.save() output_file = tmp_path / "output.csv" - pruned_applications_csv_service_with_one_application.write_csv_file(output_file) + talpa_applications_csv_service_with_one_application.write_csv_file(output_file) with open(output_file, encoding="utf-8-sig") as f: contents = f.read() assert contents.startswith('"Hakemusnumero";"Työnantajan tyyppi"')