NEW FEATURES: - Financial Health: Score 0-100, 6 metrics, insights, recommendations - Goals: Create/edit savings goals, contributions, progress tracking - Budgets: Monthly category limits, usage alerts, year summary - Reports: 7 tabs with charts (category, evolution, projection, etc.) BACKEND: - New models: FinancialGoal, GoalContribution, Budget - New controllers: FinancialHealthController, FinancialGoalController, BudgetController, ReportController - New migrations: financial_goals, goal_contributions, budgets FRONTEND: - New pages: FinancialHealth.jsx, Goals.jsx, Budgets.jsx, Reports.jsx - New services: financialHealthService, financialGoalService, budgetService, reportService - Navigation: New 'Planning' group in sidebar Chart.js integration for all visualizations
199 lines
6.3 KiB
PHP
199 lines
6.3 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\FinancialGoal;
|
|
use App\Models\GoalContribution;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
|
|
class FinancialGoalController extends Controller
|
|
{
|
|
/**
|
|
* Listar todas las metas del usuario
|
|
*/
|
|
public function index(Request $request)
|
|
{
|
|
$query = FinancialGoal::forUser(Auth::id())
|
|
->with('contributions')
|
|
->orderByRaw("FIELD(status, 'active', 'paused', 'completed', 'cancelled')")
|
|
->orderBy('priority', 'desc')
|
|
->orderBy('target_date', 'asc');
|
|
|
|
if ($request->has('status')) {
|
|
$query->where('status', $request->status);
|
|
}
|
|
|
|
$goals = $query->get();
|
|
|
|
// Calcular estadísticas generales
|
|
$activeGoals = $goals->where('status', 'active');
|
|
$stats = [
|
|
'total_goals' => $goals->count(),
|
|
'active_goals' => $activeGoals->count(),
|
|
'completed_goals' => $goals->where('status', 'completed')->count(),
|
|
'total_target' => $activeGoals->sum('target_amount'),
|
|
'total_saved' => $activeGoals->sum('current_amount'),
|
|
'overall_progress' => $activeGoals->sum('target_amount') > 0
|
|
? round(($activeGoals->sum('current_amount') / $activeGoals->sum('target_amount')) * 100, 1)
|
|
: 0,
|
|
];
|
|
|
|
return response()->json([
|
|
'data' => $goals,
|
|
'stats' => $stats,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Crear una nueva meta
|
|
*/
|
|
public function store(Request $request)
|
|
{
|
|
$validated = $request->validate([
|
|
'name' => 'required|string|max:255',
|
|
'description' => 'nullable|string',
|
|
'icon' => 'nullable|string|max:50',
|
|
'color' => 'nullable|string|max:20',
|
|
'target_amount' => 'required|numeric|min:0.01',
|
|
'current_amount' => 'nullable|numeric|min:0',
|
|
'currency' => 'nullable|string|size:3',
|
|
'target_date' => 'nullable|date|after:today',
|
|
'start_date' => 'nullable|date',
|
|
'monthly_contribution' => 'nullable|numeric|min:0',
|
|
'priority' => 'nullable|in:high,medium,low',
|
|
]);
|
|
|
|
$validated['user_id'] = Auth::id();
|
|
$validated['start_date'] = $validated['start_date'] ?? now()->format('Y-m-d');
|
|
|
|
$goal = FinancialGoal::create($validated);
|
|
|
|
// Si tiene monto inicial, crear contribución
|
|
if (isset($validated['current_amount']) && $validated['current_amount'] > 0) {
|
|
$goal->contributions()->create([
|
|
'amount' => $validated['current_amount'],
|
|
'contribution_date' => $validated['start_date'],
|
|
'notes' => 'Monto inicial',
|
|
]);
|
|
}
|
|
|
|
return response()->json([
|
|
'message' => 'Meta creada con éxito',
|
|
'data' => $goal->fresh('contributions'),
|
|
], 201);
|
|
}
|
|
|
|
/**
|
|
* Ver una meta específica
|
|
*/
|
|
public function show($id)
|
|
{
|
|
$goal = FinancialGoal::forUser(Auth::id())
|
|
->with(['contributions' => function($q) {
|
|
$q->orderBy('contribution_date', 'desc');
|
|
}])
|
|
->findOrFail($id);
|
|
|
|
return response()->json($goal);
|
|
}
|
|
|
|
/**
|
|
* Actualizar una meta
|
|
*/
|
|
public function update(Request $request, $id)
|
|
{
|
|
$goal = FinancialGoal::forUser(Auth::id())->findOrFail($id);
|
|
|
|
$validated = $request->validate([
|
|
'name' => 'sometimes|string|max:255',
|
|
'description' => 'nullable|string',
|
|
'icon' => 'nullable|string|max:50',
|
|
'color' => 'nullable|string|max:20',
|
|
'target_amount' => 'sometimes|numeric|min:0.01',
|
|
'target_date' => 'nullable|date',
|
|
'monthly_contribution' => 'nullable|numeric|min:0',
|
|
'status' => 'sometimes|in:active,completed,paused,cancelled',
|
|
'priority' => 'sometimes|in:high,medium,low',
|
|
]);
|
|
|
|
$goal->update($validated);
|
|
|
|
// Si se marca como completada manualmente
|
|
if (isset($validated['status']) && $validated['status'] === 'completed' && !$goal->completed_at) {
|
|
$goal->completed_at = now();
|
|
$goal->save();
|
|
}
|
|
|
|
return response()->json([
|
|
'message' => 'Meta actualizada',
|
|
'data' => $goal->fresh('contributions'),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Eliminar una meta
|
|
*/
|
|
public function destroy($id)
|
|
{
|
|
$goal = FinancialGoal::forUser(Auth::id())->findOrFail($id);
|
|
$goal->delete();
|
|
|
|
return response()->json([
|
|
'message' => 'Meta eliminada',
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Añadir contribución a una meta
|
|
*/
|
|
public function addContribution(Request $request, $id)
|
|
{
|
|
$goal = FinancialGoal::forUser(Auth::id())->findOrFail($id);
|
|
|
|
$validated = $request->validate([
|
|
'amount' => 'required|numeric|min:0.01',
|
|
'contribution_date' => 'nullable|date',
|
|
'transaction_id' => 'nullable|exists:transactions,id',
|
|
'notes' => 'nullable|string',
|
|
]);
|
|
|
|
$contribution = $goal->addContribution(
|
|
$validated['amount'],
|
|
$validated['contribution_date'] ?? now(),
|
|
$validated['transaction_id'] ?? null,
|
|
$validated['notes'] ?? null
|
|
);
|
|
|
|
return response()->json([
|
|
'message' => 'Contribución añadida',
|
|
'data' => $goal->fresh('contributions'),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Eliminar contribución
|
|
*/
|
|
public function removeContribution($goalId, $contributionId)
|
|
{
|
|
$goal = FinancialGoal::forUser(Auth::id())->findOrFail($goalId);
|
|
$contribution = $goal->contributions()->findOrFail($contributionId);
|
|
|
|
// Restar el monto de la meta
|
|
$goal->current_amount -= $contribution->amount;
|
|
if ($goal->status === 'completed') {
|
|
$goal->status = 'active';
|
|
$goal->completed_at = null;
|
|
}
|
|
$goal->save();
|
|
|
|
$contribution->delete();
|
|
|
|
return response()->json([
|
|
'message' => 'Contribución eliminada',
|
|
'data' => $goal->fresh('contributions'),
|
|
]);
|
|
}
|
|
}
|