feature test su institution
This commit is contained in:
160
backend/app/Etl/Importers/InstitutionImporter.php
Normal file
160
backend/app/Etl/Importers/InstitutionImporter.php
Normal file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace App\Etl\Importers;
|
||||
|
||||
use App\Enums\InstitutionLinkType;
|
||||
use App\Etl\Contracts\Importer;
|
||||
use App\Etl\ImportSummary;
|
||||
use App\Models\Institution;
|
||||
use App\Models\InstitutionLink;
|
||||
use App\Models\Lists\InstitutionCategory;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Importa le istituzioni da v1 (`institution`) nello schema v2.
|
||||
*
|
||||
* - Upsert su `legacy_id` → idempotente (re-run aggiorna, non duplica).
|
||||
* - `category` (v1) → `category_id` (v2): mappa 1:1 perché il lookup è seedato con
|
||||
* gli stessi id legacy. Categoria assente nel lookup (es. 1 "uncategorized") → skip + warning.
|
||||
* - `uuid` NON copiato dal legacy: lo rigenera il model (orderedUuid) alla creazione.
|
||||
* - `url` (campo singolo v1) → riga `institution_links` con type=official, solo se valorizzato.
|
||||
* - Pulizia dati: trim su name/abbreviation/address/city/logo.
|
||||
*
|
||||
* NB l'auditing è già off in console (audit.console=false), quindi l'import non genera audit.
|
||||
* NB la migrazione dei FILE logo (da v1 a storage/app/public) è fuori scope di questa fetta:
|
||||
* qui si copia solo il valore stringa della colonna.
|
||||
*/
|
||||
class InstitutionImporter implements Importer
|
||||
{
|
||||
private const SOURCE = 'legacy';
|
||||
|
||||
public function key(): string
|
||||
{
|
||||
return 'institutions';
|
||||
}
|
||||
|
||||
public function label(): string
|
||||
{
|
||||
return 'Institutions';
|
||||
}
|
||||
|
||||
public function import(bool $dryRun): ImportSummary
|
||||
{
|
||||
$summary = new ImportSummary;
|
||||
|
||||
/** @var list<int> $validCategoryIds */
|
||||
$validCategoryIds = InstitutionCategory::query()->pluck('id')->map(intval(...))->all();
|
||||
|
||||
if ($validCategoryIds === []) {
|
||||
$summary->warn('Lookup institution_categories vuoto: esegui prima `db:seed`. Import saltato.');
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
$rows = DB::connection(self::SOURCE)
|
||||
->table('institution')
|
||||
->orderBy('id')
|
||||
->get();
|
||||
|
||||
DB::transaction(function () use ($rows, $dryRun, $summary, $validCategoryIds): void {
|
||||
foreach ($rows as $row) {
|
||||
$this->importRow($row, $dryRun, $validCategoryIds, $summary);
|
||||
}
|
||||
});
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Importa (upsert) una singola istituzione legacy. Categoria assente nel
|
||||
* lookup v2 → skip con warning.
|
||||
*
|
||||
* @param list<int> $validCategoryIds
|
||||
*/
|
||||
private function importRow(object $row, bool $dryRun, array $validCategoryIds, ImportSummary $summary): void
|
||||
{
|
||||
$categoryId = (int) $row->category;
|
||||
|
||||
if (! in_array($categoryId, $validCategoryIds, true)) {
|
||||
$summary->warn(sprintf(
|
||||
"Institution legacy #%d ('%s'): category %d assente nel lookup v2 → saltata.",
|
||||
$row->id, trim((string) $row->name), $categoryId,
|
||||
));
|
||||
$summary->skipped++;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($dryRun) {
|
||||
Institution::query()->where('legacy_id', (int) $row->id)->exists()
|
||||
? $summary->updated++
|
||||
: $summary->created++;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$institution = Institution::updateOrCreate(
|
||||
['legacy_id' => (int) $row->id],
|
||||
$this->mapAttributes($row, $categoryId),
|
||||
);
|
||||
|
||||
$institution->wasRecentlyCreated ? $summary->created++ : $summary->updated++;
|
||||
|
||||
$this->syncOfficialLink($institution, (string) ($row->url ?? ''));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mappa una riga legacy sugli attributi del model v2 (con pulizia dei dati).
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function mapAttributes(object $row, int $categoryId): array
|
||||
{
|
||||
return [
|
||||
'category_id' => $categoryId,
|
||||
'name' => trim((string) $row->name),
|
||||
'abbreviation' => trim((string) $row->abbreviation),
|
||||
'address' => trim((string) $row->address),
|
||||
'city' => trim((string) $row->city),
|
||||
'lat' => $row->lat,
|
||||
'lon' => $row->lon,
|
||||
'logo' => trim((string) $row->logo),
|
||||
'color' => $this->cleanColor($row->color),
|
||||
'is_storage_place' => (bool) $row->is_storage_place,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Colore valido o fallback al default v2.
|
||||
*/
|
||||
private function cleanColor(mixed $color): string
|
||||
{
|
||||
$color = trim((string) ($color ?? ''));
|
||||
|
||||
return $color !== '' ? $color : '#c5cae9';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sincronizza il link "official" dall'unico `url` legacy. Idempotente
|
||||
* (upsert su institution_id + type). URL vuoto/blank → nessun link.
|
||||
*/
|
||||
private function syncOfficialLink(Institution $institution, string $url): void
|
||||
{
|
||||
$url = trim($url);
|
||||
|
||||
if ($url === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
InstitutionLink::updateOrCreate(
|
||||
[
|
||||
'institution_id' => $institution->id,
|
||||
'type' => InstitutionLinkType::Official,
|
||||
],
|
||||
[
|
||||
'url' => $url,
|
||||
'sort_order' => 0,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user