get('q', '')); $lieux = Lieu::with('lieuType') ->where(function ($query) use ($q) { $query->where('nom_long', DbCompat::like(), "%{$q}%") ->orWhere('nom', DbCompat::like(), "%{$q}%") ->orWhere('code', DbCompat::like(), "%{$q}%"); }) ->orderBy('nom_long') ->limit(25) ->get(); return response()->json($lieux->map(fn ($l) => [ 'id' => $l->id, 'nom_long' => $l->nom_long ?? $l->nom, 'code' => $l->code, 'type' => $l->lieuType?->nom, ])); } public function index(Request $request): View { $this->authorize('viewAny', Lieu::class); $query = Lieu::with(['parent', 'lieuType'])->orderBy('nom_long'); if ($request->filled('lieu_type_id')) { $query->where('lieu_type_id', $request->integer('lieu_type_id')); } if ($request->filled('q')) { $q = trim($request->get('q')); $query->where(function ($wq) use ($q) { $wq->where('nom_long', DbCompat::like(), "%{$q}%") ->orWhere('nom', DbCompat::like(), "%{$q}%") ->orWhere('code', DbCompat::like(), "%{$q}%"); }); } if ($request->filled('lieu_id')) { $ids = $this->getDescendantAndSelfIds($request->integer('lieu_id')); $query->whereIn('id', $ids); } $lieuTypes = LieuType::orderBy('ordre')->get(['id', 'nom']); $lieuSelectionne = $request->filled('lieu_id') ? Lieu::find($request->integer('lieu_id'), ['id', 'nom', 'nom_long']) : null; $hasFilters = $request->anyFilled(['lieu_type_id', 'q', 'lieu_id']); $lieux = $hasFilters ? $query->paginate(50)->withQueryString() : null; return view('lieux.index', compact('lieux', 'lieuTypes', 'lieuSelectionne', 'hasFilters')); } public function create(): View { $this->authorize('create', Lieu::class); $lieuTypes = LieuType::orderBy('ordre')->get(['id', 'nom']); return view('lieux.create', compact('lieuTypes')); } public function store(StoreLieuRequest $request): RedirectResponse { $lieu = Lieu::create($request->validated()); return redirect()->route('lieux.show', $lieu) ->with('success', 'Lieu créé avec succès.'); } public function show(Lieu $lieu): View { $this->authorize('view', $lieu); $lieu->load('parent', 'enfants', 'lieuType'); return view('lieux.show', compact('lieu')); } public function edit(Lieu $lieu): View { $this->authorize('update', $lieu); $lieu->load('parent', 'lieuType'); $lieuTypes = LieuType::orderBy('ordre')->get(['id', 'nom']); return view('lieux.edit', compact('lieu', 'lieuTypes')); } public function update(UpdateLieuRequest $request, Lieu $lieu): RedirectResponse { $lieu->update($request->validated()); $this->recalculerEnfants($lieu); return redirect()->route('lieux.show', $lieu) ->with('success', 'Lieu mis à jour.'); } public function destroy(Lieu $lieu): RedirectResponse { $this->authorize('delete', $lieu); if ($lieu->enfants()->exists()) { return back()->with('error', 'Impossible de supprimer un lieu qui a des enfants.'); } $lieu->delete(); return redirect()->route('lieux.index') ->with('success', 'Lieu supprimé.'); } public function exportCsv(): Response { $this->authorize('viewAny', Lieu::class); $lieux = Lieu::with(['lieuType', 'parent'])->orderBy('nom_long')->get(); $handle = fopen('php://temp', 'r+'); fwrite($handle, "\xEF\xBB\xBF"); fputcsv($handle, ['nom', 'code', 'type', 'lieu_parent', 'latitude', 'longitude', 'note'], ';'); foreach ($lieux as $lieu) { fputcsv($handle, [ $lieu->nom, $lieu->code ?? '', $lieu->lieuType?->nom ?? '', $lieu->parent?->nom_long ?? '', $lieu->latitude ?? '', $lieu->longitude ?? '', $lieu->note ?? '', ], ';'); } rewind($handle); $csv = stream_get_contents($handle); fclose($handle); return response($csv, 200, [ 'Content-Type' => 'text/csv; charset=UTF-8', 'Content-Disposition' => 'attachment; filename="lieux.csv"', ]); } public function importCreate(): View { $this->authorize('create', Lieu::class); return view('lieux.import'); } public function importStore(Request $request): RedirectResponse { $this->authorize('create', Lieu::class); $request->validate([ 'fichier' => ['required', 'file', 'mimes:csv,txt', 'max:10240'], ]); $handle = fopen($request->file('fichier')->getRealPath(), 'r'); $bom = fread($handle, 3); if ($bom !== "\xEF\xBB\xBF") { rewind($handle); } $firstLine = fgets($handle); rewind($handle); if ($bom === "\xEF\xBB\xBF") { fread($handle, 3); } $sep = substr_count($firstLine, ';') >= substr_count($firstLine, ',') ? ';' : ','; $header = array_map('trim', fgetcsv($handle, 0, $sep) ?: []); $colIdx = array_flip($header); $lieuTypesCache = LieuType::all()->keyBy('nom'); $imported = 0; while (($line = fgetcsv($handle, 0, $sep)) !== false) { $nom = trim($line[$colIdx['nom'] ?? -1] ?? ''); if ($nom === '') { continue; } $parentNomLong = trim($line[$colIdx['lieu_parent'] ?? -1] ?? ''); $typeName = trim($line[$colIdx['type'] ?? -1] ?? ''); $parentId = $parentNomLong ? Lieu::where('nom_long', $parentNomLong)->value('id') : null; $lieuTypeId = $typeName ? $lieuTypesCache->get($typeName)?->id : null; Lieu::create([ 'nom' => $nom, 'code' => trim($line[$colIdx['code'] ?? -1] ?? '') ?: null, 'lieu_type_id' => $lieuTypeId, 'lieu_parent_id'=> $parentId, 'latitude' => ($v = trim($line[$colIdx['latitude'] ?? -1] ?? '')) !== '' ? (float) str_replace(',', '.', $v) : null, 'longitude' => ($v = trim($line[$colIdx['longitude'] ?? -1] ?? '')) !== '' ? (float) str_replace(',', '.', $v) : null, 'note' => trim($line[$colIdx['note'] ?? -1] ?? '') ?: null, ]); $imported++; } fclose($handle); if ($imported === 0) { return back()->with('error', 'Aucun lieu importé — vérifiez que la colonne « nom » est présente.'); } return redirect()->route('lieux.index') ->with('success', "{$imported} lieu(x) importé(s) avec succès."); } private function getDescendantAndSelfIds(int $lieuId): array { $rows = DB::select(" WITH RECURSIVE descendants AS ( SELECT id FROM lieux WHERE id = ? UNION ALL SELECT l.id FROM lieux l INNER JOIN descendants d ON l.lieu_parent_id = d.id ) SELECT id FROM descendants ", [$lieuId]); return collect($rows)->pluck('id')->toArray(); } private function getDescendantIds(Lieu $lieu): array { $ids = []; foreach ($lieu->enfants as $enfant) { $ids[] = $enfant->id; $ids = array_merge($ids, $this->getDescendantIds($enfant)); } return $ids; } private function recalculerEnfants(Lieu $lieu): void { $lieu->load('enfants'); foreach ($lieu->enfants as $enfant) { $enfant->update([]); $this->recalculerEnfants($enfant); } } }