From f3e903a282355fe619d094bb2e1b3db9314054ab Mon Sep 17 00:00:00 2001 From: indigane Date: Mon, 10 Feb 2025 13:27:59 +0200 Subject: [PATCH] Add property managers report --- backend/hitas/services/reports.py | 60 ++++++++++++++++++++ backend/hitas/tests/apis/test_api_reports.py | 50 ++++++++++++++++ backend/hitas/urls.py | 7 +++ backend/hitas/views/__init__.py | 1 + backend/hitas/views/reports.py | 23 ++++++++ backend/openapi.yaml | 21 +++++++ 6 files changed, 162 insertions(+) diff --git a/backend/hitas/services/reports.py b/backend/hitas/services/reports.py index d612df19..d657ace5 100644 --- a/backend/hitas/services/reports.py +++ b/backend/hitas/services/reports.py @@ -15,6 +15,7 @@ from hitas.models import ApartmentSale, Owner from hitas.models.housing_company import ( HitasType, + HousingCompany, HousingCompanyWithRegulatedReportAnnotations, HousingCompanyWithStateReportAnnotations, HousingCompanyWithUnregulatedReportAnnotations, @@ -22,6 +23,7 @@ ) from hitas.models.indices import SurfaceAreaPriceCeiling from hitas.models.ownership import Ownership, OwnershipWithApartmentCount +from hitas.models.property_manager import PropertyManager from hitas.utils import format_sheet, resize_columns T = TypeVar("T") @@ -71,6 +73,12 @@ class UnregulatedHousingCompaniesReportColumns(NamedTuple): apartment_count: int | str +class PropertyManagerReportColumns(NamedTuple): + property_manager_name: str + property_manager_email: str + housing_company_name: str + + class HousingCompanyStatesCount(TypedDict): housing_company_count: int apartment_count: int @@ -705,6 +713,58 @@ def build_unregulated_housing_companies_report_excel( return workbook +def build_property_managers_report_excel( + housing_companies: list[HousingCompany], + property_managers_with_no_housing_company: list[PropertyManager], +) -> Workbook: + workbook = Workbook() + worksheet: Worksheet = workbook.active + + column_headers = PropertyManagerReportColumns( + property_manager_name="Isännöitsijätoimisto/isännöitsijä", + property_manager_email="Sähköpostiosoite", + housing_company_name="Taloyhtiön nimi", + ) + worksheet.append(column_headers) + + for housing_company in housing_companies: + worksheet.append( + PropertyManagerReportColumns( + property_manager_name=housing_company.property_manager.name, + property_manager_email=housing_company.property_manager.email, + housing_company_name=housing_company.display_name, + ) + ) + + for property_manager in property_managers_with_no_housing_company: + worksheet.append( + PropertyManagerReportColumns( + property_manager_name=property_manager.name, + property_manager_email=property_manager.email, + housing_company_name="", + ) + ) + + last_row = worksheet.max_row + worksheet.auto_filter.ref = worksheet.dimensions + + column_letters = string.ascii_uppercase[: len(column_headers)] + + format_sheet( + worksheet, + formatting_rules={ + # Add a border to the header row + **{f"{letter}1": {"border": Border(bottom=Side(style="thin"))} for letter in column_letters}, + # Add a border to the last data row + **{f"{letter}{last_row}": {"border": Border(bottom=Side(style="thin"))} for letter in column_letters}, + }, + ) + + resize_columns(worksheet) + worksheet.protection.sheet = True + return workbook + + def build_housing_company_state_report_excel( housing_companies: list[HousingCompanyWithStateReportAnnotations], ) -> Workbook: diff --git a/backend/hitas/tests/apis/test_api_reports.py b/backend/hitas/tests/apis/test_api_reports.py index 21bb3635..d2bf4cdf 100644 --- a/backend/hitas/tests/apis/test_api_reports.py +++ b/backend/hitas/tests/apis/test_api_reports.py @@ -20,6 +20,7 @@ ThirtyYearRegulationResultsRow, ) from hitas.models.housing_company import HitasType, RegulationStatus +from hitas.models.property_manager import PropertyManager from hitas.models.thirty_year_regulation import FullSalesData, RegulationResult from hitas.tests.apis.helpers import HitasAPIClient from hitas.tests.factories import ( @@ -31,6 +32,7 @@ ) from hitas.tests.factories.apartment import ApartmentMaximumPriceCalculationFactory from hitas.tests.factories.indices import SurfaceAreaPriceCeilingFactory +from hitas.tests.factories.property_manager import PropertyManagerFactory # Sales report @@ -1011,6 +1013,54 @@ def test__api__unregulated_housing_companies_report__multiple_housing_companies( ] +# Property managers and their housing companies report + + +@pytest.mark.django_db +def test__api__property_managers_report(api_client: HitasAPIClient): + property_manager_1: PropertyManager = PropertyManagerFactory.create(name="Property Manager A") + housing_company_1: HousingCompany = HousingCompanyFactory.create(property_manager=property_manager_1) + + property_manager_2: PropertyManager = PropertyManagerFactory.create(name="Property Manager B") + housing_company_2: HousingCompany = HousingCompanyFactory.create(property_manager=property_manager_2) + housing_company_3: HousingCompany = HousingCompanyFactory.create(property_manager=property_manager_2) + + property_manager_3: PropertyManager = PropertyManagerFactory.create(name="Property Manager C") + housing_company_4: HousingCompany = HousingCompanyFactory.create(property_manager=property_manager_3) + + property_manager_4: PropertyManager = PropertyManagerFactory.create(name="Property Manager A2") + + url = reverse("hitas:property-managers-report-list") + response: HttpResponse = api_client.get(url) + + workbook: Workbook = load_workbook(BytesIO(response.content), data_only=False) + worksheet: Worksheet = workbook.worksheets[0] + + rows = list(worksheet.values) + assert len(rows[0][0]) > 0, "Row 1 column 1 should have a title" + assert len(rows[0][1]) > 0, "Row 1 column 2 should have a title" + assert len(rows[0][2]) > 0, "Row 1 column 3 should have a title" + # Property manager 1 + assert rows[1][0] == property_manager_1.name + assert rows[1][1] == property_manager_1.email + assert rows[1][2] == housing_company_1.display_name + # Property manager 2 - Two housing companies + assert rows[2][0] == property_manager_2.name + assert rows[2][1] == property_manager_2.email + assert rows[2][2] == housing_company_2.display_name + assert rows[3][0] == property_manager_2.name + assert rows[3][1] == property_manager_2.email + assert rows[3][2] == housing_company_3.display_name + # Property manager 3 + assert rows[4][0] == property_manager_3.name + assert rows[4][1] == property_manager_3.email + assert rows[4][2] == housing_company_4.display_name + # Property manager 4 - No housing company + assert rows[5][0] == property_manager_4.name + assert rows[5][1] == property_manager_4.email + assert rows[5][2] is None + + # Housing company state report diff --git a/backend/hitas/urls.py b/backend/hitas/urls.py index 00d5eb5b..acc2f174 100644 --- a/backend/hitas/urls.py +++ b/backend/hitas/urls.py @@ -106,6 +106,13 @@ basename="unregulated-housing-companies-report", ) +# /api/v1/reports/download-property-managers-report +router.register( + r"reports/download-property-managers-report", + views.PropertyManagersReportView, + basename="property-managers-report", +) + # /api/v1/reports/housing-company-states router.register( r"reports/housing-company-states", diff --git a/backend/hitas/views/__init__.py b/backend/hitas/views/__init__.py index bdb3686f..140efdbb 100644 --- a/backend/hitas/views/__init__.py +++ b/backend/hitas/views/__init__.py @@ -36,6 +36,7 @@ MultipleOwnershipsReportView, OwnershipsByCompanyJSONReportView, OwnershipsByHousingCompanyReport, + PropertyManagersReportView, RegulatedHousingCompaniesReportView, RegulatedOwnershipsReportView, SalesAndMaximumPricesReportView, diff --git a/backend/hitas/views/reports.py b/backend/hitas/views/reports.py index ebd8af1b..7bed01b9 100644 --- a/backend/hitas/views/reports.py +++ b/backend/hitas/views/reports.py @@ -8,6 +8,8 @@ from rest_framework.viewsets import ViewSet from hitas.models import HousingCompany, Owner, Ownership +from hitas.models.housing_company import RegulationStatus +from hitas.models.property_manager import PropertyManager from hitas.services.apartment_sale import find_sales_on_interval_for_reporting from hitas.services.housing_company import ( find_half_hitas_housing_companies_for_reporting, @@ -24,6 +26,7 @@ build_housing_company_state_report_excel, build_multiple_ownerships_report_excel, build_owners_by_housing_companies_report_excel, + build_property_managers_report_excel, build_regulated_housing_companies_report_excel, build_regulated_ownerships_report_excel, build_sales_and_maximum_prices_report_excel, @@ -111,6 +114,26 @@ def list(self, request: Request, *args, **kwargs) -> HttpResponse: return get_excel_response(filename=filename, excel=workbook) +class PropertyManagersReportView(ViewSet): + renderer_classes = [HitasJSONRenderer, ExcelRenderer] + + def list(self, request: Request, *args, **kwargs) -> HttpResponse: + housing_companies = list( + HousingCompany.objects.filter( + property_manager__isnull=False, + regulation_status=RegulationStatus.REGULATED, + ) + .select_related("property_manager") + .order_by("property_manager__name") + ) + property_managers_with_no_housing_company = list( + PropertyManager.objects.filter(housing_companies__isnull=True).order_by("name") + ) + workbook = build_property_managers_report_excel(housing_companies, property_managers_with_no_housing_company) + filename = "Isännöitsijät.xlsx" + return get_excel_response(filename=filename, excel=workbook) + + class HousingCompanyStatesReportView(ViewSet): renderer_classes = [HitasJSONRenderer, ExcelRenderer] diff --git a/backend/openapi.yaml b/backend/openapi.yaml index cdd2ae31..d2d2011b 100644 --- a/backend/openapi.yaml +++ b/backend/openapi.yaml @@ -4631,6 +4631,27 @@ paths: "500": $ref: "#/components/responses/InternalServerError" + /api/v1/reports/download-property-managers-report: + get: + description: Download an Excel report of property managers and their housing companies + operationId: fetch-property-managers-report-excel + tags: + - Reports + responses: + "200": + description: Successfully downloaded a report of property managers + content: + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: + schema: + type: string + format: binary + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalServerError" + /api/v1/reports/housing-company-states: get: description: Get housing companies counted by their states