67 lines
2.3 KiB
Python
67 lines
2.3 KiB
Python
"""Groups a year's lines by category with computed figures; shared by the API and every report."""
|
|||
|
|
from dataclasses import dataclass, field
|
||
|
|
|
||
|
|
from sqlalchemy import select
|
||
|
|
from sqlalchemy.orm import Session, selectinload
|
||
|
|
|
||
|
|
from .calc import LineFigures, Totals, line_figures
|
||
|
|
from .models import BudgetYear, Category, CostLine
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass
|
||
|
|
class LineView:
|
||
|
|
line: CostLine
|
||
|
|
figures: LineFigures
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass
|
||
|
|
class CategoryView:
|
||
|
|
category: Category
|
||
|
|
lines: list[LineView] = field(default_factory=list)
|
||
|
|
totals: Totals = field(default_factory=Totals)
|
||
|
|
|
||
|
|
def name(self, lang: str) -> str:
|
||
|
|
return (self.category.name_pt if lang == "pt" else "") or self.category.name_en
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass
|
||
|
|
class YearView:
|
||
|
|
year: BudgetYear
|
||
|
|
categories: list[CategoryView]
|
||
|
|
revenue: Totals
|
||
|
|
costs: Totals
|
||
|
|
|
||
|
|
def with_lines(self, section: str | None = None) -> list[CategoryView]:
|
||
|
|
return [c for c in self.categories if c.lines and (section is None or c.category.section == section)]
|
||
|
|
|
||
|
|
|
||
|
|
def build_year_view(db: Session, year: BudgetYear, site_id: int | None = None) -> YearView:
|
||
|
|
categories = db.scalars(select(Category).where(Category.department_id == year.department_id)
|
||
|
|
.order_by(Category.sort_order, Category.id)).all()
|
||
|
|
lines = db.scalars(
|
||
|
|
select(CostLine)
|
||
|
|
.where(CostLine.year_id == year.id)
|
||
|
|
.options(selectinload(CostLine.site), selectinload(CostLine.category))
|
||
|
|
.order_by(CostLine.sort_order, CostLine.id)
|
||
|
|
).all()
|
||
|
|
views = {c.id: CategoryView(c) for c in categories}
|
||
|
|
for line in lines:
|
||
|
|
if site_id is not None and line.site_id != site_id:
|
||
|
|
continue
|
||
|
|
lv = LineView(line, line_figures(line, year.default_rate))
|
||
|
|
cv = views.get(line.category_id)
|
||
|
|
if cv is None: # a line whose category was moved to another department
|
||
|
|
cv = views[line.category_id] = CategoryView(line.category)
|
||
|
|
cv.lines.append(lv)
|
||
|
|
cv.totals.add(lv.figures)
|
||
|
|
revenue, costs = Totals(), Totals()
|
||
|
|
shown = []
|
||
|
|
for cv in views.values():
|
||
|
|
if not cv.lines and not (cv.category.active and cv.category.show_when_empty):
|
||
|
|
continue
|
||
|
|
shown.append(cv)
|
||
|
|
target = revenue if cv.category.section == "revenue" else costs
|
||
|
|
for lv in cv.lines:
|
||
|
|
target.add(lv.figures)
|
||
|
|
return YearView(year, shown, revenue, costs)
|