mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-05-19 11:08:47 +02:00
Remove Ahorro from budget UI, add SALARY type and savings auto-accrual
All checks were successful
Deploy to VPS / deploy (push) Successful in 23s
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:
@@ -160,7 +160,7 @@ export interface Transaction {
|
||||
|
||||
// --- Budget / Recurring Items ---
|
||||
|
||||
export type RecurringItemType = 'INCOME' | 'EXPENSE' | 'SAVINGS';
|
||||
export type RecurringItemType = 'INCOME' | 'EXPENSE';
|
||||
export type RecurringFrequency = 'WEEKLY' | 'MONTHLY' | 'QUARTERLY' | 'BIANNUAL' | 'YEARLY';
|
||||
|
||||
export interface RecurringItem {
|
||||
@@ -233,7 +233,6 @@ export interface MonthlyProjection {
|
||||
year: number;
|
||||
projected_income: number;
|
||||
projected_fixed_expenses: number;
|
||||
projected_savings: number;
|
||||
actual_credit_card: number;
|
||||
actual_cash: number;
|
||||
actual_transfers: number;
|
||||
@@ -250,7 +249,6 @@ export interface YearlyProjection {
|
||||
months: MonthlyProjection[];
|
||||
annual_income: number;
|
||||
annual_expenses: number;
|
||||
annual_savings: number;
|
||||
annual_net: number;
|
||||
}
|
||||
|
||||
@@ -259,17 +257,52 @@ export interface MonthlyDetail {
|
||||
month: number;
|
||||
income_items: RecurringItemDetail[];
|
||||
expense_items: RecurringItemDetail[];
|
||||
savings_items: RecurringItemDetail[];
|
||||
actuals_by_source: ActualsBySource[];
|
||||
total_projected_income: number;
|
||||
total_projected_expenses: number;
|
||||
total_projected_savings: number;
|
||||
uncovered_actual: number;
|
||||
gran_total_egresos: number;
|
||||
net_balance: number;
|
||||
cc_by_category: { category_name: string; amount: number }[];
|
||||
}
|
||||
|
||||
// --- Savings Accrual ---
|
||||
|
||||
export interface SavingsAccrual {
|
||||
id: number;
|
||||
year: number;
|
||||
month: number;
|
||||
memp_amount: number;
|
||||
mpat_amount: number;
|
||||
trigger_transaction_id: number | null;
|
||||
applied_at: string;
|
||||
notes: string | null;
|
||||
}
|
||||
|
||||
export interface SavingsAccrualCreate {
|
||||
year: number;
|
||||
month: number;
|
||||
memp_amount?: number;
|
||||
mpat_amount?: number;
|
||||
trigger_transaction_id?: number | null;
|
||||
notes?: string | null;
|
||||
}
|
||||
|
||||
export interface SavingsAccrualUpdate {
|
||||
memp_amount?: number;
|
||||
mpat_amount?: number;
|
||||
notes?: string | null;
|
||||
}
|
||||
|
||||
export const getSavingsAccruals = () =>
|
||||
api.get<SavingsAccrual[]>('/savings-accrual/');
|
||||
export const createSavingsAccrual = (data: SavingsAccrualCreate) =>
|
||||
api.post<SavingsAccrual>('/savings-accrual/', data);
|
||||
export const updateSavingsAccrual = (id: number, data: SavingsAccrualUpdate) =>
|
||||
api.patch<SavingsAccrual>(`/savings-accrual/${id}`, data);
|
||||
export const deleteSavingsAccrual = (id: number) =>
|
||||
api.delete(`/savings-accrual/${id}`);
|
||||
|
||||
// Budget API functions
|
||||
export const getRecurringItems = (params?: { item_type?: string; is_active?: boolean }) =>
|
||||
api.get<RecurringItem[]>('/budget/recurring', { params });
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
import {
|
||||
TrendingUp,
|
||||
TrendingDown,
|
||||
PiggyBank,
|
||||
CreditCard,
|
||||
Banknote,
|
||||
ArrowLeftRight,
|
||||
@@ -346,8 +345,8 @@ export default function MonthlyDetail({ detail, loading, onNavigateToTransaction
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Actuals + Savings + Summary */}
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
{/* Actuals + Summary */}
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
{/* Cash & Transfer Actuals Card */}
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
@@ -401,33 +400,6 @@ export default function MonthlyDetail({ detail, loading, onNavigateToTransaction
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Savings */}
|
||||
{detail.savings_items.length > 0 && (
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
||||
<PiggyBank className="w-4 h-4" />
|
||||
Ahorro
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
{detail.savings_items.map((item) => (
|
||||
<div key={item.id} className="flex items-center justify-between text-sm">
|
||||
<span>{item.name}</span>
|
||||
<span data-sensitive className="font-mono">{formatAmount(item.amount, 'CRC')}</span>
|
||||
</div>
|
||||
))}
|
||||
<Separator />
|
||||
<div className="flex items-center justify-between font-semibold text-sm">
|
||||
<span>Total Ahorro</span>
|
||||
<span data-sensitive className="font-mono">
|
||||
{formatAmount(detail.total_projected_savings, 'CRC')}
|
||||
</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Summary */}
|
||||
<Card className={cn(
|
||||
'border-2',
|
||||
@@ -446,12 +418,6 @@ export default function MonthlyDetail({ detail, loading, onNavigateToTransaction
|
||||
-{formatAmount(detail.gran_total_egresos, 'CRC')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span>Ahorro</span>
|
||||
<span data-sensitive className="font-mono font-medium">
|
||||
-{formatAmount(detail.total_projected_savings, 'CRC')}
|
||||
</span>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="font-semibold">Balance Neto</span>
|
||||
|
||||
@@ -29,7 +29,6 @@ import { Plus, Trash2 } from 'lucide-react';
|
||||
const TYPE_OPTIONS: { value: RecurringItemType; label: string }[] = [
|
||||
{ value: 'INCOME', label: 'Ingreso' },
|
||||
{ value: 'EXPENSE', label: 'Egreso' },
|
||||
{ value: 'SAVINGS', label: 'Ahorro' },
|
||||
];
|
||||
|
||||
const FREQ_OPTIONS: { value: RecurringFrequency; label: string }[] = [
|
||||
|
||||
@@ -17,7 +17,6 @@ import ConfirmDialog from '@/components/ConfirmDialog';
|
||||
const TYPE_LABELS: Record<string, { label: string; variant: 'default' | 'secondary' | 'outline' }> = {
|
||||
INCOME: { label: 'Ingreso', variant: 'default' },
|
||||
EXPENSE: { label: 'Egreso', variant: 'secondary' },
|
||||
SAVINGS: { label: 'Ahorro', variant: 'outline' },
|
||||
};
|
||||
|
||||
const FREQ_LABELS: Record<string, string> = {
|
||||
|
||||
@@ -89,7 +89,6 @@ export default function YearlyOverview({
|
||||
<TableHead className="text-right">Egresos Fijos</TableHead>
|
||||
<TableHead className="text-right">Otros Gastos</TableHead>
|
||||
<TableHead className="text-right">Gran Total</TableHead>
|
||||
<TableHead className="text-right">Ahorro</TableHead>
|
||||
<TableHead className="text-right">Acum. Anterior</TableHead>
|
||||
<TableHead className="text-right">Neto Mes</TableHead>
|
||||
<TableHead className="text-right">Balance Acum.</TableHead>
|
||||
@@ -132,9 +131,6 @@ export default function YearlyOverview({
|
||||
<TableCell data-sensitive className="text-right font-mono text-sm font-medium">
|
||||
{formatAmount(m.gran_total_egresos, 'CRC')}
|
||||
</TableCell>
|
||||
<TableCell data-sensitive className="text-right font-mono text-sm text-muted-foreground">
|
||||
{formatAmount(m.projected_savings, 'CRC')}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
className={cn(
|
||||
'text-right font-mono text-sm',
|
||||
|
||||
@@ -46,7 +46,7 @@ export default function Proyecciones() {
|
||||
|
||||
{/* Annual summary cards */}
|
||||
{projection && (
|
||||
<div className="grid gap-3 grid-cols-2 md:grid-cols-4">
|
||||
<div className="grid gap-3 grid-cols-1 md:grid-cols-3">
|
||||
<Card>
|
||||
<CardContent className="pt-4 pb-3 px-4">
|
||||
<p className="text-xs text-muted-foreground">Ingresos Anuales</p>
|
||||
@@ -63,14 +63,6 @@ export default function Proyecciones() {
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-4 pb-3 px-4">
|
||||
<p className="text-xs text-muted-foreground">Ahorro Anual</p>
|
||||
<p data-sensitive className="text-lg font-bold font-mono">
|
||||
{formatAmount(projection.annual_savings, 'CRC')}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-4 pb-3 px-4">
|
||||
<p className="text-xs text-muted-foreground">Balance Neto Anual</p>
|
||||
|
||||
Reference in New Issue
Block a user