cdbf6d458c
- ImportController : create() (formulaire) + store() (traitement) - Détection automatique du séparateur ; ou , - Suppression du BOM UTF-8 - Correspondance colonnes ↔ champs par libellé - Parsing des types : date (avec calendrier), booléen, nombre, lieu (recherche par nom_long), texte - Vue sources/import.blade.php : formulaire + liste des colonnes attendues - Routes sources.import.create / sources.import.store - Bouton "↑ Importer CSV" dans la fiche source (soumis aux droits create releve) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
132 lines
4.1 KiB
PHP
132 lines
4.1 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Enums\FieldType;
|
|
use App\Models\Lieu;
|
|
use App\Models\Releve;
|
|
use App\Models\Source;
|
|
use App\Support\DbCompat;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\View\View;
|
|
|
|
class ImportController extends Controller
|
|
{
|
|
public function create(Source $source): View
|
|
{
|
|
$this->authorize('create', [Releve::class, $source]);
|
|
|
|
$source->load('sourceType.fields');
|
|
|
|
return view('sources.import', compact('source'));
|
|
}
|
|
|
|
public function store(Request $request, Source $source): RedirectResponse
|
|
{
|
|
$this->authorize('create', [Releve::class, $source]);
|
|
|
|
$request->validate([
|
|
'fichier' => ['required', 'file', 'mimes:csv,txt', 'max:10240'],
|
|
]);
|
|
|
|
$source->load('sourceType.fields');
|
|
$fieldsByLabel = $source->sourceType->fields->keyBy('label');
|
|
|
|
$path = $request->file('fichier')->getRealPath();
|
|
$handle = fopen($path, 'r');
|
|
|
|
// Suppression du BOM UTF-8 éventuel
|
|
$bom = fread($handle, 3);
|
|
if ($bom !== "\xEF\xBB\xBF") {
|
|
rewind($handle);
|
|
}
|
|
|
|
// Détection automatique du séparateur (; ou ,)
|
|
$firstLine = fgets($handle);
|
|
rewind($handle);
|
|
if ($bom === "\xEF\xBB\xBF") {
|
|
fread($handle, 3);
|
|
}
|
|
$sep = substr_count($firstLine, ';') >= substr_count($firstLine, ',') ? ';' : ',';
|
|
|
|
$header = fgetcsv($handle, 0, $sep);
|
|
if (! $header) {
|
|
fclose($handle);
|
|
return back()->with('error', 'Fichier CSV vide ou invalide.');
|
|
}
|
|
$header = array_map('trim', $header);
|
|
|
|
$imported = 0;
|
|
$userId = auth()->id();
|
|
|
|
while (($line = fgetcsv($handle, 0, $sep)) !== false) {
|
|
if (count(array_filter($line, fn ($v) => $v !== '')) === 0) {
|
|
continue;
|
|
}
|
|
|
|
$data = [];
|
|
foreach ($header as $i => $label) {
|
|
$field = $fieldsByLabel->get($label);
|
|
if (! $field) {
|
|
continue;
|
|
}
|
|
$data[$field->name] = $this->parseValue(trim($line[$i] ?? ''), $field->type);
|
|
}
|
|
|
|
$source->releves()->create([
|
|
'data' => $data,
|
|
'created_by' => $userId,
|
|
'updated_by' => $userId,
|
|
]);
|
|
$imported++;
|
|
}
|
|
|
|
fclose($handle);
|
|
|
|
if ($imported === 0) {
|
|
return back()->with('error', 'Aucun relevé importé — vérifiez que les en-têtes correspondent aux libellés des champs.');
|
|
}
|
|
|
|
return redirect()->route('sources.releves.index', $source)
|
|
->with('success', "{$imported} relevé(s) importé(s) avec succès.");
|
|
}
|
|
|
|
private function parseValue(string $raw, FieldType $type): mixed
|
|
{
|
|
return match ($type) {
|
|
FieldType::Boolean => in_array(mb_strtolower($raw), ['oui', 'yes', '1', 'true'], true),
|
|
FieldType::Number => $raw !== '' ? (float) str_replace(',', '.', $raw) : null,
|
|
FieldType::Date => $this->parseDate($raw),
|
|
FieldType::Place => $this->parsePlace($raw),
|
|
default => $raw !== '' ? $raw : null,
|
|
};
|
|
}
|
|
|
|
private function parseDate(string $raw): array
|
|
{
|
|
if ($raw === '') {
|
|
return ['valeur' => null, 'calendrier' => 'gregorien'];
|
|
}
|
|
// Format export : "YYYY-MM-DD" ou "YYYY-MM-DD (calendrier)"
|
|
if (preg_match('/^(.+?)\s*\((\w+)\)\s*$/', $raw, $m)) {
|
|
return ['valeur' => trim($m[1]), 'calendrier' => trim($m[2])];
|
|
}
|
|
return ['valeur' => $raw, 'calendrier' => 'gregorien'];
|
|
}
|
|
|
|
private function parsePlace(string $raw): ?array
|
|
{
|
|
if ($raw === '') {
|
|
return null;
|
|
}
|
|
$like = DbCompat::like();
|
|
$lieu = Lieu::where('nom_long', $raw)->first(['id', 'nom_long'])
|
|
?? Lieu::where('nom_long', $like, $raw . '%')->first(['id', 'nom_long']);
|
|
|
|
return $lieu
|
|
? ['id' => $lieu->id, 'nom_long' => $lieu->nom_long]
|
|
: ['id' => null, 'nom_long' => $raw];
|
|
}
|
|
}
|