e3fac99045
Backend: - GET /api/v1/dashboard?month=YYYY-MM: KPIs, by_category, 6-month trend, budget alerts - GET/POST/PUT/DELETE /api/v1/budgets: budget envelopes with spent_cents/remaining_cents - POST /api/v1/budgets/rollover: copy budgets from M-1 to target month - GET /api/v1/history?year=YYYY: monthly summary for the year - GET /api/v1/export/csv|pdf?month=YYYY-MM: StreamingResponse exports (WeasyPrint PDF) - New schemas: dashboard, budget, history - Services: dashboard_service, budget_service - Routers mounted in main.py Frontend: - DashboardPage: 4 KPI cards, PieChart (expenses by category), BarChart (6-month trend), month navigation, budget alert badges, CSV/PDF export buttons - BudgetsPage: progress bars (green/orange/red), create/edit form, delete, rollover M-1 - HistoryPage: annual table with month click → dashboard, LineChart revenues/expenses - CategoriesPage: list by type with create/edit/delete (was missing from Phase 2) - TransactionsPage: added CSV/PDF export buttons - App.tsx: full routing with ProtectedRoute + Layout for all authenticated pages - New hooks: useDashboard, useBudgets (with mutations), useHistory - API types + client updated for all new endpoints Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
52 lines
1.2 KiB
Python
52 lines
1.2 KiB
Python
import uuid
|
|
from datetime import datetime
|
|
|
|
from pydantic import BaseModel, field_validator
|
|
|
|
|
|
class BudgetCreate(BaseModel):
|
|
category_id: uuid.UUID
|
|
month: str # YYYY-MM
|
|
limit_cents: int
|
|
|
|
@field_validator("limit_cents")
|
|
@classmethod
|
|
def limit_positive(cls, v: int) -> int:
|
|
if v <= 0:
|
|
raise ValueError("limit_cents must be positive")
|
|
return v
|
|
|
|
@field_validator("month")
|
|
@classmethod
|
|
def month_format(cls, v: str) -> str:
|
|
parts = v.split("-")
|
|
if len(parts) != 2 or not all(p.isdigit() for p in parts):
|
|
raise ValueError("month must be in YYYY-MM format")
|
|
return v
|
|
|
|
|
|
class BudgetUpdate(BaseModel):
|
|
limit_cents: int
|
|
|
|
@field_validator("limit_cents")
|
|
@classmethod
|
|
def limit_positive(cls, v: int) -> int:
|
|
if v <= 0:
|
|
raise ValueError("limit_cents must be positive")
|
|
return v
|
|
|
|
|
|
class BudgetResponse(BaseModel):
|
|
id: uuid.UUID
|
|
category_id: uuid.UUID
|
|
category_name: str
|
|
category_color: str | None
|
|
month: str
|
|
limit_cents: int
|
|
spent_cents: int
|
|
remaining_cents: int
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
model_config = {"from_attributes": True}
|