236d37976c
DbCompat (app/Support/DbCompat.php) : - like() → ilike (pgsql) ou like (mysql) - jsonRegexRaw() → data::text ~* ? (pgsql) ou CAST(data AS CHAR) REGEXP ? (mysql) - ftsRaw() → to_tsvector/plainto_tsquery (pgsql) ou null/fallback LIKE (mysql) - generatedJsonCol() → syntaxe colonne générée JSON selon le SGBD - generatedJsonNestedCol() → idem pour champs imbriqués Migrations : - create_releves_table : JSON/JSONB selon SGBD, colonnes générées adaptées, index GIN uniquement pour PostgreSQL Controllers : - LieuController (search + index) : ilike → DbCompat::like() - Admin\UserController (index) : ilike → DbCompat::like() - RechercheController : FTS + regex → DbCompat, fallback LIKE MySQL - ExportController : regex → DbCompat::jsonRegexRaw() UpdateService : - backupDatabase() : pg_dump (pgsql) ou mysqldump (mysql) - restoreBackup() : psql (pgsql) ou mysql (mysql) Docker : - docker-compose.yml : suppression Redis (plus requis) - docker-compose.mysql.yml : nouveau fichier pour dev MySQL - docker-compose.prod.yml : suppression Redis, DB_IMAGE configurable, CACHE_STORE/SESSION_DRIVER/QUEUE_CONNECTION → database par défaut .env.example : - DB_PORT commenté avec les deux valeurs (5432/3306) - CACHE_STORE et QUEUE_CONNECTION commentés (database par défaut) - Redis marqué optionnel Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
120 lines
4.5 KiB
PHP
120 lines
4.5 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Enums\SourceStatus;
|
|
use App\Models\Lieu;
|
|
use App\Models\Releve;
|
|
use App\Models\SourceType;
|
|
use App\Support\DbCompat;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\View\View;
|
|
|
|
class RechercheController extends Controller
|
|
{
|
|
public function index(Request $request): View
|
|
{
|
|
$sourceTypes = SourceType::orderBy('nom')->get(['id', 'nom']);
|
|
$resultats = null;
|
|
$total = null;
|
|
|
|
// Charger le lieu sélectionné pour pré-remplir le picker
|
|
$lieuSelectionne = $request->filled('lieu_id')
|
|
? Lieu::find($request->integer('lieu_id'), ['id', 'nom', 'nom_long'])
|
|
: null;
|
|
|
|
if ($request->anyFilled(['q', 'source_type_id', 'lieu_id', 'annee_debut', 'annee_fin'])) {
|
|
[$resultats, $total] = $this->search($request);
|
|
}
|
|
|
|
return view('recherche.index', compact('sourceTypes', 'resultats', 'total', 'lieuSelectionne'));
|
|
}
|
|
|
|
private function search(Request $request): array
|
|
{
|
|
$user = auth()->user();
|
|
|
|
$query = Releve::with(['source.sourceType', 'source.depot', 'createur'])
|
|
->whereHas('source', function ($q) use ($user, $request) {
|
|
if (! $user->isSectionManager()) {
|
|
$assignedIds = $user->sourcesAssignees()->pluck('sources.id');
|
|
$q->where(function ($sq) use ($assignedIds) {
|
|
$sq->where('status', SourceStatus::Termine)
|
|
->orWhereIn('id', $assignedIds);
|
|
});
|
|
}
|
|
if ($request->filled('source_type_id')) {
|
|
$q->where('source_type_id', $request->integer('source_type_id'));
|
|
}
|
|
});
|
|
|
|
// ── Recherche textuelle ──────────────────────────────────────────────
|
|
if ($request->filled('q')) {
|
|
$q = trim($request->get('q'));
|
|
$like = DbCompat::like();
|
|
$fts = DbCompat::ftsRaw();
|
|
|
|
$query->where(function ($wq) use ($q, $like, $fts) {
|
|
$wq->where('nom', $like, "%{$q}%")
|
|
->orWhere('prenom', $like, "%{$q}%")
|
|
->orWhere('date_evenement', $like, "%{$q}%");
|
|
|
|
if ($fts) {
|
|
$wq->orWhereRaw($fts, [$q]);
|
|
}
|
|
});
|
|
}
|
|
|
|
// ── Filtre par lieu (+ descendants via CTE récursive) ────────────────
|
|
if ($request->filled('lieu_id')) {
|
|
$lieuNoms = $this->getLieuNoms($request->integer('lieu_id'));
|
|
if ($lieuNoms->isNotEmpty()) {
|
|
$pattern = $lieuNoms
|
|
->map(fn ($n) => preg_quote($n, '/'))
|
|
->join('|');
|
|
$query->whereRaw(DbCompat::jsonRegexRaw('data'), [$pattern]);
|
|
}
|
|
}
|
|
|
|
// ── Filtre par plage d'années ────────────────────────────────────────
|
|
if ($request->filled('annee_debut')) {
|
|
$query->whereRaw("date_evenement >= ?", [$request->integer('annee_debut') . '-01-01']);
|
|
}
|
|
if ($request->filled('annee_fin')) {
|
|
$query->whereRaw("date_evenement <= ?", [$request->integer('annee_fin') . '-12-31']);
|
|
}
|
|
|
|
// ── Tri + pagination ────────────────────────────────────────────────
|
|
$total = $query->count();
|
|
$resultats = $query
|
|
->orderByRaw('nom ASC NULLS LAST')
|
|
->orderByRaw('date_evenement ASC NULLS LAST')
|
|
->paginate(25)
|
|
->withQueryString();
|
|
|
|
return [$resultats, $total];
|
|
}
|
|
|
|
/**
|
|
* Retourne les noms du lieu et de tous ses descendants via CTE récursive PostgreSQL.
|
|
*/
|
|
private function getLieuNoms(int $lieuId): \Illuminate\Support\Collection
|
|
{
|
|
$rows = DB::select("
|
|
WITH RECURSIVE descendants AS (
|
|
SELECT id, nom
|
|
FROM lieux
|
|
WHERE id = ?
|
|
UNION ALL
|
|
SELECT l.id, l.nom
|
|
FROM lieux l
|
|
INNER JOIN descendants d ON l.lieu_parent_id = d.id
|
|
)
|
|
SELECT DISTINCT nom FROM descendants WHERE nom IS NOT NULL
|
|
", [$lieuId]);
|
|
|
|
return collect($rows)->pluck('nom')->filter();
|
|
}
|
|
}
|