Files
mesreleves-php/app/Http/Controllers/ExportController.php
T
yann64 fdd81977c2 Export CSV des relevés d'une source
- ExportController::sourceCsv() : génère un CSV avec BOM UTF-8 (compatible Excel)
  séparateur point-virgule, en-têtes = labels des champs, valeurs formatées
  (dates avec calendrier si non grégorien, lieux = nom_long, booléens Oui/Non)
- Route GET export/source/{source}/csv → export.source.csv
- Boutons ↓ CSV et ↓ GEDCOM dans la section relevés de la fiche source

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 19:39:21 +02:00

174 lines
6.1 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Enums\FieldType;
use App\Enums\SourceStatus;
use App\Models\Releve;
use App\Models\Source;
use App\Services\GedcomExportService;
use App\Support\DbCompat;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;
class ExportController extends Controller
{
public function __construct(
private readonly GedcomExportService $gedcom,
) {}
/** Export CSV de tous les relevés d'une source */
public function sourceCsv(Source $source): Response
{
$this->authorize('view', $source);
$source->load(['sourceType.fields', 'releves']);
$fields = $source->sourceType->fields->sortBy('order');
$handle = fopen('php://temp', 'r+');
// BOM UTF-8 pour la compatibilité Excel
fwrite($handle, "\xEF\xBB\xBF");
// En-tête
fputcsv($handle, $fields->pluck('label')->toArray(), ';');
// Lignes
foreach ($source->releves as $releve) {
$row = [];
foreach ($fields as $field) {
$val = $releve->data[$field->name] ?? null;
$row[] = $this->formatCsvValue($val, $field->type);
}
fputcsv($handle, $row, ';');
}
rewind($handle);
$csv = stream_get_contents($handle);
fclose($handle);
$filename = $this->sanitizeFilename($source->nom) . '.csv';
return response($csv, 200, [
'Content-Type' => 'text/csv; charset=UTF-8',
'Content-Disposition' => "attachment; filename=\"{$filename}\"",
]);
}
/** Export de tous les relevés d'une source */
public function source(Source $source): Response
{
$this->authorize('view', $source);
$gedcomContent = $this->gedcom->exportSource($source);
$filename = $this->sanitizeFilename($source->nom) . '.ged';
return response($gedcomContent, 200, [
'Content-Type' => 'text/plain; charset=UTF-8',
'Content-Disposition' => "attachment; filename=\"{$filename}\"",
]);
}
/** Export depuis les résultats de recherche (avec les mêmes filtres) */
public function recherche(Request $request): Response
{
$user = auth()->user();
$query = Releve::with(['source.sourceType', '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'));
}
}); // $request déjà dans le use()
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]);
}
});
}
if ($request->filled('lieu_id')) {
$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
", [$request->integer('lieu_id')]);
$noms = collect($rows)->pluck('nom')->filter();
if ($noms->isNotEmpty()) {
$pattern = $noms->map(fn ($n) => preg_quote($n, '/'))->join('|');
$query->whereRaw(DbCompat::jsonRegexRaw('data'), [$pattern]);
}
}
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']);
}
$releves = $query->orderByRaw(DbCompat::nullsLast('nom'))->get();
if ($releves->isEmpty()) {
return back()->with('error', 'Aucun relevé à exporter.');
}
$titre = $request->filled('q') ? 'Recherche_' . $request->get('q') : 'Export';
$gedcomContent = $this->gedcom->exportReleves($releves, $titre);
$filename = $this->sanitizeFilename($titre) . '.ged';
return response($gedcomContent, 200, [
'Content-Type' => 'text/plain; charset=UTF-8',
'Content-Disposition' => "attachment; filename=\"{$filename}\"",
]);
}
private function formatCsvValue(mixed $val, FieldType $type): string
{
if ($val === null || $val === '') {
return '';
}
return match ($type) {
FieldType::Boolean => $val ? 'Oui' : 'Non',
FieldType::Date => is_array($val)
? trim(($val['valeur'] ?? '') . (
! empty($val['calendrier']) && $val['calendrier'] !== 'gregorien'
? ' (' . $val['calendrier'] . ')'
: ''
))
: (string) $val,
FieldType::Place => is_array($val) ? ($val['nom_long'] ?? '') : (string) $val,
default => (string) $val,
};
}
private function sanitizeFilename(string $name): string
{
$name = iconv('UTF-8', 'ASCII//TRANSLIT', $name) ?: $name;
return preg_replace('/[^a-zA-Z0-9_-]/', '_', $name);
}
}