Section groups with collapsible headers + Sinking Funds #12
2 changed files with 84 additions and 1 deletions
|
|
@ -9,7 +9,15 @@ from enum import Enum
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from quartermaster.groups import (
|
||||||
|
GROUP_DEFAULT_OPEN,
|
||||||
|
GROUP_LABELS,
|
||||||
|
Group,
|
||||||
|
group_order,
|
||||||
|
sections_in_group,
|
||||||
|
)
|
||||||
from quartermaster.models import (
|
from quartermaster.models import (
|
||||||
|
SECTION_LABELS,
|
||||||
DebtTarget,
|
DebtTarget,
|
||||||
Entry,
|
Entry,
|
||||||
Month,
|
Month,
|
||||||
|
|
@ -48,6 +56,16 @@ class ZeroAmounts:
|
||||||
applied: Decimal
|
applied: Decimal
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class MonthGroupView:
|
||||||
|
group: Group
|
||||||
|
label: str
|
||||||
|
default_open: bool
|
||||||
|
sections: list[MonthSectionView]
|
||||||
|
total_planned: Decimal
|
||||||
|
total_applied: Decimal
|
||||||
|
|
||||||
|
|
||||||
def valid_year_month(year_month: str) -> bool:
|
def valid_year_month(year_month: str) -> bool:
|
||||||
return bool(YEAR_MONTH_RE.match(year_month))
|
return bool(YEAR_MONTH_RE.match(year_month))
|
||||||
|
|
||||||
|
|
@ -148,6 +166,28 @@ def month_zero(month: Month) -> ZeroAmounts:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def month_group_views(month: Month) -> list[MonthGroupView]:
|
||||||
|
views: list[MonthGroupView] = []
|
||||||
|
for group in group_order():
|
||||||
|
sections = [
|
||||||
|
section_view(month, s, SECTION_LABELS[s])
|
||||||
|
for s in sections_in_group(group)
|
||||||
|
]
|
||||||
|
planned = sum((sv.total_planned for sv in sections), Decimal("0"))
|
||||||
|
applied = sum((sv.total_applied for sv in sections), Decimal("0"))
|
||||||
|
views.append(
|
||||||
|
MonthGroupView(
|
||||||
|
group=group,
|
||||||
|
label=GROUP_LABELS[group],
|
||||||
|
default_open=GROUP_DEFAULT_OPEN[group],
|
||||||
|
sections=sections,
|
||||||
|
total_planned=planned,
|
||||||
|
total_applied=applied,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return views
|
||||||
|
|
||||||
|
|
||||||
def section_view(month: Month, section: Section, label: str) -> MonthSectionView:
|
def section_view(month: Month, section: Section, label: str) -> MonthSectionView:
|
||||||
entries = [e for e in month.entries if e.section == section]
|
entries = [e for e in month.entries if e.section == section]
|
||||||
entries.sort(key=lambda e: e.id)
|
entries.sort(key=lambda e: e.id)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,14 @@ from decimal import Decimal
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from quartermaster.models import DebtTarget, Entry, Section
|
from quartermaster.groups import (
|
||||||
|
GROUP_DEFAULT_OPEN,
|
||||||
|
GROUP_LABELS,
|
||||||
|
Group,
|
||||||
|
group_order,
|
||||||
|
sections_in_group,
|
||||||
|
)
|
||||||
|
from quartermaster.models import SECTION_LABELS, DebtTarget, Entry, Section
|
||||||
|
|
||||||
|
|
||||||
def zero_tone(value: Decimal) -> str:
|
def zero_tone(value: Decimal) -> str:
|
||||||
|
|
@ -23,6 +30,15 @@ class SectionView:
|
||||||
total: Decimal
|
total: Decimal
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class BudgetGroupView:
|
||||||
|
group: Group
|
||||||
|
label: str
|
||||||
|
default_open: bool
|
||||||
|
sections: list[SectionView]
|
||||||
|
total: Decimal
|
||||||
|
|
||||||
|
|
||||||
def list_entries(db: Session, section: Section) -> list[Entry]:
|
def list_entries(db: Session, section: Section) -> list[Entry]:
|
||||||
stmt = select(Entry).where(Entry.section == section).order_by(Entry.id)
|
stmt = select(Entry).where(Entry.section == section).order_by(Entry.id)
|
||||||
return list(db.scalars(stmt))
|
return list(db.scalars(stmt))
|
||||||
|
|
@ -32,6 +48,33 @@ def section_total(entries: list[Entry]) -> Decimal:
|
||||||
return sum((e.amount for e in entries), Decimal("0"))
|
return sum((e.amount for e in entries), Decimal("0"))
|
||||||
|
|
||||||
|
|
||||||
|
def section_view(db: Session, section: Section) -> SectionView:
|
||||||
|
entries = list_entries(db, section)
|
||||||
|
return SectionView(
|
||||||
|
section=section,
|
||||||
|
label=SECTION_LABELS[section],
|
||||||
|
entries=entries,
|
||||||
|
total=section_total(entries),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def budget_group_views(db: Session) -> list[BudgetGroupView]:
|
||||||
|
views: list[BudgetGroupView] = []
|
||||||
|
for group in group_order():
|
||||||
|
sections = [section_view(db, s) for s in sections_in_group(group)]
|
||||||
|
total = sum((sv.total for sv in sections), Decimal("0"))
|
||||||
|
views.append(
|
||||||
|
BudgetGroupView(
|
||||||
|
group=group,
|
||||||
|
label=GROUP_LABELS[group],
|
||||||
|
default_open=GROUP_DEFAULT_OPEN[group],
|
||||||
|
sections=sections,
|
||||||
|
total=total,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return views
|
||||||
|
|
||||||
|
|
||||||
def budget_zero(db: Session) -> Decimal:
|
def budget_zero(db: Session) -> Decimal:
|
||||||
stmt = select(Entry)
|
stmt = select(Entry)
|
||||||
total_income = Decimal("0")
|
total_income = Decimal("0")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue