Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apartment/elastic/documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ class ApartmentDocument(ReadOnlyDocument):

project_barred_bank_account = Keyword()
project_regular_bank_account = Keyword()
project_payment_recipient = Keyword()
project_payment_recipient_final = Keyword()

uuid = Keyword(required=True)

Expand Down
37 changes: 30 additions & 7 deletions application_form/pdf/hitas.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,18 +390,16 @@ class HitasContractPDFData(PDFData):
}


def create_hitas_contract_pdf(
def get_hitas_contract_pdf_data(
apartment: ApartmentDocument,
reservation: ApartmentReservation,
sales_price_paid_place: str,
sales_price_paid_time: str,
salesperson: User,
) -> BytesIO:
) -> Union[HitasContractPDFData, HitasCompleteApartmentContractPDFData]:
customer = SafeAttributeObject(reservation.customer)
primary_profile = SafeAttributeObject(customer.primary_profile)
secondary_profile = SafeAttributeObject(customer.secondary_profile)
apartment: ApartmentDocument = SafeAttributeObject(
get_apartment(reservation.apartment_uuid, include_project_fields=True)
)

# use contract for complete apartment
# can possibly be None, use bool() to convert to False in that case
Expand Down Expand Up @@ -629,14 +627,39 @@ def hitas_price(cents: Union[int, None]) -> Union[PDFCurrencyField, None]:
}

contract_dataclass = HitasContractPDFData
pdf_template_path = HITAS_CONTRACT_PDF_TEMPLATE_FILE_NAME

if complete_apartment:
contract_dataclass = HitasCompleteApartmentContractPDFData
contract_data = full_apartment_contract_data
pdf_template_path = HITAS_COMPLETE_APARTMENT_CONTRACT_PDF_TEMPLATE_FILE_NAME

pdf_data = contract_dataclass(**contract_data)

return pdf_data


def create_hitas_contract_pdf(
reservation: ApartmentReservation,
sales_price_paid_place: str,
sales_price_paid_time: str,
salesperson: User,
) -> BytesIO:
apartment: ApartmentDocument = SafeAttributeObject(
get_apartment(reservation.apartment_uuid, include_project_fields=True)
)

pdf_data = get_hitas_contract_pdf_data(
apartment=apartment,
reservation=reservation,
sales_price_paid_place=sales_price_paid_place,
sales_price_paid_time=sales_price_paid_time,
salesperson=salesperson,
)
pdf_template_path = HITAS_CONTRACT_PDF_TEMPLATE_FILE_NAME
complete_apartment = bool(apartment.project_use_complete_contract)

if complete_apartment:
pdf_template_path = HITAS_COMPLETE_APARTMENT_CONTRACT_PDF_TEMPLATE_FILE_NAME

return create_hitas_contract_pdf_from_data(pdf_data, pdf_template_path)


Expand Down
80 changes: 79 additions & 1 deletion application_form/tests/pdf_utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
import re
import subprocess
from typing import List
from datetime import date
from typing import List, Union

import pytest
from faker import Faker

from apartment.elastic.documents import ApartmentDocument
from apartment.enums import OwnershipType
from apartment.tests.factories import ApartmentDocumentFactory
from application_form.models.reservation import ApartmentReservation
from application_form.pdf.haso import get_haso_contract_pdf_data, HasoContractPDFData
from application_form.pdf.hitas import (
get_hitas_contract_pdf_data,
HitasCompleteApartmentContractPDFData,
HitasContractPDFData,
)
from application_form.tests.factories import ApartmentReservationFactory
from invoicing.enums import InstallmentType
from invoicing.tests.factories import ApartmentInstallmentFactory
from users.tests.factories import UserFactory


