From 5ebdffc0ec19dd9a927e69b6fc6c068d694fbe0b Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Thu, 30 Jul 2026 03:59:58 +0200 Subject: [PATCH] feat: expose organization hierarchy catalog --- .../backend/directory.py | 30 +++++++++++++++++++ tests/test_hierarchy_directory.py | 23 ++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/govoplan_organizations/backend/directory.py b/src/govoplan_organizations/backend/directory.py index 51e754d..1a528fb 100644 --- a/src/govoplan_organizations/backend/directory.py +++ b/src/govoplan_organizations/backend/directory.py @@ -14,6 +14,7 @@ from govoplan_core.core.organizations import ( OrganizationFunctionRef, OrganizationFunctionTypeRef, OrganizationFunctionTypeResolution, + OrganizationHierarchyCatalogRef, OrganizationHierarchyDirection, OrganizationHierarchyDirectory, OrganizationHierarchyEdgeRef, @@ -279,6 +280,35 @@ class SqlOrganizationDirectory( return None return _unit_type_ref(item) + def hierarchy_catalog( + self, + tenant_id: str, + ) -> OrganizationHierarchyCatalogRef: + with self._session() as session: + structures = ( + session.query(OrganizationStructure) + .filter(OrganizationStructure.tenant_id == tenant_id) + .order_by(OrganizationStructure.name, OrganizationStructure.id) + .all() + ) + relation_types = ( + session.query(OrganizationRelationType) + .filter(OrganizationRelationType.tenant_id == tenant_id) + .order_by( + OrganizationRelationType.structure_id, + OrganizationRelationType.name, + OrganizationRelationType.id, + ) + .all() + ) + return OrganizationHierarchyCatalogRef( + tenant_id=tenant_id, + structures=tuple(_structure_ref(item) for item in structures), + relation_types=tuple( + _relation_type_ref(item) for item in relation_types + ), + ) + def get_function_type( self, tenant_id: str, diff --git a/tests/test_hierarchy_directory.py b/tests/test_hierarchy_directory.py index a7374dc..7e445fb 100644 --- a/tests/test_hierarchy_directory.py +++ b/tests/test_hierarchy_directory.py @@ -240,6 +240,29 @@ class OrganizationHierarchyDirectoryTests(unittest.TestCase): project.matches[0].path[0].relation_type.id, ) + def test_hierarchy_catalog_is_tenant_scoped_and_preserves_structure_links( + self, + ) -> None: + catalog = self.directory.hierarchy_catalog("tenant-1") + empty = self.directory.hierarchy_catalog("tenant-2") + + self.assertEqual( + {self.employer.id, self.project.id}, + {item.id for item in catalog.structures}, + ) + self.assertEqual( + { + (self.employer_parent.id, self.employer.id), + (self.project_parent.id, self.project.id), + }, + { + (item.id, item.structure_id) + for item in catalog.relation_types + }, + ) + self.assertEqual((), empty.structures) + self.assertEqual((), empty.relation_types) + def test_bounded_paths_report_depth_and_cycles(self) -> None: bounded = self.directory.resolve_hierarchy_relatives( "tenant-1",