Files
aurum/backend/app/schemas/reports.py
ZProger 140f58d33f fix: restrict transaction splits to one parent category, show subcategory breakdown
Раньше сплит позволял смешивать вообще любые категории. Реальная задача —
разложить одну покупку по подкатегориям ОДНОЙ базовой категории (100 € на
продукты: сколько на сладости, сколько на уход). А раз Dashboard и рейтинг
категорий сворачивают подкатегории в родителя, эта разбивка нигде не была
видна.

- _build_splits() требует, чтобы все категории сплита были одной родительской
  категорией или её прямыми подкатегориями.
- category_rollup.py отдаёт для каждого родительского тотала список
  category-источников, которые в него скинулись (children) — пусто, если
  источник один.
- Dashboard и рейтинг категорий: строка с непустым children раскрывается по
  клику, показывая вклад каждой подкатегории.
- Форма транзакции: выбор категории при сплите — сначала базовая (верхний
  уровень), затем строки сплита выбирают саму базовую или её подкатегорию.
2026-08-30 19:06:29 +03:00

57 lines
1.4 KiB
Python

from datetime import date as date_
from decimal import Decimal
from pydantic import BaseModel, Field
class CategoryRankingChildItem(BaseModel):
"""One subcategory's (or the parent's own direct, un-subcategorized)
share of a CategoryRankingItem's total — see
services/category_rollup.py's CategoryRollupChildItem."""
category_id: int
name: str
color: str
icon: str | None
amount: Decimal
class CategorySpendingPoint(BaseModel):
year: int
month: int
amount: Decimal
class CategorySpendingReport(BaseModel):
category_id: int
category_name: str
category_color: str
category_icon: str | None
start_date: date_ | None
end_date: date_ | None
total_amount: Decimal
transaction_count: int
average_per_month: Decimal
series: list[CategorySpendingPoint]
class CategoryRankingItem(BaseModel):
category_id: int
name: str
color: str
icon: str | None
amount: Decimal
percent: float
transaction_count: int
# Populated only when this category's spend came from more than one
# distinct category (subcategories, or a mix of itself and its
# children) — e.g. a receipt split across "Groceries" subcategories.
children: list[CategoryRankingChildItem] = Field(default_factory=list)
class CategoryRankingReport(BaseModel):
start_date: date_ | None
end_date: date_ | None
total_amount: Decimal
items: list[CategoryRankingItem]