def assert_pdf_has_text(pdf: bytes, text: str) -> bool:
Expand Down Expand Up @@ -51,3 +68,64 @@ def remove_pdf_id(pdf: bytes) -> bytes:
Remove the /ID entry from the PDF file.
"""
return re.sub(rb"/ID\s+\[<[^]]+>\]", b"", pdf)


def set_up_contract_pdf_test_data(
ownership_type: Union[OwnershipType, None] = OwnershipType.HASO,
apartment: Union[ApartmentDocument, None] = None,
reservation: Union[ApartmentReservation, None] = None,
salesperson: Union[str, None] = None,
sales_price_paid_place: Union[str, None] = None,
sales_price_paid_time: Union[str, None] = None,
) -> Union[
HitasContractPDFData, HitasCompleteApartmentContractPDFData, HasoContractPDFData
]: # noqa: E501

faker = Faker()
if not apartment:
apartment = ApartmentDocumentFactory(
project_ownership_type=ownership_type.value
)

if not reservation:
reservation = ApartmentReservationFactory(apartment_uuid=apartment.uuid)

installment_types = [
InstallmentType.PAYMENT_1,
InstallmentType.PAYMENT_2,
InstallmentType.PAYMENT_3,
InstallmentType.PAYMENT_4,
InstallmentType.PAYMENT_5,
InstallmentType.PAYMENT_6,
InstallmentType.PAYMENT_7,
]
for installment_type in installment_types:
ApartmentInstallmentFactory(
apartment_reservation=reservation,
value=100_000,
type=installment_type,
)
pass

if not salesperson:
salesperson = UserFactory()

if not sales_price_paid_place:
sales_price_paid_place = faker.city()

if not sales_price_paid_time:
sales_price_paid_time = f"{date.today():%d.%m.%Y}"

func = {
OwnershipType.HASO: get_haso_contract_pdf_data,
OwnershipType.HITAS: get_hitas_contract_pdf_data,
}[ownership_type]

pdf_data = func(
reservation,
salesperson=salesperson,
sales_price_paid_place=sales_price_paid_place,
sales_price_paid_time=sales_price_paid_time,
)

return pdf_data
25 changes: 13 additions & 12 deletions application_form/tests/test_pdf_haso.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,15 @@

import pytest

from apartment.enums import OwnershipType
from apartment.tests.factories import ApartmentDocumentFactory
from apartment_application_service.pdf import PDFCurrencyField as CF
from application_form.tests.factories import ApartmentReservationFactory
from users.tests.factories import UserFactory

from ..pdf.haso import (
create_haso_contract_pdf_from_data,
get_haso_contract_pdf_data,
HasoContractPDFData,
from ..pdf.haso import create_haso_contract_pdf_from_data, HasoContractPDFData
from .pdf_utils import (
get_cleaned_pdf_texts,
remove_pdf_id,
set_up_contract_pdf_test_data,
)
from .pdf_utils import get_cleaned_pdf_texts, remove_pdf_id

# This variable should be normally False, but can be set temporarily to
# True to override the expected test result PDF file. This is useful
Expand Down Expand Up @@ -83,18 +80,22 @@ def setUp(self) -> None:
def test_pdf_content_is_not_empty(self):
assert self.pdf_content

@pytest.mark.django_db
def test_payment_recipient_field_goes_on_pdf(self):

pass

@pytest.mark.django_db
def test_salesperson_signing_info_is_formatted_correctly(self):
"""Assert that the chosen salesperson's name and signing time/place get passed
correctly to the HASO contract PDF generation.
Small test mainly for TDD purposes."""
apt = ApartmentDocumentFactory(project_ownership_type=OwnershipType.HASO.value)
res = ApartmentReservationFactory(apartment_uuid=apt.uuid)

salesperson = UserFactory(first_name="Markku", last_name="Myyjä")
paid_place = "Helsinki"
paid_time = "10.9.2025"
pdf_data = get_haso_contract_pdf_data(
res,

pdf_data = set_up_contract_pdf_test_data(
salesperson=salesperson,
sales_price_paid_place=paid_place,
sales_price_paid_time=paid_time,
Expand Down
84 changes: 53 additions & 31 deletions invoicing/pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@

from apartment.elastic.documents import ApartmentDocument
from apartment.elastic.queries import get_apartment, get_project
from apartment.enums import OwnershipType
from apartment_application_service.pdf import create_pdf, PDFData
from customer.models import Customer
from invoicing.enums import InstallmentType
from invoicing.models import ApartmentInstallment

_logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -54,9 +56,9 @@ class InvoicePDFData(PDFData):
}


def create_invoice_pdf_from_installments(
installments: Union[QuerySet, List[ApartmentInstallment]]
):
def get_invoice_pdf_data_from_installment(
installment: ApartmentInstallment,
) -> InvoicePDFData:
@lru_cache
def get_cached_project(project_uuid: UUID):
return get_project(project_uuid)
Expand All @@ -65,35 +67,55 @@ def get_cached_project(project_uuid: UUID):
def get_cached_apartment(apartment_uuid: UUID) -> ApartmentDocument:
return get_apartment(apartment_uuid, include_project_fields=True)

reservation = installment.apartment_reservation
payer_name_and_address = _get_payer_name_and_address(
installment.apartment_reservation.customer
)
apartment = get_cached_apartment(reservation.apartment_uuid)
project = get_cached_project(apartment.project_uuid)

# override language to Finnish, as the user's browser settings etc.
# shouldn't affect the printed out PDFs
with translation.override("fi"):
apartment_text = (
_("Apartment")
+ f" {apartment.apartment_number}\n\n{installment.type}"
+ 20 * " "
+ str(installment.value).replace(".", ",")
+ " €"
)

final_installment_type = InstallmentType.PAYMENT_7
if apartment.project_ownership_type == OwnershipType.HASO.value:
final_installment_type = InstallmentType.RIGHT_OF_OCCUPANCY_PAYMENT_3
pass

payment_recipient = apartment.project_payment_recipient
if installment.type == final_installment_type:
payment_recipient = apartment.project_payment_recipient_final
pass

invoice_pdf_data = InvoicePDFData(
recipient=payment_recipient,
recipient_account_number=f"{project.project_contract_rs_bank or ''} "
f"{installment.account_number}".strip(),
payer_name_and_address=payer_name_and_address,
reference_number=installment.reference_number,
due_date=installment.due_date,
amount=installment.value,
apartment=apartment_text,
)

return invoice_pdf_data


def create_invoice_pdf_from_installments(
installments: Union[QuerySet, List[ApartmentInstallment]]
):

invoice_pdf_data_list = []
for installment in installments:
reservation = installment.apartment_reservation
payer_name_and_address = _get_payer_name_and_address(
installment.apartment_reservation.customer
)
apartment = get_cached_apartment(reservation.apartment_uuid)
project = get_cached_project(apartment.project_uuid)

# override language to Finnish, as the user's browser settings etc.
# shouldn't affect the printed out PDFs
with translation.override("fi"):
apartment_text = (
_("Apartment")
+ f" {apartment.apartment_number}\n\n{installment.type}"
+ 20 * " "
+ str(installment.value).replace(".", ",")
+ " €"
)

invoice_pdf_data = InvoicePDFData(
recipient=project.project_housing_company,
recipient_account_number=f"{project.project_contract_rs_bank or ''} "
f"{installment.account_number}".strip(),
payer_name_and_address=payer_name_and_address,
reference_number=installment.reference_number,
due_date=installment.due_date,
amount=installment.value,
apartment=apartment_text,
)
invoice_pdf_data = get_invoice_pdf_data_from_installment(installment)
invoice_pdf_data_list.append(invoice_pdf_data)

return create_pdf(INVOICE_PDF_TEMPLATE_FILE_NAME, invoice_pdf_data_list)
57 changes: 56 additions & 1 deletion invoicing/tests/test_pdf.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,66 @@
import pytest
from django.utils.translation import gettext_lazy as _

from apartment.enums import OwnershipType
from apartment.tests.factories import ApartmentDocumentFactory
from application_form.tests.factories import ApartmentReservationFactory
from customer.tests.factories import CustomerFactory
from invoicing.pdf import _get_payer_name_and_address
from invoicing.enums import InstallmentType
from invoicing.pdf import (
_get_payer_name_and_address,
get_invoice_pdf_data_from_installment,
)
from invoicing.tests.factories import ApartmentInstallmentFactory
from users.tests.factories import ProfileFactory


@pytest.mark.django_db
@pytest.mark.parametrize("ownership_type", (OwnershipType.HASO, OwnershipType.HITAS))
def test_pdf_payment_recipients_set_correctly(ownership_type):
apartment = ApartmentDocumentFactory(
project_ownership_type=ownership_type.value,
project_payment_recipient="Payment recipient",
project_payment_recipient_final="Final payment recipient",
)
reservation = ApartmentReservationFactory(apartment_uuid=apartment.uuid)

first_installment_type = InstallmentType.PAYMENT_1
final_installment_type = InstallmentType.PAYMENT_7

if ownership_type == OwnershipType.HASO:
first_installment_type = InstallmentType.RIGHT_OF_OCCUPANCY_PAYMENT
final_installment_type = InstallmentType.RIGHT_OF_OCCUPANCY_PAYMENT_3

first_installment = ApartmentInstallmentFactory(
apartment_reservation=reservation,
value=100_000,
type=first_installment_type,
)

final_installment = ApartmentInstallmentFactory(
apartment_reservation=reservation,
value=100_000,
type=final_installment_type,
)

first_installment_pdf_data = get_invoice_pdf_data_from_installment(
first_installment
)
final_installment_pdf_data = get_invoice_pdf_data_from_installment(
final_installment
)

assert first_installment_pdf_data.recipient == apartment.project_payment_recipient
assert (
final_installment_pdf_data.recipient
== apartment.project_payment_recipient_final
) # noqa: E501

# assert the final payment still gets the final recipient

pass


@pytest.mark.django_db
def test_pdf_payer_name_address_correct():
customer = CustomerFactory(
Expand Down