d582f15681
- 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.
110 lines
3.4 KiB
Python
110 lines
3.4 KiB
Python
from datetime import date
|
|
|
|
from fastapi import APIRouter, Query
|
|
|
|
from app.analytics import service
|
|
from app.dependencies import Context, Db
|
|
|
|
router = APIRouter(prefix="/analytics", tags=["analytics"])
|
|
|
|
|
|
@router.get("/category-spend")
|
|
def category_spend(
|
|
date_from: date = Query(alias="from"),
|
|
date_to: date = Query(alias="to"),
|
|
*,
|
|
db: Db,
|
|
context: Context,
|
|
) -> list[dict]:
|
|
rows = service.category_spend(
|
|
db, space_id=context.space.id, date_from=date_from, date_to=date_to
|
|
)
|
|
return [
|
|
{
|
|
"categoryId": row["category_id"],
|
|
"name": row["category"] if not row["category"].startswith("category.") else None,
|
|
"nameKey": row["category"] if row["category"].startswith("category.") else None,
|
|
"amountKopecks": row["amount_kopecks"],
|
|
}
|
|
for row in rows
|
|
]
|
|
|
|
|
|
@router.get("/cash-flow")
|
|
def cash_flow(
|
|
date_from: date = Query(alias="from"),
|
|
date_to: date = Query(alias="to"),
|
|
*,
|
|
db: Db,
|
|
context: Context,
|
|
) -> list[dict]:
|
|
rows = service.cash_flow(db, space_id=context.space.id, date_from=date_from, date_to=date_to)
|
|
months: dict[str, dict[str, int | str]] = {}
|
|
for row in rows:
|
|
month = row["date"].strftime("%Y-%m")
|
|
bucket = months.setdefault(
|
|
month,
|
|
{
|
|
"month": month,
|
|
"incomeKopecks": 0,
|
|
"expenseKopecks": 0,
|
|
"refundsKopecks": 0,
|
|
"netKopecks": 0,
|
|
},
|
|
)
|
|
amount = int(row["amount_kopecks"])
|
|
key = {
|
|
"income": "incomeKopecks",
|
|
"expense": "expenseKopecks",
|
|
"refund": "refundsKopecks",
|
|
}[row["type"]]
|
|
bucket[key] = int(bucket[key]) + abs(amount)
|
|
bucket["netKopecks"] = int(bucket["netKopecks"]) + amount
|
|
return list(months.values())
|
|
|
|
|
|
@router.get("/plan-fact")
|
|
def plan_fact(month: str, db: Db, context: Context) -> dict:
|
|
from app.budgeting.router import parse_month
|
|
|
|
year, month_number = parse_month(month)
|
|
state = service.plan_fact(db, space_id=context.space.id, year=year, month=month_number)
|
|
categories = state.get("monthly_categories", [])
|
|
return {
|
|
"month": month,
|
|
"items": [
|
|
{
|
|
"categoryId": row["category_id"],
|
|
"name": row["name"] if not str(row["name"]).startswith("category.") else None,
|
|
"nameKey": row["name"] if str(row["name"]).startswith("category.") else None,
|
|
"plannedKopecks": row["planned"],
|
|
"actualKopecks": row["spent"],
|
|
}
|
|
for row in categories
|
|
],
|
|
}
|
|
|
|
|
|
@router.get("/balances")
|
|
def balances(db: Db, context: Context) -> dict:
|
|
from datetime import UTC, datetime
|
|
|
|
rows = service.balances(db, space_id=context.space.id)
|
|
assets = sum(max(row["balance_kopecks"], 0) for row in rows if row["kind"] != "credit")
|
|
liabilities = sum(row["debt_kopecks"] for row in rows)
|
|
return {
|
|
"asOf": datetime.now(UTC).date(),
|
|
"assetsKopecks": assets,
|
|
"liabilitiesKopecks": liabilities,
|
|
"netKopecks": assets - liabilities,
|
|
"items": [
|
|
{
|
|
"accountId": row["account_id"],
|
|
"name": row["account"],
|
|
"type": row["kind"],
|
|
"balanceKopecks": row["balance_kopecks"],
|
|
}
|
|
for row in rows
|
|
],
|
|
}
|