mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-05-19 10:28:48 +02:00
Add deferred transactions, revamp budget projections and UI
Adds deferred_to_next_cycle flag to transactions for billing cycle bleed-over handling. Overhauls budget projection engine and refreshes Budget page with improved monthly detail and transaction columns. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -194,6 +194,11 @@ class ActualsBySource(BaseModel):
|
||||
count: int
|
||||
|
||||
|
||||
class CCCategorySpending(BaseModel):
|
||||
category_name: str
|
||||
amount: float
|
||||
|
||||
|
||||
class MonthlyDetailResponse(BaseModel):
|
||||
year: int
|
||||
month: int
|
||||
@@ -207,6 +212,7 @@ class MonthlyDetailResponse(BaseModel):
|
||||
uncovered_actual: float
|
||||
gran_total_egresos: float
|
||||
net_balance: float
|
||||
cc_by_category: list[CCCategorySpending]
|
||||
|
||||
|
||||
@router.get("/month/{year}/{month}", response_model=MonthlyDetailResponse)
|
||||
@@ -230,6 +236,7 @@ def get_monthly_detail(
|
||||
uncovered_actual=data["uncovered_actual"],
|
||||
gran_total_egresos=data["gran_total_egresos"],
|
||||
net_balance=data["net_balance"],
|
||||
cc_by_category=[CCCategorySpending(**c) for c in data["cc_by_category"]],
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -19,19 +19,11 @@ from app.models.models import (
|
||||
TransactionUpdate,
|
||||
)
|
||||
|
||||
from app.services.budget_projection import get_cycle_range, get_previous_cycle
|
||||
|
||||
router = APIRouter(prefix="/transactions", tags=["transactions"])
|
||||
|
||||
|
||||
def get_cycle_range(year: int, month: int) -> tuple[datetime, datetime]:
|
||||
"""Return (start, end) for billing cycle: month/18 to month+1/18."""
|
||||
start = datetime(year, month, 18)
|
||||
if month == 12:
|
||||
end = datetime(year + 1, 1, 18)
|
||||
else:
|
||||
end = datetime(year, month + 1, 18)
|
||||
return start, end
|
||||
|
||||
|
||||
class BillingCycle(BaseModel):
|
||||
year: int
|
||||
month: int
|
||||
@@ -54,6 +46,7 @@ def auto_categorize(merchant: str, session: Session) -> Optional[int]:
|
||||
@router.get("/", response_model=list[TransactionRead])
|
||||
def list_transactions(
|
||||
source: Optional[TransactionSource] = None,
|
||||
exclude_source: Optional[TransactionSource] = None,
|
||||
search: Optional[str] = None,
|
||||
category_id: Optional[int] = None,
|
||||
cycle_year: Optional[int] = None,
|
||||
@@ -68,13 +61,32 @@ def list_transactions(
|
||||
query = select(Transaction)
|
||||
if source:
|
||||
query = query.where(Transaction.source == source)
|
||||
if exclude_source:
|
||||
query = query.where(Transaction.source != exclude_source)
|
||||
if category_id:
|
||||
query = query.where(Transaction.category_id == category_id)
|
||||
if search:
|
||||
query = query.where(col(Transaction.merchant).ilike(f"%{search}%"))
|
||||
if cycle_year and cycle_month:
|
||||
start, end = get_cycle_range(cycle_year, cycle_month)
|
||||
query = query.where(Transaction.date >= start, Transaction.date < end)
|
||||
prev_y, prev_m = get_previous_cycle(cycle_year, cycle_month)
|
||||
prev_start, prev_end = get_cycle_range(prev_y, prev_m)
|
||||
# Normal transactions in this cycle (not deferred) + deferred from previous cycle
|
||||
from sqlalchemy import or_, and_
|
||||
query = query.where(
|
||||
or_(
|
||||
and_(
|
||||
Transaction.date >= start,
|
||||
Transaction.date < end,
|
||||
Transaction.deferred_to_next_cycle == False, # noqa: E712
|
||||
),
|
||||
and_(
|
||||
Transaction.date >= prev_start,
|
||||
Transaction.date < prev_end,
|
||||
Transaction.deferred_to_next_cycle == True, # noqa: E712
|
||||
),
|
||||
)
|
||||
)
|
||||
elif start_date and end_date:
|
||||
query = query.where(
|
||||
Transaction.date >= datetime.fromisoformat(start_date),
|
||||
|
||||
Reference in New Issue
Block a user