diff --git a/apartment/elastic/documents.py b/apartment/elastic/documents.py index f34d99a4..179a40e6 100644 --- a/apartment/elastic/documents.py +++ b/apartment/elastic/documents.py @@ -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) diff --git a/application_form/pdf/hitas.py b/application_form/pdf/hitas.py index edc32410..5ddfbca9 100644 --- a/application_form/pdf/hitas.py +++ b/application_form/pdf/hitas.py @@ -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 @@ -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) diff --git a/application_form/tests/pdf_utils.py b/application_form/tests/pdf_utils.py index 97945367..49d450e0 100644 --- a/application_form/tests/pdf_utils.py +++ b/application_form/tests/pdf_utils.py @@ -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: @@ -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 diff --git a/application_form/tests/test_pdf_haso.py b/application_form/tests/test_pdf_haso.py index b744907e..30c168ca 100644 --- a/application_form/tests/test_pdf_haso.py +++ b/application_form/tests/test_pdf_haso.py @@ -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 @@ -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, diff --git a/invoicing/pdf.py b/invoicing/pdf.py index 309ddf17..562a19ce 100644 --- a/invoicing/pdf.py +++ b/invoicing/pdf.py @@ -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__) @@ -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) @@ -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) diff --git a/invoicing/tests/test_pdf.py b/invoicing/tests/test_pdf.py index 50e93128..fbcd0e00 100644 --- a/invoicing/tests/test_pdf.py +++ b/invoicing/tests/test_pdf.py @@ -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(