user(); $query = Budget::with(['service', 'lignes', 'commune']); // Le responsable ne voit que les budgets de son service if (!$user->hasRole(['admin', 'directeur', 'raf'])) { $query->where('service_id', $user->service_id); } $annee = $request->get('annee', now()->year); $query->where('annee', $annee); $budgets = $query->orderBy('annee', 'desc')->get()->map(function ($budget) { return [ 'id' => $budget->id, 'service' => $budget->service->nom, 'commune' => $budget->commune?->nom, 'annee' => $budget->annee, 'type_budget' => $budget->type_budget, 'statut' => $budget->statut, 'total_invest_propose' => $budget->totalPropose('investissement'), 'total_fonct_propose' => $budget->totalPropose('fonctionnement'), 'total_invest_arbitre' => $budget->totalArbitre('investissement'), 'total_fonct_arbitre' => $budget->totalArbitre('fonctionnement'), ]; }); return Inertia::render('Budgets/Index', [ 'budgets' => $budgets, 'services' => Service::all(), 'communes' => \App\Models\Commune::orderBy('nom')->get(), 'filters' => ['annee' => $annee] ]); } public function store(Request $request) { $request->validate([ 'service_id' => 'required|exists:services,id', 'annee' => 'required|integer', 'type_budget' => 'required|in:agglo,mutualise', ]); Budget::create($request->all()); return redirect()->back()->with('success', 'Enveloppe budgétaire créée avec succès.'); } public function show(Budget $budget, Request $request) { $user = $request->user(); if (!$user->hasRole(['admin', 'directeur', 'raf']) && $budget->service_id !== $user->service_id) { abort(403); } $budget->load(['service', 'lignes.commandes', 'lignes.historique.user']); $totals = [ 'invest_propose' => $budget->totalPropose('investissement'), 'invest_arbitre' => $budget->totalArbitre('investissement'), 'fonct_propose' => $budget->totalPropose('fonctionnement'), 'fonct_arbitre' => $budget->totalArbitre('fonctionnement'), ]; $lignes = $budget->lignes->map(function ($ligne) { return array_merge($ligne->toArray(), [ 'historique' => $ligne->historique->map(fn($h) => [ 'id' => $h->id, 'ancien_statut' => $h->ancien_statut, 'nouveau_statut' => $h->nouveau_statut, 'ancien_montant_arbitre' => $h->ancien_montant_arbitre, 'nouveau_montant_arbitre'=> $h->nouveau_montant_arbitre, 'commentaire' => $h->commentaire, 'user' => $h->user->name, 'created_at' => $h->created_at->format('d/m/Y H:i'), ]), ]); }); return Inertia::render('Budgets/Show', [ 'budget' => $budget, 'lignes' => $lignes, 'totals' => $totals, 'communes'=> \App\Models\Commune::orderBy('nom')->get(), ]); } public function update(Request $request, Budget $budget) { $this->authorize('update', $budget); $request->validate([ 'statut' => 'required|in:preparation,arbitrage_dsi,arbitrage_direction,valide,cloture', ]); $budget->update(['statut' => $request->statut]); return redirect()->back()->with('success', 'Statut du budget mis à jour.'); } public function execution(Request $request) { $user = $request->user(); $annee = $request->get('annee', now()->year); $query = \App\Models\LigneBudget::with(['budget.service', 'commandes.commune']) ->whereHas('budget', function($q) use ($annee) { $q->where('annee', $annee); }); // Filtrage par service si pas admin/directeur/raf if (!$user->hasRole(['admin', 'directeur', 'raf'])) { $query->whereHas('budget', function($q) use ($user) { $q->where('service_id', $user->service_id); }); } $statutsConsommes = ['validee', 'commandee', 'partiellement_recue', 'recue_complete', 'cloturee']; $statutsEngages = ['brouillon', 'en_attente_validation']; $lignes = $query->get()->map(function($lb) use ($statutsConsommes, $statutsEngages) { // Toutes les commandes déjà chargées en mémoire — zéro requête supplémentaire $commandes = $lb->commandes; $consomme = (float) $commandes->whereIn('statut', $statutsConsommes)->sum('montant_ttc'); $engage = (float) $commandes->whereIn('statut', $statutsEngages)->sum('montant_ttc'); $totalEngage = $consomme + $engage; // Ventilation par commune — calculée en mémoire $ventilation = $commandes ->whereNotIn('statut', ['annulee']) ->groupBy('commune_id') ->map(fn($group, $communeId) => [ 'commune' => $group->first()->commune?->nom ?? 'Non défini', 'total' => (float) $group->sum('montant_ttc'), ]) ->values(); // Liste détaillée des commandes pour affichage $commandesList = $commandes ->whereNotIn('statut', ['annulee']) ->map(fn($c) => [ 'id' => $c->id, 'reference' => $c->numero_commande ?? 'CMD-'.$c->id, 'nom' => $c->objet, 'commune' => $c->commune?->nom, 'montant_ttc'=> (float) $c->montant_ttc, 'statut' => $c->statut, ]) ->values(); return [ 'id' => $lb->id, 'nom' => $lb->nom, 'type_depense' => $lb->type_depense, 'type_budget' => $lb->budget->type_budget, 'service' => $lb->budget->service->nom, 'annee' => $lb->budget->annee, 'montant_arbitre' => (float) $lb->montant_arbitre, 'consomme' => $consomme, 'engage' => $engage, 'total_cumule' => $totalEngage, 'reste' => (float) $lb->montant_arbitre - $totalEngage, 'ventilation' => $ventilation, 'commandes' => $commandesList ]; }); return Inertia::render('Budgets/Execution', [ 'lignes' => $lignes, 'services' => \App\Models\Service::orderBy('nom')->get(), 'filters' => [ 'annee' => $annee ] ]); } private function getExportFilename(string $base, int $annee, array $filters, string $ext): string { $name = $base . '-' . $annee; if ($filters['type']) $name .= '-' . $filters['type']; if ($filters['envelope']) $name .= '-' . $filters['envelope']; if ($filters['service']) $name .= '-' . \Str::slug($filters['service']); return $name . '.' . $ext; } /** * Valide et extrait les filtres d'export depuis la requête. * Les valeurs non reconnues sont ignorées pour éviter toute injection de paramètres. */ private function resolveExportFilters(Request $request): array { $type = $request->get('type'); $envelope = $request->get('envelope'); return [ 'type' => in_array($type, ['investissement', 'fonctionnement']) ? $type : null, 'envelope' => in_array($envelope, ['agglo', 'mutualise']) ? $envelope : null, 'service' => $request->get('service') !== 'all' ? $request->get('service') : null, ]; } public function exportExcel(Request $request) { $annee = (int) $request->get('annee', now()->year); $filters = $this->resolveExportFilters($request); $filename = $this->getExportFilename('budget-execution', $annee, $filters, 'xlsx'); return Excel::download(new BudgetExecutionExport($request->user(), $annee, $filters), $filename); } public function exportOds(Request $request) { $annee = (int) $request->get('annee', now()->year); $filters = $this->resolveExportFilters($request); $filename = $this->getExportFilename('budget-execution', $annee, $filters, 'ods'); return Excel::download(new BudgetExecutionExport($request->user(), $annee, $filters), $filename, \Maatwebsite\Excel\Excel::ODS); } public function exportPdf(Request $request) { $user = $request->user(); $annee = (int) $request->get('annee', now()->year); $filters = $this->resolveExportFilters($request); $query = LigneBudget::with(['budget.service', 'commandes']) ->whereHas('budget', function($q) use ($annee) { $q->where('annee', $annee); }); if (!$user->hasRole(['admin', 'directeur', 'raf'])) { $query->whereHas('budget', function($q) use ($user) { $q->where('service_id', $user->service_id); }); } if ($filters['type']) { $query->where('type_depense', $filters['type']); } if ($filters['envelope']) { $query->whereHas('budget', fn($q) => $q->where('type_budget', $filters['envelope'])); } if ($filters['service']) { $query->whereHas('budget.service', fn($q) => $q->where('nom', $filters['service'])); } $statutsConsommes = ['validee', 'commandee', 'partiellement_recue', 'recue_complete', 'cloturee']; $statutsEngages = ['brouillon', 'en_attente_validation']; $lignes = $query->get()->map(function($lb) use ($statutsConsommes, $statutsEngages) { $commandes = $lb->commandes; $lb->consomme = (float) $commandes->whereIn('statut', $statutsConsommes)->sum('montant_ttc'); $lb->engage = (float) $commandes->whereIn('statut', $statutsEngages)->sum('montant_ttc'); $lb->total_cumule = $lb->consomme + $lb->engage; $lb->reste = (float) ($lb->montant_arbitre ?? 0) - $lb->total_cumule; return $lb; }); $pdf = Pdf::loadView('exports.budgets_pdf', [ 'lignes' => $lignes, 'annee' => $annee, 'user' => $user, 'type' => $filters['type'] ? ucfirst($filters['type']) : 'Tous types', 'envelope' => $filters['envelope'] ? ucfirst($filters['envelope']) : 'Toutes enveloppes', 'service' => !$user->hasRole(['admin', 'directeur', 'raf']) ? ($user->service->nom ?? 'Inconnu') : ($filters['service'] ?? 'Tous les services'), ])->setPaper('a4', 'landscape'); $filename = $this->getExportFilename('rapport-budgetaire', $annee, $filters, 'pdf'); return $pdf->download($filename); } }