Remove Ahorro from budget UI, add SALARY type and savings auto-accrual
All checks were successful
Deploy to VPS / deploy (push) Successful in 23s

Ahorro was already deducted from gross salary so displaying it in
budget projections was misleading. This removes the Ahorro card,
summary line, Proyecciones column, and Ahorro Anual card from the UI,
and strips all savings fields from budget API responses.

Adds SALARY TransactionType so salary deposits can be distinguished
from generic DEPOSITO transfers. When a SALARY transaction arrives,
the system auto-increments MEMP and MPAT savings account balances
(+200K CRC each) once per month via an idempotent accrual log.

New CRUD endpoints at /api/v1/savings-accrual/ allow manual correction
of the accrual history. Feb+Mar 2026 are seeded as historical baseline.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Carlos Escalante
2026-04-15 19:13:29 -06:00
parent 94a8a894a6
commit d929ed6573
15 changed files with 304 additions and 96 deletions

View File

@@ -39,7 +39,9 @@ def list_recurring_items(
session: Session = Depends(get_session),
_user: str = Depends(get_current_user),
):
query = select(RecurringItem)
query = select(RecurringItem).where(
RecurringItem.item_type != RecurringItemType.SAVINGS
)
if item_type:
query = query.where(RecurringItem.item_type == item_type)
if is_active is not None:
@@ -101,7 +103,6 @@ class MonthlyProjectionResponse(BaseModel):
year: int
projected_income: float
projected_fixed_expenses: float
projected_savings: float
actual_credit_card: float
actual_cash: float
actual_transfers: float
@@ -118,7 +119,6 @@ class YearlyProjectionResponse(BaseModel):
months: list[MonthlyProjectionResponse]
annual_income: float
annual_expenses: float
annual_savings: float
annual_net: float
@@ -138,7 +138,6 @@ def get_yearly_projection(
months = []
annual_income = 0.0
annual_expenses = 0.0
annual_savings = 0.0
annual_net = 0.0
for data in months_data:
@@ -147,7 +146,6 @@ def get_yearly_projection(
year=data["year"],
projected_income=data["projected_income"],
projected_fixed_expenses=data["projected_fixed_expenses"],
projected_savings=data["projected_savings"],
actual_credit_card=data["actual_credit_card"],
actual_cash=data["actual_cash"],
actual_transfers=data["actual_transfers"],
@@ -161,7 +159,6 @@ def get_yearly_projection(
months.append(monthly)
annual_income += data["projected_income"]
annual_expenses += data["gran_total_egresos"]
annual_savings += data["projected_savings"]
annual_net += data["net_balance"]
return YearlyProjectionResponse(
@@ -169,7 +166,6 @@ def get_yearly_projection(
months=months,
annual_income=annual_income,
annual_expenses=annual_expenses,
annual_savings=annual_savings,
annual_net=annual_net,
)
@@ -204,11 +200,9 @@ class MonthlyDetailResponse(BaseModel):
month: int
income_items: list[RecurringItemDetail]
expense_items: list[RecurringItemDetail]
savings_items: list[RecurringItemDetail]
actuals_by_source: list[ActualsBySource]
total_projected_income: float
total_projected_expenses: float
total_projected_savings: float
uncovered_actual: float
gran_total_egresos: float
net_balance: float
@@ -228,11 +222,9 @@ def get_monthly_detail(
month=data["month"],
income_items=[RecurringItemDetail(**i) for i in data["income_items"]],
expense_items=[RecurringItemDetail(**i) for i in data["expense_items"]],
savings_items=[RecurringItemDetail(**i) for i in data["savings_items"]],
actuals_by_source=[ActualsBySource(**a) for a in data["actuals_by_source"]],
total_projected_income=data["projected_income"],
total_projected_expenses=data["projected_fixed_expenses"],
total_projected_savings=data["projected_savings"],
uncovered_actual=data["uncovered_actual"],
gran_total_egresos=data["gran_total_egresos"],
net_balance=data["net_balance"],