Files
finance/backend/app/analytics/service.py
T
k1nq d582f15681 feat: add tests for import polling, login page, money utilities, and query keys
- Implemented tests for import polling functionality, ensuring correct behavior for active and terminal import statuses.
- Created tests for the login page, validating user interactions and error handling.
- Added unit tests for money utility functions, covering parsing, formatting, and summation of monetary values.
- Developed tests for query keys, ensuring proper scoping and invalidation roots.

chore: setup testing environment and configuration

- Added setup file for testing with Vitest and Testing Library.
- Configured TypeScript and Vite for the frontend project.
- Created Docker and Nginx configurations for local development and production environments.
- Established a PostgreSQL initialization script for setting up application roles.

docs: add README and environment configuration for infra

- Included README for the infra directory, detailing local development setup and service descriptions.
- Created an example environment configuration file for local development with placeholders for sensitive information.
2026-09-14 09:26:33 +05:00

106 lines
3.7 KiB
Python

import uuid
from collections import defaultdict
from datetime import date
from sqlalchemy import func
from sqlalchemy.orm import Session
from app.budgeting.models import PeriodClosure
from app.budgeting.service import compute_period_state, get_or_create_period
from app.categorization.service import resolve_category_name
from app.ledger.models import Account, Category, Operation
from app.ledger.service import account_balance
def category_spend(
db: Session, *, space_id: uuid.UUID, date_from: date, date_to: date
) -> list[dict]:
expenses = (
db.query(Operation)
.filter(
Operation.space_id == space_id,
Operation.status == "confirmed",
Operation.type == "expense",
Operation.occurred_on >= date_from,
Operation.occurred_on <= date_to,
)
.all()
)
totals: dict[uuid.UUID | None, int] = defaultdict(int)
for expense in expenses:
refunded = (
db.query(func.coalesce(func.sum(Operation.amount_kopecks), 0))
.filter(
Operation.space_id == space_id,
Operation.status == "confirmed",
Operation.type == "refund",
Operation.parent_operation_id == expense.id,
)
.scalar()
)
totals[expense.category_id] += expense.amount_kopecks - int(refunded or 0)
categories = {
item.id: resolve_category_name(item)
for item in db.query(Category).filter_by(space_id=space_id).all()
}
return [
{
"category_id": category_id,
"category": (
categories.get(category_id, "category.unnamed")
if category_id is not None
else "category.uncategorized"
),
"amount_kopecks": amount,
}
for category_id, amount in sorted(totals.items(), key=lambda item: item[1], reverse=True)
]
def cash_flow(db: Session, *, space_id: uuid.UUID, date_from: date, date_to: date) -> list[dict]:
operations = (
db.query(Operation)
.filter(Operation.space_id == space_id, Operation.status == "confirmed")
.all()
)
totals: dict[tuple[date, str], int] = defaultdict(int)
for operation in operations:
effective = operation.posted_on or operation.occurred_on
if not date_from <= effective <= date_to or operation.type == "transfer":
continue
signed = (
operation.amount_kopecks
if operation.type in {"income", "refund"}
else -operation.amount_kopecks
)
totals[(effective, operation.type)] += signed
return [
{"date": day, "type": kind, "amount_kopecks": amount}
for (day, kind), amount in sorted(totals.items())
]
def plan_fact(db: Session, *, space_id: uuid.UUID, year: int, month: int) -> dict:
period = get_or_create_period(db, space_id=space_id, year=year, month=month)
if period.status == "closed":
closure = db.query(PeriodClosure).filter_by(period_id=period.id).one_or_none()
if closure is not None:
return closure.snapshot
return compute_period_state(db, space_id=space_id, period=period)
def balances(db: Session, *, space_id: uuid.UUID) -> list[dict]:
result = []
for account in db.query(Account).filter_by(space_id=space_id).order_by(Account.created_at):
balance = account_balance(db, account)
result.append(
{
"account_id": account.id,
"account": account.name,
"kind": account.kind,
"balance_kopecks": balance,
"debt_kopecks": max(-balance, 0) if account.kind == "credit" else 0,
}
)
return result