Skip to content

Commit

Permalink
Hl 887 new csv 2 (#3197)
Browse files Browse the repository at this point in the history
* chore: add factory and seeding for alteration

* feat: csv service for application alteration

* feat: download endpoint for alteration csv

* feat: update & download alteration as csv

* feat: add justification column to csv

* fix: test for csv download endpoint

* fix: button values type

* fix: import

* fix: optional values and onSubmit props

* fix: do not render button if no values present

---------

Co-authored-by: Riku Kestilä <[email protected]>
  • Loading branch information
sirtawast and rikuke authored Aug 12, 2024
1 parent 13f1f5b commit 15534b8
Show file tree
Hide file tree
Showing 14 changed files with 556 additions and 20 deletions.
71 changes: 70 additions & 1 deletion backend/benefit/applications/api/v1/application_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from dateutil.relativedelta import relativedelta
from django.conf import settings
from django.core import exceptions
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
from django.db import transaction
from django.db.models import Prefetch, Q, QuerySet
from django.http import FileResponse, HttpResponse, StreamingHttpResponse
Expand Down Expand Up @@ -41,13 +42,22 @@
ApplicationOrigin,
ApplicationStatus,
)
from applications.models import Application, ApplicationAlteration, ApplicationBatch
from applications.models import (
AhjoSetting,
Application,
ApplicationAlteration,
ApplicationBatch,
)
from applications.services.ahjo_integration import (
ExportFileInfo,
generate_zip,
prepare_csv_file,
prepare_pdf_files,
)
from applications.services.application_alteration_csv_report import (
AlterationCsvConfigurableFields,
ApplicationAlterationCsvService,
)
from applications.services.applications_csv_report import ApplicationsCsvService
from applications.services.generate_application_summary import (
generate_application_summary_file,
Expand Down Expand Up @@ -472,6 +482,64 @@ def update(self, request, *args, **kwargs):
else:
raise PermissionDenied(_("You are not allowed to do this action"))

@action(methods=["PATCH"], detail=False)
def update_with_csv(self, request):
"""
Update alteration and respond with a CSV file.
"""
alteration = ApplicationAlteration.objects.get(
application_id__in=[request.GET.get("application_id")],
id__in=[request.GET.get("alteration_id")],
)

alteration.recovery_justification = request.data.get("recovery_justification")
alteration.recovery_amount = request.data.get("recovery_amount")
alteration.recovery_end_date = request.data.get("recovery_end_date")
alteration.recovery_start_date = request.data.get("recovery_start_date")

alteration.save()
# CsvService requires a queryset, so we need to create a queryset with the alteration
queryset = ApplicationAlteration.objects.filter(
id__in=[alteration.id],
)
try:
alteration_fields = AhjoSetting.objects.get(
name="application_alteration_fields"
)
configurable_fields = AlterationCsvConfigurableFields(
account_number=alteration_fields.data["account_number"],
billing_department=alteration_fields.data["billing_department"],
)
return self._alterations_csv_response(queryset, configurable_fields)
except ObjectDoesNotExist:
raise ImproperlyConfigured(
"application_alteration_fields fields not found in the ahjo_settings table"
)

def _alterations_csv_response(
self,
queryset: QuerySet[ApplicationAlteration],
config: AlterationCsvConfigurableFields,
) -> StreamingHttpResponse:
"""Generate a response with a CSV file containing application alteration data."""
csv_service = ApplicationAlterationCsvService(queryset, config)

response = HttpResponse(
csv_service.get_csv_string(True).encode("utf-8"),
content_type="text/csv",
)
response["Content-Disposition"] = "attachment; filename={filename}.csv".format(
filename=self._alteration_filename()
)
return response

@staticmethod
def _alteration_filename():
return format_lazy(
_("Takaisinmaksu viety {date}"),
date=timezone.now().strftime("%Y%m%d_%H%M%S"),
)


@extend_schema(
description=(
Expand Down Expand Up @@ -681,6 +749,7 @@ def _csv_response(
csv_service.get_csv_string_lines_generator(remove_quotes),
content_type="text/csv",
)

response["Content-Disposition"] = "attachment; filename={filename}.csv".format(
filename=self._export_filename_without_suffix()
)
Expand Down
8 changes: 8 additions & 0 deletions backend/benefit/applications/management/commands/seed.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from applications.enums import (
AhjoStatus as AhjoStatusEnum,
ApplicationAlterationType,
ApplicationBatchStatus,
ApplicationOrigin,
ApplicationStatus,
Expand All @@ -27,6 +28,7 @@
from applications.tests.factories import (
AcceptedDecisionProposalFactory,
AdditionalInformationNeededApplicationFactory,
ApplicationAlterationFactory,
ApplicationBatchFactory,
ApplicationWithAttachmentFactory,
CancelledApplicationFactory,
Expand Down Expand Up @@ -112,6 +114,12 @@ def _create_batch(
application=app,
)

ApplicationAlterationFactory(
application=app,
alteration_type=ApplicationAlterationType.TERMINATION,
handled_by=app.calculation.handler,
)

elif proposal_for_decision == ApplicationStatus.REJECTED:
app = RejectedApplicationFactory()
create_decision_text_for_application(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from dataclasses import dataclass
from typing import Union

from django.db.models.query import QuerySet

from applications.models import ApplicationAlteration
from applications.services.csv_export_base import CsvColumn, CsvExportBase


@dataclass
class AlterationCsvConfigurableFields:
"""Configuration for changeable fields of application alteration csv.
These values can change and should be stored in the db or in the settings.py.
"""

account_number: str
billing_department: str = "1800 Kaupunginkanslia (Kansl)"


class ApplicationAlterationCsvService(CsvExportBase):
def __init__(
self,
application_alterations: QuerySet[ApplicationAlteration],
config: AlterationCsvConfigurableFields,
):
self.application_alterations = application_alterations
self.billing_department = config.billing_department
self.account_number = config.account_number

def get_recovery_period(
self, alteration: ApplicationAlteration
) -> Union[str, None]:
if alteration.recovery_start_date and alteration.recovery_end_date:
start = alteration.recovery_start_date.strftime("%d.%m.%Y")
end = alteration.recovery_end_date.strftime("%d.%m.%Y")
return f"{start} - {end}"
return None

def get_title(self, alteration: ApplicationAlteration) -> str:
return "Helsinki-lisä takaisinperintä"

def get_billing_department(self, alteration: ApplicationAlteration) -> str:
return self.billing_department

def get_account_number(self, alteration: ApplicationAlteration) -> str:
return self.account_number

def get_handler_name(self, alteration: ApplicationAlteration) -> Union[str, None]:
if alteration.handled_by:
return f"{alteration.handled_by.get_full_name()}, {alteration.handled_by.email}"
return ""

def get_company_address(self, alteration: ApplicationAlteration) -> str:
return alteration.application.company.get_full_address()

def get_company_contact_person(self, alteration: ApplicationAlteration) -> str:
application = alteration.application
return f"{application.company_contact_person_first_name} {application.company_contact_person_last_name} \
{application.company_contact_person_email} {application.company_contact_person_phone_number}"

def get_recovery_justification(self, alteration: ApplicationAlteration) -> str:
return alteration.recovery_justification

@property
def CSV_COLUMNS(self):
columns = [
CsvColumn("Viitetiedot", "application.application_number"),
CsvColumn(
"Aikajakso, jolta tukea peritään takaisin", self.get_recovery_period
),
CsvColumn("Summatieto", "recovery_amount"),
CsvColumn("Laskutettavan virallinen nimi", "application.company.name"),
CsvColumn("Laskutusosoite", self.get_company_address),
CsvColumn("Y-tunnus", "application.company.business_id"),
CsvColumn("Laskutettavan yhteyshenkilö", self.get_company_contact_person),
CsvColumn("Verkkolaskuosoite/OVT-tunnus", "einvoice_address"),
CsvColumn("Operaattori-/välittäjätunnus", "einvoice_provider_identifier"),
CsvColumn("Tilitunniste", self.get_account_number),
CsvColumn("Lisätietoja antaa", self.get_handler_name),
CsvColumn("Otsikko", self.get_title),
CsvColumn("Laskuttava yksikkö", self.get_billing_department),
CsvColumn("Selite", self.get_recovery_justification),
]
return columns

def get_alterations(self):
return self.application_alterations

def get_row_items(self):
for alteration in self.get_alterations():
yield alteration

def get_csv_cell_list_lines_generator(self):
if self.get_alterations():
yield from super().get_csv_cell_list_lines_generator()
else:
header_row = self._get_header_row()
yield header_row
yield ["Takaisinmaksuja ei löytynyt"] + [""] * (len(header_row) - 1)
54 changes: 53 additions & 1 deletion backend/benefit/applications/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,30 @@
AhjoDecisionDetails,
AhjoRecordTitle,
AhjoRecordType,
ApplicationAlterationType,
ApplicationStatus,
BenefitType,
DecisionType,
)
from applications.models import AhjoSetting, Application, ApplicationBatch
from applications.models import (
AhjoSetting,
Application,
ApplicationAlteration,
ApplicationBatch,
)
from applications.services.ahjo_decision_service import (
replace_decision_template_placeholders,
)
from applications.services.ahjo_payload import resolve_payload_language
from applications.services.application_alteration_csv_report import (
AlterationCsvConfigurableFields,
ApplicationAlterationCsvService,
)
from applications.services.applications_csv_report import ApplicationsCsvService
from applications.tests.factories import (
AcceptedDecisionProposalFactory,
AhjoDecisionTextFactory,
ApplicationAlterationFactory,
ApplicationBatchFactory,
ApplicationFactory,
CancelledApplicationFactory,
Expand All @@ -38,6 +49,7 @@
from common.tests.conftest import * # noqa
from companies.tests.conftest import * # noqa
from helsinkibenefit.tests.conftest import * # noqa
from shared.common.tests.factories import UserFactory
from shared.service_bus.enums import YtjOrganizationCode
from terms.tests.conftest import * # noqa
from terms.tests.factories import TermsOfServiceApprovalFactory
Expand Down Expand Up @@ -804,3 +816,43 @@ def batch_for_decision_details(application_with_ahjo_decision):
handler=application_with_ahjo_decision.calculation.handler,
auto_generated_by_ahjo=True,
)


@pytest.fixture
def application_alteration_csv_service():
application_1 = DecidedApplicationFactory(application_number=100003)
application_2 = DecidedApplicationFactory(application_number=100004)

handled_by = UserFactory()

ApplicationAlterationFactory(
application=application_1,
alteration_type=ApplicationAlterationType.TERMINATION,
handled_by=handled_by,
)

ApplicationAlterationFactory(
application=application_2,
alteration_type=ApplicationAlterationType.SUSPENSION,
handled_by=handled_by,
)

config = AlterationCsvConfigurableFields(
account_number="123456",
)

return ApplicationAlterationCsvService(
ApplicationAlteration.objects.filter(
application_id__in=[application_1.id, application_2.id]
),
config=config,
)


@pytest.fixture
def application_alteration(decided_application):
return ApplicationAlterationFactory(
application=decided_application,
alteration_type=ApplicationAlterationType.TERMINATION,
handled_by=decided_application.handler,
)
59 changes: 59 additions & 0 deletions backend/benefit/applications/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import factory

from applications.enums import (
ApplicationAlterationState,
ApplicationStatus,
ApplicationStep,
BenefitType,
Expand All @@ -18,6 +19,7 @@
AhjoDecisionText,
Application,
APPLICATION_LANGUAGE_CHOICES,
ApplicationAlteration,
ApplicationBasis,
ApplicationBatch,
Attachment,
Expand Down Expand Up @@ -490,3 +492,60 @@ class DeniedDecisionProposalFactory(factory.django.DjangoModelFactory):

class Meta:
model = DecisionProposalTemplateSection


class ApplicationAlterationFactory(factory.django.DjangoModelFactory):
state = ApplicationAlterationState.RECEIVED

end_date = factory.Faker(
"date_between_dates",
date_start=factory.LazyAttribute(lambda _: date.today() - timedelta(days=30)),
date_end=factory.LazyAttribute(lambda _: date.today()),
)

resume_date = factory.Faker(
"date_between_dates",
date_start=factory.LazyAttribute(lambda _: date.today() - timedelta(days=30)),
date_end=factory.LazyAttribute(lambda _: date.today()),
)

reason = factory.Faker("sentence", nb_words=2)

handled_at = factory.Faker(
"date_between_dates",
date_start=factory.LazyAttribute(lambda _: date.today() - timedelta(days=30)),
date_end=factory.LazyAttribute(lambda _: date.today()),
)

recovery_start_date = factory.Faker(
"date_between_dates",
date_start=factory.LazyAttribute(lambda _: date.today() - timedelta(days=30)),
date_end=factory.LazyAttribute(lambda _: date.today()),
)

recovery_end_date = factory.Faker(
"date_between_dates",
date_start=factory.LazyAttribute(lambda _: date.today() - timedelta(days=30)),
date_end=factory.LazyAttribute(lambda _: date.today()),
)

recovery_amount = factory.Faker(
"pydecimal", left_digits=4, right_digits=2, positive=True
)

use_einvoice = factory.Faker("boolean")

einvoice_provider_name = factory.Faker("company")

einvoice_provider_identifier = factory.Faker("sentence", nb_words=2)

einvoice_address = factory.Faker("street_address", locale="fi_FI")

contact_person_name = factory.Faker("name", locale="fi_FI")

is_recoverable = factory.Faker("boolean")

recovery_justification = factory.Faker("sentence", nb_words=2)

class Meta:
model = ApplicationAlteration
Loading

0 comments on commit 15534b8

Please sign in to comment.