category_tree.py 1.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
  1. """Build nested category tree from global_tree_category."""
  2. from __future__ import annotations
  3. from typing import Any
  4. from supply_infra.db.models.global_tree_category import GlobalTreeCategory
  5. from supply_infra.db.repositories.global_tree_category_repo import GlobalTreeCategoryRepository
  6. from supply_infra.db.session import get_session
  7. def _normalize_parent_id(parent_id: int | None) -> int | None:
  8. if parent_id is None or parent_id == 0:
  9. return None
  10. return parent_id
  11. def _build_children_map(
  12. categories: list[GlobalTreeCategory],
  13. ) -> dict[int | None, list[GlobalTreeCategory]]:
  14. children_map: dict[int | None, list[GlobalTreeCategory]] = {}
  15. for category in categories:
  16. parent_key = _normalize_parent_id(category.parent_id)
  17. children_map.setdefault(parent_key, []).append(category)
  18. for children in children_map.values():
  19. children.sort(key=lambda c: (c.level or 0, c.id))
  20. return children_map
  21. def _to_node(
  22. category: GlobalTreeCategory,
  23. children_map: dict[int | None, list[GlobalTreeCategory]],
  24. ) -> dict[str, Any]:
  25. children = [
  26. _to_node(child, children_map) for child in children_map.get(category.id, [])
  27. ]
  28. return {
  29. "id": category.id,
  30. "name": category.name,
  31. "level": category.level,
  32. "description": category.description,
  33. "children": children,
  34. }
  35. def build_category_tree() -> list[dict[str, Any]]:
  36. """Load all active categories and return nested root nodes."""
  37. with get_session() as session:
  38. repo = GlobalTreeCategoryRepository(session)
  39. categories = repo.list_active_categories()
  40. # Build JSON while session is open to avoid DetachedInstanceError.
  41. children_map = _build_children_map(categories)
  42. return [_to_node(root, children_map) for root in children_map.get(None, [])]