Initial commit with contrats and domaines modules
This commit is contained in:
94
app/Http/Controllers/ArticleController.php
Normal file
94
app/Http/Controllers/ArticleController.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Article;
|
||||
use App\Models\Categorie;
|
||||
use App\Models\Fournisseur;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class ArticleController extends Controller
|
||||
{
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$articles = Article::with(['categorie', 'fournisseur'])
|
||||
->when($request->search, fn ($q) => $q->where(function ($sub) use ($request) {
|
||||
$sub->where('designation', 'like', "%{$request->search}%")
|
||||
->orWhere('reference', 'like', "%{$request->search}%");
|
||||
}))
|
||||
->when($request->categorie_id, fn ($q) => $q->where('categorie_id', $request->categorie_id))
|
||||
->orderBy('designation')
|
||||
->paginate(20)
|
||||
->withQueryString();
|
||||
|
||||
return Inertia::render('Articles/Index', [
|
||||
'articles' => $articles,
|
||||
'categories' => Categorie::active()->orderBy('ordre')->get(),
|
||||
'filters' => $request->only(['search', 'categorie_id']),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(): Response
|
||||
{
|
||||
return Inertia::render('Articles/Create', [
|
||||
'categories' => Categorie::active()->orderBy('ordre')->get(),
|
||||
'fournisseurs' => Fournisseur::active()->orderBy('nom')->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'reference' => 'nullable|string|max:100',
|
||||
'designation' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'categorie_id' => 'nullable|exists:categories,id',
|
||||
'fournisseur_id' => 'nullable|exists:fournisseurs,id',
|
||||
'prix_unitaire_ht' => 'nullable|numeric|min:0',
|
||||
'unite' => 'nullable|string|max:30',
|
||||
]);
|
||||
|
||||
Article::create($validated);
|
||||
|
||||
return redirect()->route('articles.index')
|
||||
->with('success', 'Article créé avec succès.');
|
||||
}
|
||||
|
||||
public function edit(Article $article): Response
|
||||
{
|
||||
return Inertia::render('Articles/Edit', [
|
||||
'article' => $article,
|
||||
'categories' => Categorie::active()->orderBy('ordre')->get(),
|
||||
'fournisseurs' => Fournisseur::active()->orderBy('nom')->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, Article $article): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'reference' => 'nullable|string|max:100',
|
||||
'designation' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'categorie_id' => 'nullable|exists:categories,id',
|
||||
'fournisseur_id' => 'nullable|exists:fournisseurs,id',
|
||||
'prix_unitaire_ht' => 'nullable|numeric|min:0',
|
||||
'unite' => 'nullable|string|max:30',
|
||||
'active' => 'boolean',
|
||||
]);
|
||||
|
||||
$article->update($validated);
|
||||
|
||||
return redirect()->route('articles.index')
|
||||
->with('success', 'Article mis à jour.');
|
||||
}
|
||||
|
||||
public function destroy(Article $article): RedirectResponse
|
||||
{
|
||||
$article->update(['active' => false]);
|
||||
|
||||
return back()->with('success', 'Article désactivé.');
|
||||
}
|
||||
}
|
||||
52
app/Http/Controllers/Auth/AuthenticatedSessionController.php
Normal file
52
app/Http/Controllers/Auth/AuthenticatedSessionController.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Auth\LoginRequest;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class AuthenticatedSessionController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the login view.
|
||||
*/
|
||||
public function create(): Response
|
||||
{
|
||||
return Inertia::render('Auth/Login', [
|
||||
'canResetPassword' => Route::has('password.request'),
|
||||
'status' => session('status'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming authentication request.
|
||||
*/
|
||||
public function store(LoginRequest $request): RedirectResponse
|
||||
{
|
||||
$request->authenticate();
|
||||
|
||||
$request->session()->regenerate();
|
||||
|
||||
return redirect()->intended(route('dashboard', absolute: false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy an authenticated session.
|
||||
*/
|
||||
public function destroy(Request $request): RedirectResponse
|
||||
{
|
||||
Auth::guard('web')->logout();
|
||||
|
||||
$request->session()->invalidate();
|
||||
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return redirect('/');
|
||||
}
|
||||
}
|
||||
41
app/Http/Controllers/Auth/ConfirmablePasswordController.php
Normal file
41
app/Http/Controllers/Auth/ConfirmablePasswordController.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class ConfirmablePasswordController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the confirm password view.
|
||||
*/
|
||||
public function show(): Response
|
||||
{
|
||||
return Inertia::render('Auth/ConfirmPassword');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm the user's password.
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
if (! Auth::guard('web')->validate([
|
||||
'email' => $request->user()->email,
|
||||
'password' => $request->password,
|
||||
])) {
|
||||
throw ValidationException::withMessages([
|
||||
'password' => __('auth.password'),
|
||||
]);
|
||||
}
|
||||
|
||||
$request->session()->put('auth.password_confirmed_at', time());
|
||||
|
||||
return redirect()->intended(route('dashboard', absolute: false));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EmailVerificationNotificationController extends Controller
|
||||
{
|
||||
/**
|
||||
* Send a new email verification notification.
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
if ($request->user()->hasVerifiedEmail()) {
|
||||
return redirect()->intended(route('dashboard', absolute: false));
|
||||
}
|
||||
|
||||
$request->user()->sendEmailVerificationNotification();
|
||||
|
||||
return back()->with('status', 'verification-link-sent');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class EmailVerificationPromptController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the email verification prompt.
|
||||
*/
|
||||
public function __invoke(Request $request): RedirectResponse|Response
|
||||
{
|
||||
return $request->user()->hasVerifiedEmail()
|
||||
? redirect()->intended(route('dashboard', absolute: false))
|
||||
: Inertia::render('Auth/VerifyEmail', ['status' => session('status')]);
|
||||
}
|
||||
}
|
||||
69
app/Http/Controllers/Auth/NewPasswordController.php
Normal file
69
app/Http/Controllers/Auth/NewPasswordController.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Auth\Events\PasswordReset;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rules;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class NewPasswordController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the password reset view.
|
||||
*/
|
||||
public function create(Request $request): Response
|
||||
{
|
||||
return Inertia::render('Auth/ResetPassword', [
|
||||
'email' => $request->email,
|
||||
'token' => $request->route('token'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming new password request.
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'token' => 'required',
|
||||
'email' => 'required|email',
|
||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||
]);
|
||||
|
||||
// Here we will attempt to reset the user's password. If it is successful we
|
||||
// will update the password on an actual user model and persist it to the
|
||||
// database. Otherwise we will parse the error and return the response.
|
||||
$status = Password::reset(
|
||||
$request->only('email', 'password', 'password_confirmation', 'token'),
|
||||
function ($user) use ($request) {
|
||||
$user->forceFill([
|
||||
'password' => Hash::make($request->password),
|
||||
'remember_token' => Str::random(60),
|
||||
])->save();
|
||||
|
||||
event(new PasswordReset($user));
|
||||
}
|
||||
);
|
||||
|
||||
// If the password was successfully reset, we will redirect the user back to
|
||||
// the application's home authenticated view. If there is an error we can
|
||||
// redirect them back to where they came from with their error message.
|
||||
if ($status == Password::PASSWORD_RESET) {
|
||||
return redirect()->route('login')->with('status', __($status));
|
||||
}
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'email' => [trans($status)],
|
||||
]);
|
||||
}
|
||||
}
|
||||
29
app/Http/Controllers/Auth/PasswordController.php
Normal file
29
app/Http/Controllers/Auth/PasswordController.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
class PasswordController extends Controller
|
||||
{
|
||||
/**
|
||||
* Update the user's password.
|
||||
*/
|
||||
public function update(Request $request): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'current_password' => ['required', 'current_password'],
|
||||
'password' => ['required', Password::defaults(), 'confirmed'],
|
||||
]);
|
||||
|
||||
$request->user()->update([
|
||||
'password' => Hash::make($validated['password']),
|
||||
]);
|
||||
|
||||
return back();
|
||||
}
|
||||
}
|
||||
51
app/Http/Controllers/Auth/PasswordResetLinkController.php
Normal file
51
app/Http/Controllers/Auth/PasswordResetLinkController.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class PasswordResetLinkController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the password reset link request view.
|
||||
*/
|
||||
public function create(): Response
|
||||
{
|
||||
return Inertia::render('Auth/ForgotPassword', [
|
||||
'status' => session('status'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming password reset link request.
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'email' => 'required|email',
|
||||
]);
|
||||
|
||||
// We will send the password reset link to this user. Once we have attempted
|
||||
// to send the link, we will examine the response then see the message we
|
||||
// need to show to the user. Finally, we'll send out a proper response.
|
||||
$status = Password::sendResetLink(
|
||||
$request->only('email')
|
||||
);
|
||||
|
||||
if ($status == Password::RESET_LINK_SENT) {
|
||||
return back()->with('status', __($status));
|
||||
}
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'email' => [trans($status)],
|
||||
]);
|
||||
}
|
||||
}
|
||||
52
app/Http/Controllers/Auth/RegisteredUserController.php
Normal file
52
app/Http/Controllers/Auth/RegisteredUserController.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class RegisteredUserController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the registration view.
|
||||
*/
|
||||
public function create(): Response
|
||||
{
|
||||
return Inertia::render('Auth/Register');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming registration request.
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|string|lowercase|email|max:255|unique:'.User::class,
|
||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||
]);
|
||||
|
||||
$user = User::create([
|
||||
'name' => $request->name,
|
||||
'email' => $request->email,
|
||||
'password' => Hash::make($request->password),
|
||||
]);
|
||||
|
||||
event(new Registered($user));
|
||||
|
||||
Auth::login($user);
|
||||
|
||||
return redirect(route('dashboard', absolute: false));
|
||||
}
|
||||
}
|
||||
27
app/Http/Controllers/Auth/VerifyEmailController.php
Normal file
27
app/Http/Controllers/Auth/VerifyEmailController.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Auth\Events\Verified;
|
||||
use Illuminate\Foundation\Auth\EmailVerificationRequest;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
|
||||
class VerifyEmailController extends Controller
|
||||
{
|
||||
/**
|
||||
* Mark the authenticated user's email address as verified.
|
||||
*/
|
||||
public function __invoke(EmailVerificationRequest $request): RedirectResponse
|
||||
{
|
||||
if ($request->user()->hasVerifiedEmail()) {
|
||||
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
|
||||
}
|
||||
|
||||
if ($request->user()->markEmailAsVerified()) {
|
||||
event(new Verified($request->user()));
|
||||
}
|
||||
|
||||
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
|
||||
}
|
||||
}
|
||||
63
app/Http/Controllers/CategorieController.php
Normal file
63
app/Http/Controllers/CategorieController.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Categorie;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class CategorieController extends Controller
|
||||
{
|
||||
public function index(): Response
|
||||
{
|
||||
return Inertia::render('Categories/Index', [
|
||||
'categories' => Categorie::withCount('articles')->orderBy('ordre')->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$this->authorize('create', Categorie::class);
|
||||
|
||||
$validated = $request->validate([
|
||||
'nom' => 'required|string|max:100|unique:categories,nom',
|
||||
'description' => 'nullable|string',
|
||||
'couleur' => 'nullable|string|max:7',
|
||||
'icone' => 'nullable|string|max:50',
|
||||
'ordre' => 'nullable|integer|min:0',
|
||||
]);
|
||||
|
||||
Categorie::create($validated);
|
||||
|
||||
return back()->with('success', 'Catégorie créée.');
|
||||
}
|
||||
|
||||
public function update(Request $request, Categorie $categorie): RedirectResponse
|
||||
{
|
||||
$this->authorize('update', Categorie::class);
|
||||
|
||||
$validated = $request->validate([
|
||||
'nom' => 'required|string|max:100|unique:categories,nom,' . $categorie->id,
|
||||
'description' => 'nullable|string',
|
||||
'couleur' => 'nullable|string|max:7',
|
||||
'icone' => 'nullable|string|max:50',
|
||||
'ordre' => 'nullable|integer|min:0',
|
||||
'active' => 'boolean',
|
||||
]);
|
||||
|
||||
$categorie->update($validated);
|
||||
|
||||
return back()->with('success', 'Catégorie mise à jour.');
|
||||
}
|
||||
|
||||
public function destroy(Categorie $categorie): RedirectResponse
|
||||
{
|
||||
$this->authorize('delete', Categorie::class);
|
||||
|
||||
$categorie->update(['active' => false]);
|
||||
|
||||
return back()->with('success', 'Catégorie désactivée.');
|
||||
}
|
||||
}
|
||||
247
app/Http/Controllers/CommandeController.php
Normal file
247
app/Http/Controllers/CommandeController.php
Normal file
@@ -0,0 +1,247 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Article;
|
||||
use App\Models\Categorie;
|
||||
use App\Models\Commande;
|
||||
use App\Models\Fournisseur;
|
||||
use App\Models\Service;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class CommandeController extends Controller
|
||||
{
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$query = Commande::with(['service', 'fournisseur', 'demandeur'])
|
||||
->when($request->service_id, fn ($q) => $q->parService($request->service_id))
|
||||
->when($request->fournisseur_id, fn ($q) => $q->parFournisseur($request->fournisseur_id))
|
||||
->when($request->statut, fn ($q) => $q->parStatut($request->statut))
|
||||
->when($request->priorite, fn ($q) => $q->where('priorite', $request->priorite))
|
||||
->when($request->date_from, fn ($q) => $q->whereDate('date_demande', '>=', $request->date_from))
|
||||
->when($request->date_to, fn ($q) => $q->whereDate('date_demande', '<=', $request->date_to))
|
||||
->when($request->search, function ($q) use ($request) {
|
||||
$q->where(function ($sub) use ($request) {
|
||||
$sub->where('numero_commande', 'like', "%{$request->search}%")
|
||||
->orWhere('objet', 'like', "%{$request->search}%")
|
||||
->orWhereHas('fournisseur', fn ($f) => $f->where('nom', 'like', "%{$request->search}%"));
|
||||
});
|
||||
});
|
||||
|
||||
$commandes = $query->latest()->paginate(20)->withQueryString();
|
||||
|
||||
return Inertia::render('Commandes/Index', [
|
||||
'commandes' => $commandes,
|
||||
'services' => Service::all(),
|
||||
'fournisseurs' => Fournisseur::active()->orderBy('nom')->get(),
|
||||
'filters' => $request->only(['search', 'service_id', 'fournisseur_id', 'statut', 'priorite', 'date_from', 'date_to']),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(): Response
|
||||
{
|
||||
$this->authorize('create', Commande::class);
|
||||
|
||||
return Inertia::render('Commandes/Create', [
|
||||
'services' => Service::all(),
|
||||
'fournisseurs' => Fournisseur::active()->orderBy('nom')->get(),
|
||||
'categories' => Categorie::active()->orderBy('ordre')->get(),
|
||||
'articles' => Article::active()->with('categorie')->orderBy('designation')->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$this->authorize('create', Commande::class);
|
||||
|
||||
$validated = $request->validate([
|
||||
'service_id' => 'required|exists:services,id',
|
||||
'fournisseur_id' => 'nullable|exists:fournisseurs,id',
|
||||
'objet' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'justification' => 'nullable|string',
|
||||
'priorite' => 'required|in:normale,haute,urgente',
|
||||
'reference_fournisseur' => 'nullable|string|max:100',
|
||||
'imputation_budgetaire' => 'nullable|string|max:100',
|
||||
'date_demande' => 'required|date',
|
||||
'date_souhaitee' => 'nullable|date|after_or_equal:date_demande',
|
||||
'date_livraison_prevue' => 'nullable|date',
|
||||
'notes' => 'nullable|string',
|
||||
'notes_fournisseur' => 'nullable|string',
|
||||
'lignes' => 'array',
|
||||
'lignes.*.designation' => 'required|string|max:255',
|
||||
'lignes.*.reference' => 'nullable|string|max:100',
|
||||
'lignes.*.quantite' => 'required|numeric|min:0.001',
|
||||
'lignes.*.unite' => 'nullable|string|max:30',
|
||||
'lignes.*.prix_unitaire_ht' => 'nullable|numeric|min:0',
|
||||
'lignes.*.taux_tva' => 'nullable|numeric|min:0|max:100',
|
||||
'lignes.*.categorie_id' => 'nullable|exists:categories,id',
|
||||
'lignes.*.article_id' => 'nullable|exists:articles,id',
|
||||
'lignes.*.notes' => 'nullable|string',
|
||||
]);
|
||||
|
||||
DB::transaction(function () use ($validated, $request) {
|
||||
$commande = Commande::create([
|
||||
...$validated,
|
||||
'numero_commande' => Commande::genererNumero(),
|
||||
'user_id' => $request->user()->id,
|
||||
'statut' => 'brouillon',
|
||||
]);
|
||||
|
||||
foreach ($validated['lignes'] ?? [] as $index => $ligne) {
|
||||
$commande->lignes()->create(array_merge($ligne, ['ordre' => $index]));
|
||||
}
|
||||
});
|
||||
|
||||
return redirect()->route('commandes.index')
|
||||
->with('success', 'Commande créée avec succès.');
|
||||
}
|
||||
|
||||
public function show(Commande $commande): Response
|
||||
{
|
||||
$this->authorize('view', $commande);
|
||||
|
||||
$commande->load([
|
||||
'service', 'fournisseur', 'demandeur', 'validateur', 'acheteur',
|
||||
'lignes.categorie',
|
||||
'historique.user',
|
||||
'piecesJointes.user',
|
||||
]);
|
||||
|
||||
$transitionsDisponibles = collect(Commande::STATUT_TRANSITIONS[$commande->statut] ?? [])
|
||||
->filter(fn ($statut) => request()->user()->hasRole('admin') || $this->userCanTransition(request()->user(), $commande, $statut))
|
||||
->values();
|
||||
|
||||
return Inertia::render('Commandes/Show', [
|
||||
'commande' => $commande,
|
||||
'transitionsDisponibles' => $transitionsDisponibles,
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(Commande $commande): Response
|
||||
{
|
||||
$this->authorize('update', $commande);
|
||||
|
||||
$commande->load('lignes.categorie');
|
||||
|
||||
return Inertia::render('Commandes/Edit', [
|
||||
'commande' => $commande,
|
||||
'services' => Service::all(),
|
||||
'fournisseurs' => Fournisseur::active()->orderBy('nom')->get(),
|
||||
'categories' => Categorie::active()->orderBy('ordre')->get(),
|
||||
'articles' => Article::active()->with('categorie')->orderBy('designation')->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, Commande $commande): RedirectResponse
|
||||
{
|
||||
$this->authorize('update', $commande);
|
||||
|
||||
$validated = $request->validate([
|
||||
'service_id' => 'required|exists:services,id',
|
||||
'fournisseur_id' => 'nullable|exists:fournisseurs,id',
|
||||
'objet' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'justification' => 'nullable|string',
|
||||
'priorite' => 'required|in:normale,haute,urgente',
|
||||
'reference_fournisseur' => 'nullable|string|max:100',
|
||||
'imputation_budgetaire' => 'nullable|string|max:100',
|
||||
'date_demande' => 'required|date',
|
||||
'date_souhaitee' => 'nullable|date',
|
||||
'date_livraison_prevue' => 'nullable|date',
|
||||
'notes' => 'nullable|string',
|
||||
'notes_fournisseur' => 'nullable|string',
|
||||
'lignes' => 'array',
|
||||
'lignes.*.id' => 'nullable|integer',
|
||||
'lignes.*.designation' => 'required|string|max:255',
|
||||
'lignes.*.reference' => 'nullable|string|max:100',
|
||||
'lignes.*.quantite' => 'required|numeric|min:0.001',
|
||||
'lignes.*.quantite_recue' => 'nullable|numeric|min:0',
|
||||
'lignes.*.unite' => 'nullable|string|max:30',
|
||||
'lignes.*.prix_unitaire_ht' => 'nullable|numeric|min:0',
|
||||
'lignes.*.taux_tva' => 'nullable|numeric|min:0|max:100',
|
||||
'lignes.*.categorie_id' => 'nullable|exists:categories,id',
|
||||
'lignes.*.article_id' => 'nullable|exists:articles,id',
|
||||
'lignes.*.notes' => 'nullable|string',
|
||||
]);
|
||||
|
||||
DB::transaction(function () use ($validated, $commande) {
|
||||
$commande->update($validated);
|
||||
|
||||
$lignesData = $validated['lignes'] ?? [];
|
||||
$lignesIds = collect($lignesData)->pluck('id')->filter()->all();
|
||||
|
||||
// Supprimer les lignes retirées
|
||||
$commande->lignes()->whereNotIn('id', $lignesIds)->delete();
|
||||
|
||||
foreach ($lignesData as $index => $ligneData) {
|
||||
$commande->lignes()->updateOrCreate(
|
||||
['id' => $ligneData['id'] ?? null],
|
||||
array_merge($ligneData, ['ordre' => $index, 'commande_id' => $commande->id])
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return redirect()->route('commandes.show', $commande)
|
||||
->with('success', 'Commande mise à jour.');
|
||||
}
|
||||
|
||||
public function destroy(Commande $commande): RedirectResponse
|
||||
{
|
||||
$this->authorize('delete', $commande);
|
||||
|
||||
$commande->delete();
|
||||
|
||||
return redirect()->route('commandes.index')
|
||||
->with('success', 'Commande supprimée.');
|
||||
}
|
||||
|
||||
public function transition(Request $request, Commande $commande): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'statut' => 'required|string',
|
||||
'commentaire' => 'nullable|string|max:1000',
|
||||
]);
|
||||
|
||||
$this->authorize('transition', [$commande, $validated['statut']]);
|
||||
|
||||
$ok = $commande->transitionnerVers($validated['statut'], $request->user(), $validated['commentaire'] ?? null);
|
||||
|
||||
if (!$ok) {
|
||||
return back()->with('error', 'Transition non autorisée.');
|
||||
}
|
||||
|
||||
return redirect()->route('commandes.show', $commande)
|
||||
->with('success', 'Statut mis à jour : ' . Commande::STATUTS_LABELS[$validated['statut']]);
|
||||
}
|
||||
|
||||
private function userCanTransition($user, Commande $commande, string $statut): bool
|
||||
{
|
||||
try {
|
||||
$this->authorize('transition', [$commande, $statut]);
|
||||
return true;
|
||||
} catch (\Exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function exportPdf(Commande $commande)
|
||||
{
|
||||
$this->authorize('view', $commande);
|
||||
|
||||
$commande->load([
|
||||
'service', 'fournisseur', 'demandeur', 'validateur', 'acheteur',
|
||||
'lignes.categorie',
|
||||
]);
|
||||
|
||||
$pdf = \Barryvdh\DomPDF\Facade\Pdf::loadView('pdf.commande', [
|
||||
'commande' => $commande,
|
||||
]);
|
||||
|
||||
return $pdf->stream('commande-' . $commande->numero_commande . '.pdf');
|
||||
}
|
||||
}
|
||||
161
app/Http/Controllers/ContratController.php
Normal file
161
app/Http/Controllers/ContratController.php
Normal file
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Contrat;
|
||||
use App\Models\Fournisseur;
|
||||
use App\Models\Service;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ContratController extends Controller
|
||||
{
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$this->authorize('viewAny', Contrat::class);
|
||||
|
||||
$query = Contrat::with(['fournisseur', 'service']);
|
||||
|
||||
if (!$request->user()->hasRole('admin')) {
|
||||
$query->where('service_id', $request->user()->service_id);
|
||||
}
|
||||
|
||||
$query->when($request->search, function ($q, $search) {
|
||||
$q->where(function ($sub) use ($search) {
|
||||
$sub->where('titre', 'like', "%{$search}%")
|
||||
->orWhereHas('fournisseur', fn($f) => $f->where('nom', 'like', "%{$search}%"));
|
||||
});
|
||||
})->when($request->service_id, function ($q, $serviceId) use ($request) {
|
||||
if ($request->user()->hasRole('admin')) {
|
||||
$q->where('service_id', $serviceId);
|
||||
}
|
||||
})->when($request->fournisseur_id, function ($q, $fournisseurId) {
|
||||
$q->where('fournisseur_id', $fournisseurId);
|
||||
})->when($request->statut, function ($q, $statut) {
|
||||
$q->where('statut', $statut);
|
||||
});
|
||||
|
||||
$contrats = $query->orderBy('date_echeance', 'asc')->paginate(20)->withQueryString();
|
||||
|
||||
// Calculate estProcheEcheance for the frontend
|
||||
$contrats->getCollection()->transform(function ($contrat) {
|
||||
$contrat->append(['est_proche_echeance', 'est_en_retard']);
|
||||
return $contrat;
|
||||
});
|
||||
|
||||
return Inertia::render('Contrats/Index', [
|
||||
'contrats' => $contrats,
|
||||
'services' => $request->user()->hasRole('admin') ? Service::all() : Service::where('id', $request->user()->service_id)->get(),
|
||||
'fournisseurs' => Fournisseur::active()->orderBy('nom')->get(),
|
||||
'filters' => $request->only(['search', 'service_id', 'fournisseur_id', 'statut']),
|
||||
'statuts' => Contrat::STATUTS_LABELS,
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(Request $request): Response
|
||||
{
|
||||
$this->authorize('create', Contrat::class);
|
||||
|
||||
return Inertia::render('Contrats/Create', [
|
||||
'services' => $request->user()->hasRole('admin') ? Service::all() : Service::where('id', $request->user()->service_id)->get(),
|
||||
'fournisseurs' => Fournisseur::active()->orderBy('nom')->get(),
|
||||
'statuts' => Contrat::STATUTS_LABELS,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$this->authorize('create', Contrat::class);
|
||||
|
||||
$rules = [
|
||||
'titre' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'fournisseur_id' => 'required|exists:fournisseurs,id',
|
||||
'date_debut' => 'nullable|date',
|
||||
'date_echeance' => 'required|date',
|
||||
'statut' => 'required|in:actif,a_renouveler,expire,resilie',
|
||||
'montant' => 'nullable|numeric|min:0',
|
||||
'preavis_jours' => 'nullable|integer|min:0',
|
||||
];
|
||||
|
||||
// Only admins can select other services, otherwise we force the user's service
|
||||
if ($request->user()->hasRole('admin')) {
|
||||
$rules['service_id'] = 'required|exists:services,id';
|
||||
}
|
||||
|
||||
$validated = $request->validate($rules);
|
||||
|
||||
if (!$request->user()->hasRole('admin')) {
|
||||
$validated['service_id'] = $request->user()->service_id;
|
||||
}
|
||||
|
||||
$contrat = Contrat::create($validated);
|
||||
|
||||
return redirect()->route('contrats.index')
|
||||
->with('success', 'Contrat créé avec succès.');
|
||||
}
|
||||
|
||||
public function show(Contrat $contrat): Response
|
||||
{
|
||||
$this->authorize('view', $contrat);
|
||||
|
||||
$contrat->load(['fournisseur', 'service', 'piecesJointes.user']);
|
||||
$contrat->append(['est_proche_echeance', 'est_en_retard']);
|
||||
|
||||
return Inertia::render('Contrats/Show', [
|
||||
'contrat' => $contrat,
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(Contrat $contrat, Request $request): Response
|
||||
{
|
||||
$this->authorize('update', $contrat);
|
||||
|
||||
return Inertia::render('Contrats/Edit', [
|
||||
'contrat' => $contrat,
|
||||
'services' => $request->user()->hasRole('admin') ? Service::all() : Service::where('id', $request->user()->service_id)->get(),
|
||||
'fournisseurs' => Fournisseur::active()->orderBy('nom')->get(),
|
||||
'statuts' => Contrat::STATUTS_LABELS,
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, Contrat $contrat): RedirectResponse
|
||||
{
|
||||
$this->authorize('update', $contrat);
|
||||
|
||||
$rules = [
|
||||
'titre' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'fournisseur_id' => 'required|exists:fournisseurs,id',
|
||||
'date_debut' => 'nullable|date',
|
||||
'date_echeance' => 'required|date',
|
||||
'statut' => 'required|in:actif,a_renouveler,expire,resilie',
|
||||
'montant' => 'nullable|numeric|min:0',
|
||||
'preavis_jours' => 'nullable|integer|min:0',
|
||||
];
|
||||
|
||||
if ($request->user()->hasRole('admin')) {
|
||||
$rules['service_id'] = 'required|exists:services,id';
|
||||
}
|
||||
|
||||
$validated = $request->validate($rules);
|
||||
|
||||
$contrat->update($validated);
|
||||
|
||||
return redirect()->route('contrats.show', $contrat)
|
||||
->with('success', 'Contrat mis à jour.');
|
||||
}
|
||||
|
||||
public function destroy(Contrat $contrat): RedirectResponse
|
||||
{
|
||||
$this->authorize('delete', $contrat);
|
||||
|
||||
$contrat->delete();
|
||||
|
||||
return redirect()->route('contrats.index')
|
||||
->with('success', 'Contrat supprimé.');
|
||||
}
|
||||
}
|
||||
8
app/Http/Controllers/Controller.php
Normal file
8
app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
use \Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
}
|
||||
103
app/Http/Controllers/DashboardController.php
Normal file
103
app/Http/Controllers/DashboardController.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Commande;
|
||||
use App\Models\Service;
|
||||
use App\Models\Contrat;
|
||||
use App\Models\Domaine;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class DashboardController extends Controller
|
||||
{
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$user = $request->user();
|
||||
$stats = [
|
||||
'total' => Commande::count(),
|
||||
'en_cours' => Commande::enCours()->count(),
|
||||
'en_retard' => Commande::enRetard()->count(),
|
||||
'brouillons' => Commande::parStatut('brouillon')->count(),
|
||||
'en_attente_validation' => Commande::parStatut('en_attente_validation')->count(),
|
||||
'validees' => Commande::parStatut('validee')->count(),
|
||||
'commandees' => Commande::parStatut('commandee')->count(),
|
||||
'partiellement_recues' => Commande::parStatut('partiellement_recue')->count(),
|
||||
'recues_complete' => Commande::parStatut('recue_complete')->count(),
|
||||
];
|
||||
|
||||
$commandesRecentes = Commande::with(['service', 'fournisseur', 'demandeur'])
|
||||
->latest()
|
||||
->limit(8)
|
||||
->get();
|
||||
|
||||
$commandesEnRetard = Commande::enRetard()
|
||||
->with(['service', 'fournisseur', 'demandeur'])
|
||||
->orderBy('date_souhaitee')
|
||||
->limit(10)
|
||||
->get();
|
||||
|
||||
$commandesUrgentes = Commande::urgentes()
|
||||
->enCours()
|
||||
->with(['service', 'fournisseur', 'demandeur'])
|
||||
->latest()
|
||||
->limit(5)
|
||||
->get();
|
||||
|
||||
$statsParStatut = Commande::select('statut', DB::raw('count(*) as total'))
|
||||
->groupBy('statut')
|
||||
->get()
|
||||
->keyBy('statut');
|
||||
|
||||
$statsParService = Service::withCount([
|
||||
'commandes',
|
||||
'commandes as commandes_en_cours_count' => fn ($q) => $q->enCours(),
|
||||
])->get();
|
||||
|
||||
$montantParMois = Commande::select(
|
||||
DB::raw('YEAR(date_demande) as annee'),
|
||||
DB::raw('MONTH(date_demande) as mois'),
|
||||
DB::raw('SUM(montant_ttc) as total_ttc'),
|
||||
DB::raw('COUNT(*) as nb_commandes')
|
||||
)
|
||||
->whereYear('date_demande', now()->year)
|
||||
->whereNotIn('statut', ['annulee'])
|
||||
->groupBy('annee', 'mois')
|
||||
->orderBy('mois')
|
||||
->get();
|
||||
|
||||
// Stats Contrats
|
||||
$contratsQuery = Contrat::query();
|
||||
if (!$user->hasRole('admin')) {
|
||||
$contratsQuery->where('service_id', $user->service_id);
|
||||
}
|
||||
$tousContrats = $contratsQuery->get();
|
||||
$statsContrats = [
|
||||
'total' => $tousContrats->count(),
|
||||
'proches' => $tousContrats->filter(fn($c) => $c->est_proche_echeance && !$c->est_en_retard)->count(),
|
||||
'en_retard' => $tousContrats->filter(fn($c) => $c->est_en_retard)->count(),
|
||||
];
|
||||
|
||||
// Stats Domaines (Visibles par tous)
|
||||
$tousDomaines = Domaine::all();
|
||||
$statsDomaines = [
|
||||
'total' => $tousDomaines->count(),
|
||||
'proches' => $tousDomaines->filter(fn($d) => $d->est_proche_echeance && !$d->est_en_retard)->count(),
|
||||
'en_retard' => $tousDomaines->filter(fn($d) => $d->est_en_retard)->count(),
|
||||
];
|
||||
|
||||
return Inertia::render('Dashboard/Index', compact(
|
||||
'stats',
|
||||
'commandesRecentes',
|
||||
'commandesEnRetard',
|
||||
'commandesUrgentes',
|
||||
'statsParStatut',
|
||||
'statsParService',
|
||||
'montantParMois',
|
||||
'statsContrats',
|
||||
'statsDomaines',
|
||||
));
|
||||
}
|
||||
}
|
||||
148
app/Http/Controllers/DomaineController.php
Normal file
148
app/Http/Controllers/DomaineController.php
Normal file
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Domaine;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
use Iodev\Whois\Factory as WhoisFactory;
|
||||
use Iodev\Whois\Exceptions\ConnectionException;
|
||||
use Iodev\Whois\Exceptions\ServerMismatchException;
|
||||
use Iodev\Whois\Exceptions\WhoisException;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class DomaineController extends Controller
|
||||
{
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$this->authorize('viewAny', Domaine::class);
|
||||
|
||||
$query = Domaine::query();
|
||||
|
||||
$query->when($request->search, function ($q, $search) {
|
||||
$q->where('nom', 'like', "%{$search}%")
|
||||
->orWhere('prestataire', 'like', "%{$search}%")
|
||||
->orWhere('hebergeur', 'like', "%{$search}%");
|
||||
});
|
||||
|
||||
$domaines = $query->orderBy('date_echeance', 'asc')->paginate(20)->withQueryString();
|
||||
|
||||
$domaines->getCollection()->transform(function ($domaine) {
|
||||
$domaine->append(['est_proche_echeance', 'est_en_retard']);
|
||||
return $domaine;
|
||||
});
|
||||
|
||||
return Inertia::render('Domaines/Index', [
|
||||
'domaines' => $domaines,
|
||||
'filters' => $request->only(['search']),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(): Response
|
||||
{
|
||||
$this->authorize('create', Domaine::class);
|
||||
|
||||
return Inertia::render('Domaines/Create');
|
||||
}
|
||||
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$this->authorize('create', Domaine::class);
|
||||
|
||||
$validated = $request->validate([
|
||||
'nom' => 'required|string|max:255|unique:domaines,nom',
|
||||
'date_echeance' => 'nullable|date',
|
||||
'prestataire' => 'nullable|string',
|
||||
'hebergeur' => 'nullable|string|max:255',
|
||||
]);
|
||||
|
||||
$domaine = Domaine::create($validated);
|
||||
|
||||
// Si la date d'échéance n'est pas fournie, on tente de la récupérer via WHOIS
|
||||
if (empty($validated['date_echeance'])) {
|
||||
$this->syncWhoisDate($domaine);
|
||||
}
|
||||
|
||||
return redirect()->route('domaines.index')
|
||||
->with('success', 'Domaine créé avec succès.');
|
||||
}
|
||||
|
||||
public function edit(Domaine $domaine): Response
|
||||
{
|
||||
$this->authorize('update', $domaine);
|
||||
|
||||
return Inertia::render('Domaines/Edit', [
|
||||
'domaine' => $domaine,
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, Domaine $domaine): RedirectResponse
|
||||
{
|
||||
$this->authorize('update', $domaine);
|
||||
|
||||
$validated = $request->validate([
|
||||
'nom' => 'required|string|max:255|unique:domaines,nom,' . $domaine->id,
|
||||
'date_echeance' => 'nullable|date',
|
||||
'prestataire' => 'nullable|string',
|
||||
'hebergeur' => 'nullable|string|max:255',
|
||||
]);
|
||||
|
||||
$domaine->update($validated);
|
||||
|
||||
return redirect()->route('domaines.index')
|
||||
->with('success', 'Domaine mis à jour.');
|
||||
}
|
||||
|
||||
public function destroy(Domaine $domaine): RedirectResponse
|
||||
{
|
||||
$this->authorize('delete', $domaine);
|
||||
|
||||
$domaine->delete();
|
||||
|
||||
return redirect()->route('domaines.index')
|
||||
->with('success', 'Domaine supprimé.');
|
||||
}
|
||||
|
||||
public function syncWhois(Domaine $domaine): RedirectResponse
|
||||
{
|
||||
$this->authorize('update', $domaine);
|
||||
|
||||
if ($this->syncWhoisDate($domaine)) {
|
||||
return back()->with('success', 'Date d\'échéance synchronisée avec le Whois depuis le port 43.');
|
||||
}
|
||||
|
||||
return back()->with('error', 'Impossible de récupérer la date d\'échéance pour ce domaine (Whois indisponible ou format inconnu).');
|
||||
}
|
||||
|
||||
private function syncWhoisDate(Domaine $domaine): bool
|
||||
{
|
||||
try {
|
||||
$whois = WhoisFactory::get()->createWhois();
|
||||
$info = $whois->loadDomainInfo($domaine->nom);
|
||||
|
||||
if ($info && $info->expirationDate) {
|
||||
// expirationDate is a unix timestamp integer in the package
|
||||
$domaine->update([
|
||||
'date_echeance' => Carbon::createFromTimestamp($info->expirationDate)->format('Y-m-d')
|
||||
]);
|
||||
return true;
|
||||
}
|
||||
} catch (ConnectionException $e) {
|
||||
// Port 43 bloqué ou timeout
|
||||
return false;
|
||||
} catch (ServerMismatchException $e) {
|
||||
// Serveur Whois inconnu
|
||||
return false;
|
||||
} catch (WhoisException $e) {
|
||||
// Autre erreur Whois
|
||||
return false;
|
||||
} catch (\Exception $e) {
|
||||
// Erreur inattendue
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
116
app/Http/Controllers/FournisseurController.php
Normal file
116
app/Http/Controllers/FournisseurController.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Fournisseur;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class FournisseurController extends Controller
|
||||
{
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$this->authorize('viewAny', Fournisseur::class);
|
||||
|
||||
$query = Fournisseur::withCount('commandes')
|
||||
->when($request->search, fn ($q) => $q->where(function ($sub) use ($request) {
|
||||
$sub->where('nom', 'like', "%{$request->search}%")
|
||||
->orWhere('ville', 'like', "%{$request->search}%")
|
||||
->orWhere('email', 'like', "%{$request->search}%");
|
||||
}))
|
||||
->when($request->has('active') && $request->active !== '', fn ($q) => $q->where('active', $request->active));
|
||||
|
||||
$fournisseurs = $query->orderBy('nom')->paginate(20)->withQueryString();
|
||||
|
||||
return Inertia::render('Fournisseurs/Index', [
|
||||
'fournisseurs' => $fournisseurs,
|
||||
'filters' => $request->only(['search', 'active']),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(): Response
|
||||
{
|
||||
$this->authorize('create', Fournisseur::class);
|
||||
|
||||
return Inertia::render('Fournisseurs/Create');
|
||||
}
|
||||
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$this->authorize('create', Fournisseur::class);
|
||||
|
||||
$validated = $this->validateFournisseur($request);
|
||||
Fournisseur::create($validated);
|
||||
|
||||
return redirect()->route('fournisseurs.index')
|
||||
->with('success', 'Fournisseur créé avec succès.');
|
||||
}
|
||||
|
||||
public function show(Fournisseur $fournisseur): Response
|
||||
{
|
||||
$this->authorize('view', $fournisseur);
|
||||
|
||||
$fournisseur->load(['commandes' => fn ($q) => $q->with('service', 'demandeur')->latest()->limit(10)]);
|
||||
|
||||
return Inertia::render('Fournisseurs/Show', compact('fournisseur'));
|
||||
}
|
||||
|
||||
public function edit(Fournisseur $fournisseur): Response
|
||||
{
|
||||
$this->authorize('update', $fournisseur);
|
||||
|
||||
return Inertia::render('Fournisseurs/Edit', compact('fournisseur'));
|
||||
}
|
||||
|
||||
public function update(Request $request, Fournisseur $fournisseur): RedirectResponse
|
||||
{
|
||||
$this->authorize('update', $fournisseur);
|
||||
|
||||
$validated = $this->validateFournisseur($request);
|
||||
$fournisseur->update($validated);
|
||||
|
||||
return redirect()->route('fournisseurs.show', $fournisseur)
|
||||
->with('success', 'Fournisseur mis à jour.');
|
||||
}
|
||||
|
||||
public function destroy(Fournisseur $fournisseur): RedirectResponse
|
||||
{
|
||||
$this->authorize('delete', $fournisseur);
|
||||
|
||||
$fournisseur->delete();
|
||||
|
||||
return redirect()->route('fournisseurs.index')
|
||||
->with('success', 'Fournisseur supprimé.');
|
||||
}
|
||||
|
||||
public function toggleActive(Fournisseur $fournisseur): RedirectResponse
|
||||
{
|
||||
$this->authorize('update', $fournisseur);
|
||||
|
||||
$fournisseur->update(['active' => !$fournisseur->active]);
|
||||
|
||||
return back()->with('success', 'Statut du fournisseur mis à jour.');
|
||||
}
|
||||
|
||||
private function validateFournisseur(Request $request): array
|
||||
{
|
||||
return $request->validate([
|
||||
'nom' => 'required|string|max:255',
|
||||
'raison_sociale' => 'nullable|string|max:255',
|
||||
'siret' => 'nullable|string|max:14',
|
||||
'adresse' => 'nullable|string',
|
||||
'code_postal' => 'nullable|string|max:10',
|
||||
'ville' => 'nullable|string|max:100',
|
||||
'telephone' => 'nullable|string|max:20',
|
||||
'email' => 'nullable|email|max:255',
|
||||
'contact_commercial' => 'nullable|string|max:255',
|
||||
'email_commercial' => 'nullable|email|max:255',
|
||||
'telephone_commercial' => 'nullable|string|max:20',
|
||||
'site_web' => 'nullable|url|max:255',
|
||||
'notes' => 'nullable|string',
|
||||
'active' => 'boolean',
|
||||
]);
|
||||
}
|
||||
}
|
||||
115
app/Http/Controllers/PieceJointeController.php
Normal file
115
app/Http/Controllers/PieceJointeController.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Commande;
|
||||
use App\Models\PieceJointe;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class PieceJointeController extends Controller
|
||||
{
|
||||
/**
|
||||
* Upload une pièce jointe sur une commande.
|
||||
*/
|
||||
public function store(Request $request, Commande $commande): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'type' => 'required|in:devis,bon_commande,bon_livraison,facture,autre',
|
||||
'fichier' => [
|
||||
'required',
|
||||
'file',
|
||||
'max:20480', // 20 Mo max
|
||||
'mimes:pdf,jpg,jpeg,png,gif,webp,doc,docx,xls,xlsx,odt,ods,csv,zip',
|
||||
],
|
||||
'description' => 'nullable|string|max:500',
|
||||
], [
|
||||
'fichier.mimes' => 'Types autorisés : PDF, images, Word, Excel, OpenDocument, CSV, ZIP.',
|
||||
'fichier.max' => 'La pièce jointe ne peut pas dépasser 20 Mo.',
|
||||
]);
|
||||
|
||||
$fichier = $request->file('fichier');
|
||||
$chemin = $fichier->store(
|
||||
'commandes/' . $commande->id,
|
||||
'private'
|
||||
);
|
||||
|
||||
$commande->piecesJointes()->create([
|
||||
'user_id' => $request->user()->id,
|
||||
'type' => $request->type,
|
||||
'nom_original' => $fichier->getClientOriginalName(),
|
||||
'chemin' => $chemin,
|
||||
'mime_type' => $fichier->getMimeType(),
|
||||
'taille' => $fichier->getSize(),
|
||||
'description' => $request->description,
|
||||
]);
|
||||
|
||||
return back()->with('success', 'Pièce jointe ajoutée avec succès.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload une pièce jointe sur un contrat.
|
||||
*/
|
||||
public function storeContrat(Request $request, \App\Models\Contrat $contrat): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'type' => 'required|in:contrat,avenant,autre',
|
||||
'fichier' => [
|
||||
'required',
|
||||
'file',
|
||||
'max:20480', // 20 Mo max
|
||||
'mimes:pdf,jpg,jpeg,png,gif,webp,doc,docx,xls,xlsx,odt,ods,csv,zip',
|
||||
],
|
||||
'description' => 'nullable|string|max:500',
|
||||
], [
|
||||
'fichier.mimes' => 'Types autorisés : PDF, images, Word, Excel, OpenDocument, CSV, ZIP.',
|
||||
'fichier.max' => 'La pièce jointe ne peut pas dépasser 20 Mo.',
|
||||
]);
|
||||
|
||||
$fichier = $request->file('fichier');
|
||||
$chemin = $fichier->store(
|
||||
'contrats/' . $contrat->id,
|
||||
'private'
|
||||
);
|
||||
|
||||
$contrat->piecesJointes()->create([
|
||||
'user_id' => $request->user()->id,
|
||||
'type' => $request->type,
|
||||
'nom_original' => $fichier->getClientOriginalName(),
|
||||
'chemin' => $chemin,
|
||||
'mime_type' => $fichier->getMimeType(),
|
||||
'taille' => $fichier->getSize(),
|
||||
'description' => $request->description,
|
||||
]);
|
||||
|
||||
return back()->with('success', 'Pièce jointe ajoutée avec succès au contrat.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Télécharger une pièce jointe (accès sécurisé, sans URL publique).
|
||||
*/
|
||||
public function download(PieceJointe $pieceJointe): StreamedResponse
|
||||
{
|
||||
// Vérifier que l'utilisateur est authentifié (middleware auth déjà sur le groupe)
|
||||
abort_unless(Storage::disk('private')->exists($pieceJointe->chemin), 404);
|
||||
|
||||
return Storage::disk('private')->download(
|
||||
$pieceJointe->chemin,
|
||||
$pieceJointe->nom_original
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprimer une pièce jointe.
|
||||
*/
|
||||
public function destroy(PieceJointe $pieceJointe): RedirectResponse
|
||||
{
|
||||
$this->authorize('delete', $pieceJointe);
|
||||
|
||||
$pieceJointe->supprimer();
|
||||
|
||||
return back()->with('success', 'Pièce jointe supprimée.');
|
||||
}
|
||||
}
|
||||
63
app/Http/Controllers/ProfileController.php
Normal file
63
app/Http/Controllers/ProfileController.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\ProfileUpdateRequest;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Redirect;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class ProfileController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the user's profile form.
|
||||
*/
|
||||
public function edit(Request $request): Response
|
||||
{
|
||||
return Inertia::render('Profile/Edit', [
|
||||
'mustVerifyEmail' => $request->user() instanceof MustVerifyEmail,
|
||||
'status' => session('status'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's profile information.
|
||||
*/
|
||||
public function update(ProfileUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$request->user()->fill($request->validated());
|
||||
|
||||
if ($request->user()->isDirty('email')) {
|
||||
$request->user()->email_verified_at = null;
|
||||
}
|
||||
|
||||
$request->user()->save();
|
||||
|
||||
return Redirect::route('profile.edit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the user's account.
|
||||
*/
|
||||
public function destroy(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'password' => ['required', 'current_password'],
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
Auth::logout();
|
||||
|
||||
$user->delete();
|
||||
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return Redirect::to('/');
|
||||
}
|
||||
}
|
||||
58
app/Http/Controllers/ServiceController.php
Normal file
58
app/Http/Controllers/ServiceController.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Service;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class ServiceController extends Controller
|
||||
{
|
||||
public function index(): Response
|
||||
{
|
||||
return Inertia::render('Services/Index', [
|
||||
'services' => Service::withCount('users', 'commandes')->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'nom' => 'required|string|max:100|unique:services,nom',
|
||||
'description' => 'nullable|string',
|
||||
'couleur' => 'nullable|string|max:7',
|
||||
'icone' => 'nullable|string|max:50',
|
||||
]);
|
||||
|
||||
Service::create($validated);
|
||||
|
||||
return back()->with('success', 'Service créé.');
|
||||
}
|
||||
|
||||
public function update(Request $request, Service $service): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'nom' => 'required|string|max:100|unique:services,nom,' . $service->id,
|
||||
'description' => 'nullable|string',
|
||||
'couleur' => 'nullable|string|max:7',
|
||||
'icone' => 'nullable|string|max:50',
|
||||
]);
|
||||
|
||||
$service->update($validated);
|
||||
|
||||
return back()->with('success', 'Service mis à jour.');
|
||||
}
|
||||
|
||||
public function destroy(Service $service): RedirectResponse
|
||||
{
|
||||
if ($service->users()->exists() || $service->commandes()->exists()) {
|
||||
return back()->with('error', 'Impossible de supprimer un service ayant des utilisateurs ou des commandes.');
|
||||
}
|
||||
|
||||
$service->delete();
|
||||
|
||||
return back()->with('success', 'Service supprimé.');
|
||||
}
|
||||
}
|
||||
73
app/Http/Controllers/UserController.php
Normal file
73
app/Http/Controllers/UserController.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Service;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
public function index(): Response
|
||||
{
|
||||
$this->authorize('viewAny', User::class);
|
||||
|
||||
return Inertia::render('Users/Index', [
|
||||
'users' => User::with('service', 'roles')->orderBy('name')->paginate(20),
|
||||
'services' => Service::all(),
|
||||
'roles' => Role::all(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(User $user): Response
|
||||
{
|
||||
$this->authorize('update', $user);
|
||||
|
||||
return Inertia::render('Users/Edit', [
|
||||
'user' => $user->load('service', 'roles'),
|
||||
'services' => Service::all(),
|
||||
'roles' => Role::all(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, User $user): RedirectResponse
|
||||
{
|
||||
$this->authorize('update', $user);
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email,' . $user->id,
|
||||
'service_id' => 'nullable|exists:services,id',
|
||||
'telephone' => 'nullable|string|max:20',
|
||||
'role' => 'required|string|exists:roles,name',
|
||||
'password' => 'nullable|string|min:8|confirmed',
|
||||
]);
|
||||
|
||||
$user->update([
|
||||
'name' => $validated['name'],
|
||||
'email' => $validated['email'],
|
||||
'service_id' => $validated['service_id'],
|
||||
'telephone' => $validated['telephone'],
|
||||
...(isset($validated['password']) ? ['password' => Hash::make($validated['password'])] : []),
|
||||
]);
|
||||
|
||||
$user->syncRoles([$validated['role']]);
|
||||
|
||||
return redirect()->route('users.index')
|
||||
->with('success', 'Utilisateur mis à jour.');
|
||||
}
|
||||
|
||||
public function toggleActive(User $user): RedirectResponse
|
||||
{
|
||||
$this->authorize('update', $user);
|
||||
|
||||
$user->update(['active' => !$user->active]);
|
||||
|
||||
return back()->with('success', 'Statut de l\'utilisateur mis à jour.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user