mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-05-19 10:28:48 +02:00
Add manual pension data entry and fix chart to use real historical data
All checks were successful
Deploy to VPS / deploy (push) Successful in 23s
All checks were successful
Deploy to VPS / deploy (push) Successful in 23s
- Add paste-and-preview modal for entering pension fund balances from bank website - Backend upsert logic so n8n PDF uploads overwrite manual entries - Chart now shows actual snapshot data with dynamic month labels - New POST /pensions/manual endpoint for JSON-based fund entry Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
from datetime import date
|
||||
|
||||
from fastapi import APIRouter, Depends, UploadFile
|
||||
from pydantic import BaseModel
|
||||
from sqlmodel import Session, select
|
||||
@@ -12,11 +14,93 @@ router = APIRouter(prefix="/pensions", tags=["pensions"])
|
||||
|
||||
class PensionUploadResult(BaseModel):
|
||||
imported: int
|
||||
updated: int
|
||||
duplicates: int
|
||||
errors: list[str]
|
||||
snapshots: list[PensionSnapshotRead]
|
||||
|
||||
|
||||
class PensionManualEntry(BaseModel):
|
||||
fund: str
|
||||
period_start: date
|
||||
period_end: date
|
||||
saldo_anterior: float
|
||||
aportes: float
|
||||
rendimientos: float
|
||||
retiros: float
|
||||
traslados: float
|
||||
comision: float
|
||||
correccion: float = 0.0
|
||||
bonificacion: float = 0.0
|
||||
saldo_final: float
|
||||
|
||||
|
||||
class PensionManualRequest(BaseModel):
|
||||
entries: list[PensionManualEntry]
|
||||
|
||||
|
||||
def _upsert_snapshot(
|
||||
session: Session,
|
||||
fund: str,
|
||||
period_start: date,
|
||||
period_end: date,
|
||||
saldo_anterior: float,
|
||||
aportes: float,
|
||||
rendimientos: float,
|
||||
retiros: float,
|
||||
traslados: float,
|
||||
comision: float,
|
||||
correccion: float,
|
||||
bonificacion: float,
|
||||
saldo_final: float,
|
||||
source_filename: str,
|
||||
contract_number: str = "",
|
||||
) -> tuple[PensionSnapshot, bool]:
|
||||
"""Insert or update a pension snapshot. Returns (row, is_new)."""
|
||||
existing = session.exec(
|
||||
select(PensionSnapshot).where(
|
||||
PensionSnapshot.fund == Bank(fund),
|
||||
PensionSnapshot.period_start == period_start,
|
||||
PensionSnapshot.period_end == period_end,
|
||||
)
|
||||
).first()
|
||||
|
||||
if existing:
|
||||
existing.saldo_anterior = saldo_anterior
|
||||
existing.aportes = aportes
|
||||
existing.rendimientos = rendimientos
|
||||
existing.retiros = retiros
|
||||
existing.traslados = traslados
|
||||
existing.comision = comision
|
||||
existing.correccion = correccion
|
||||
existing.bonificacion = bonificacion
|
||||
existing.saldo_final = saldo_final
|
||||
existing.source_filename = source_filename
|
||||
if contract_number:
|
||||
existing.contract_number = contract_number
|
||||
session.add(existing)
|
||||
return existing, False
|
||||
|
||||
row = PensionSnapshot(
|
||||
fund=Bank(fund),
|
||||
contract_number=contract_number,
|
||||
period_start=period_start,
|
||||
period_end=period_end,
|
||||
saldo_anterior=saldo_anterior,
|
||||
aportes=aportes,
|
||||
rendimientos=rendimientos,
|
||||
retiros=retiros,
|
||||
traslados=traslados,
|
||||
comision=comision,
|
||||
correccion=correccion,
|
||||
bonificacion=bonificacion,
|
||||
saldo_final=saldo_final,
|
||||
source_filename=source_filename,
|
||||
)
|
||||
session.add(row)
|
||||
return row, True
|
||||
|
||||
|
||||
@router.post("/upload", response_model=PensionUploadResult)
|
||||
async def upload_pension_pdfs(
|
||||
files: list[UploadFile],
|
||||
@@ -24,9 +108,9 @@ async def upload_pension_pdfs(
|
||||
_user: str = Depends(get_current_user),
|
||||
):
|
||||
imported = 0
|
||||
duplicates = 0
|
||||
updated = 0
|
||||
errors: list[str] = []
|
||||
created: list[PensionSnapshot] = []
|
||||
results: list[PensionSnapshot] = []
|
||||
|
||||
for file in files:
|
||||
filename = file.filename or "unknown.pdf"
|
||||
@@ -41,20 +125,9 @@ async def upload_pension_pdfs(
|
||||
continue
|
||||
|
||||
for snap in fund_snapshots:
|
||||
existing = session.exec(
|
||||
select(PensionSnapshot).where(
|
||||
PensionSnapshot.fund == Bank(snap.fund),
|
||||
PensionSnapshot.period_start == snap.period_start,
|
||||
PensionSnapshot.period_end == snap.period_end,
|
||||
)
|
||||
).first()
|
||||
if existing:
|
||||
duplicates += 1
|
||||
continue
|
||||
|
||||
row = PensionSnapshot(
|
||||
fund=Bank(snap.fund),
|
||||
contract_number=snap.contract_number,
|
||||
row, is_new = _upsert_snapshot(
|
||||
session,
|
||||
fund=snap.fund,
|
||||
period_start=snap.period_start,
|
||||
period_end=snap.period_end,
|
||||
saldo_anterior=snap.saldo_anterior,
|
||||
@@ -67,21 +140,72 @@ async def upload_pension_pdfs(
|
||||
bonificacion=snap.bonificacion,
|
||||
saldo_final=snap.saldo_final,
|
||||
source_filename=filename,
|
||||
contract_number=snap.contract_number,
|
||||
)
|
||||
session.add(row)
|
||||
created.append(row)
|
||||
imported += 1
|
||||
results.append(row)
|
||||
if is_new:
|
||||
imported += 1
|
||||
else:
|
||||
updated += 1
|
||||
|
||||
if imported > 0:
|
||||
if imported > 0 or updated > 0:
|
||||
session.commit()
|
||||
for row in created:
|
||||
for row in results:
|
||||
session.refresh(row)
|
||||
|
||||
return PensionUploadResult(
|
||||
imported=imported,
|
||||
duplicates=duplicates,
|
||||
updated=updated,
|
||||
duplicates=0,
|
||||
errors=errors,
|
||||
snapshots=[PensionSnapshotRead.model_validate(r) for r in created],
|
||||
snapshots=[PensionSnapshotRead.model_validate(r) for r in results],
|
||||
)
|
||||
|
||||
|
||||
@router.post("/manual", response_model=PensionUploadResult)
|
||||
def submit_manual_entries(
|
||||
body: PensionManualRequest,
|
||||
session: Session = Depends(get_session),
|
||||
_user: str = Depends(get_current_user),
|
||||
):
|
||||
imported = 0
|
||||
updated = 0
|
||||
results: list[PensionSnapshot] = []
|
||||
|
||||
for entry in body.entries:
|
||||
row, is_new = _upsert_snapshot(
|
||||
session,
|
||||
fund=entry.fund,
|
||||
period_start=entry.period_start,
|
||||
period_end=entry.period_end,
|
||||
saldo_anterior=entry.saldo_anterior,
|
||||
aportes=entry.aportes,
|
||||
rendimientos=entry.rendimientos,
|
||||
retiros=entry.retiros,
|
||||
traslados=entry.traslados,
|
||||
comision=entry.comision,
|
||||
correccion=entry.correccion,
|
||||
bonificacion=entry.bonificacion,
|
||||
saldo_final=entry.saldo_final,
|
||||
source_filename="manual-entry",
|
||||
)
|
||||
results.append(row)
|
||||
if is_new:
|
||||
imported += 1
|
||||
else:
|
||||
updated += 1
|
||||
|
||||
if imported > 0 or updated > 0:
|
||||
session.commit()
|
||||
for row in results:
|
||||
session.refresh(row)
|
||||
|
||||
return PensionUploadResult(
|
||||
imported=imported,
|
||||
updated=updated,
|
||||
duplicates=0,
|
||||
errors=[],
|
||||
snapshots=[PensionSnapshotRead.model_validate(r) for r in results],
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user