feat: module budgets complet avec sécurité, performance et métier
## Fonctionnalités - Module Budgets : enveloppes, lignes budgétaires, arbitrage DSI/Direction - Suivi de l'exécution budgétaire avec alertes visuelles (dépassement, seuil 80%) - Blocage des commandes si budget insuffisant (store + update) - Audit trail complet des arbitrages via HistoriqueBudget - Page d'index budgets refaite en tableau avec filtres et tri côté client - Page Services avec sélecteur d'icônes FontAwesome (solid + regular + brands) ## Sécurité - BudgetPolicy centralisée (viewAny, view, create, update, addLigne, updateLigne, deleteLigne, arbitrerLigne) - Autorisation sur tous les endpoints LigneBudget et Budget - Protection XSS : remplacement v-html par classes dynamiques - Validation des paramètres d'export (type, envelope) - Validation montant_arbitre ≤ montant_propose côté serveur ## Performance - Eager loading lignes.commandes.commune dans execution() et exportPdf() - Calculs montant_consomme/engage en mémoire sur collections déjà chargées - Null-safety sur montant_arbitre dans getMontantDisponibleAttribute ## Technique - Migration historique_budgets, budgets, ligne_budgets, rôle raf - SearchableSelect avec affichage du disponible budgétaire - FontAwesome enregistré globalement (fas, far, fab) - 33 tests Feature (sécurité, performance, métier) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('budgets', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('service_id')->constrained()->cascadeOnDelete();
|
||||
$table->integer('annee');
|
||||
$table->enum('type_budget', ['agglo', 'mutualise'])->default('agglo');
|
||||
$table->enum('statut', ['preparation', 'arbitrage_dsi', 'arbitrage_direction', 'valide', 'cloture'])->default('preparation');
|
||||
$table->timestamps();
|
||||
|
||||
// Un service ne peut avoir qu'un seul budget d'un type donné par an
|
||||
$table->unique(['service_id', 'annee', 'type_budget']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('budgets');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('ligne_budgets', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('budget_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('nom');
|
||||
$table->text('description')->nullable();
|
||||
$table->enum('type_depense', ['investissement', 'fonctionnement'])->default('fonctionnement');
|
||||
$table->decimal('montant_propose', 10, 2)->default(0);
|
||||
$table->decimal('montant_arbitre', 10, 2)->nullable();
|
||||
$table->enum('statut_arbitrage', ['brouillon', 'propose', 'accepte_dsi', 'accepte_direction', 'valide_definitif', 'refuse', 'reporte'])->default('brouillon');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('ligne_budgets');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('commandes', function (Blueprint $table) {
|
||||
$table->foreignId('ligne_budget_id')->nullable()->constrained('ligne_budgets')->nullOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('commandes', function (Blueprint $table) {
|
||||
$table->dropForeign(['ligne_budget_id']);
|
||||
$table->dropColumn('ligne_budget_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('budgets', function (Blueprint $table) {
|
||||
$table->foreignId('commune_id')->nullable()->after('service_id')->constrained('communes')->nullOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('budgets', function (Blueprint $table) {
|
||||
$table->dropForeign(['commune_id']);
|
||||
$table->dropColumn('commune_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('ligne_budgets', function (Blueprint $table) {
|
||||
$table->foreignId('commune_id')->nullable()->after('budget_id')->constrained('communes')->nullOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('ligne_budgets', function (Blueprint $table) {
|
||||
$table->dropForeign(['commune_id']);
|
||||
$table->dropColumn('commune_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
25
database/migrations/2026_04_11_121337_create_raf_role.php
Normal file
25
database/migrations/2026_04_11_121337_create_raf_role.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Role::firstOrCreate(['name' => 'raf', 'guard_name' => 'web']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Role::where('name', 'raf')->delete();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('historique_budgets', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('ligne_budget_id')->constrained('ligne_budgets')->cascadeOnDelete();
|
||||
$table->foreignId('user_id')->constrained('users')->restrictOnDelete();
|
||||
|
||||
$table->string('ancien_statut')->nullable();
|
||||
$table->string('nouveau_statut');
|
||||
$table->decimal('ancien_montant_arbitre', 10, 2)->nullable();
|
||||
$table->decimal('nouveau_montant_arbitre', 10, 2)->nullable();
|
||||
$table->text('commentaire')->nullable();
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('historique_budgets');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user