fix(importer): strictly use raw CFs, remove placeholder, support multi-gestione temporal tenant relations (task-856d2ae3f2)
This commit is contained in:
parent
f60898aae6
commit
b54ea07f54
|
|
@ -14,11 +14,8 @@ class GesconReconstructMirror0021Command extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'gescon:reconstruct-mirror-0021 {--stabile=0021 : Codice dello stabile pilot}';
|
protected $signature = 'gescon:reconstruct-mirror-0021 {--stabile=0021 : Codice dello stabile pilot}';
|
||||||
|
|
||||||
protected $description = 'Ricostruisce Anagrafica Unica, Unità Immobiliari e Relazioni Temporali dal condomin_mirror lossless 0021';
|
protected $description = 'Ricostruisce Anagrafica Unica, Unità Immobiliari e Relazioni Temporali dal condomin_mirror 0021 con gestione rigorosa CF e subentri';
|
||||||
|
|
||||||
/**
|
|
||||||
* Mappa in memoria delle persone già risolte per identificatore stabile (id_cond)
|
|
||||||
*/
|
|
||||||
private array $resolvedPersonasByStableId = [];
|
private array $resolvedPersonasByStableId = [];
|
||||||
|
|
||||||
public function handle(): int
|
public function handle(): int
|
||||||
|
|
@ -26,23 +23,35 @@ public function handle(): int
|
||||||
$stabileCode = (string) $this->option('stabile');
|
$stabileCode = (string) $this->option('stabile');
|
||||||
$this->info("=== Ricostruzione Anagrafica e Unità dal condomin_mirror Stabile {$stabileCode} ===");
|
$this->info("=== Ricostruzione Anagrafica e Unità dal condomin_mirror Stabile {$stabileCode} ===");
|
||||||
|
|
||||||
// 1. Verifica Stabile
|
// 1. Estrazione CF Stabile raw da Stabili.mdb (Nessun fallback 80000000021)
|
||||||
|
$rawStabileCf = $this->getRawStabileCodiceFiscale($stabileCode);
|
||||||
|
if (! $rawStabileCf) {
|
||||||
|
$this->error("BLOCCO_DATI: Impossibile recuperare il codice fiscale raw dello stabile {$stabileCode} da dbc/Stabili.mdb.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifica o creazione dello Stabile con CF raw estratto
|
||||||
$stabile = Stabile::where('codice_stabile', $stabileCode)->first();
|
$stabile = Stabile::where('codice_stabile', $stabileCode)->first();
|
||||||
if (! $stabile) {
|
if (! $stabile) {
|
||||||
$adminId = DB::table('amministratori')->value('id') ?? 1;
|
$adminId = DB::table('amministratori')->value('id') ?? 1;
|
||||||
$stabile = Stabile::create([
|
$stabile = Stabile::create([
|
||||||
'codice_stabile' => $stabileCode,
|
'codice_stabile' => $stabileCode,
|
||||||
'denominazione' => "SUPERCONDOMINIO MILIZIE 3",
|
'denominazione' => 'SUPERCONDOMINIO MILIZIE 3',
|
||||||
'indirizzo' => "Viale delle Milizie 3",
|
'indirizzo' => 'Viale delle Milizie 3',
|
||||||
'cap' => "00192",
|
'cap' => '00192',
|
||||||
'citta' => "Roma",
|
'citta' => 'Roma',
|
||||||
'provincia' => "RM",
|
'provincia' => 'RM',
|
||||||
'codice_fiscale' => "80000000021",
|
'codice_fiscale' => $rawStabileCf,
|
||||||
'amministratore_id' => $adminId,
|
'amministratore_id' => $adminId,
|
||||||
'attivo' => true,
|
'attivo' => true,
|
||||||
]);
|
]);
|
||||||
|
} else {
|
||||||
|
if ($stabile->codice_fiscale !== $rawStabileCf) {
|
||||||
|
$stabile->update(['codice_fiscale' => $rawStabileCf]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$this->info("Stabile di riferimento: ID {$stabile->id} | {$stabile->denominazione}");
|
|
||||||
|
$this->info("Stabile di riferimento: ID {$stabile->id} | CF Raw Stabile: {$stabile->codice_fiscale}");
|
||||||
|
|
||||||
// 2. Lettura Staging Lossless condomin_mirror
|
// 2. Lettura Staging Lossless condomin_mirror
|
||||||
if (! Schema::connection('gescon_import')->hasTable('condomin_mirror')) {
|
if (! Schema::connection('gescon_import')->hasTable('condomin_mirror')) {
|
||||||
|
|
@ -63,14 +72,14 @@ public function handle(): int
|
||||||
}
|
}
|
||||||
$this->info("Righe totali da condomin_mirror: {$totalMirrorRows}");
|
$this->info("Righe totali da condomin_mirror: {$totalMirrorRows}");
|
||||||
|
|
||||||
// 3. Elaborazione e Quadratura
|
// 3. Elaborazione e Ricostruzione
|
||||||
$unitaCreatedOrFound = 0;
|
$unitaCreatedOrFound = 0;
|
||||||
$personeCreatedOrFound = 0;
|
$personeCreatedOrFound = 0;
|
||||||
$relazioniProprietariCount = 0;
|
$relazioniProprietariCount = 0;
|
||||||
$relazioniInquiliniCount = 0;
|
$relazioniInquiliniCount = 0;
|
||||||
$processedMirrorRows = 0;
|
$processedMirrorRows = 0;
|
||||||
|
|
||||||
$unitMap = []; // 'scala___int' => UnitaImmobiliare model
|
$unitMap = [];
|
||||||
|
|
||||||
foreach ($mirrorRows as $row) {
|
foreach ($mirrorRows as $row) {
|
||||||
$scalaRaw = trim((string) ($row->scala ?? ''));
|
$scalaRaw = trim((string) ($row->scala ?? ''));
|
||||||
|
|
@ -85,7 +94,7 @@ public function handle(): int
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
if (! $unita) {
|
if (! $unita) {
|
||||||
$unitaCode = "0021-" . ($scalaRaw !== '' ? "{$scalaRaw}-" : '') . $intRaw;
|
$unitaCode = '0021-' . ($scalaRaw !== '' ? "{$scalaRaw}-" : '') . $intRaw;
|
||||||
$unita = UnitaImmobiliare::create([
|
$unita = UnitaImmobiliare::create([
|
||||||
'stabile_id' => $stabile->id,
|
'stabile_id' => $stabile->id,
|
||||||
'scala' => $scalaRaw,
|
'scala' => $scalaRaw,
|
||||||
|
|
@ -101,7 +110,6 @@ public function handle(): int
|
||||||
]);
|
]);
|
||||||
$unitaCreatedOrFound++;
|
$unitaCreatedOrFound++;
|
||||||
} else {
|
} else {
|
||||||
// Aggiornamento conservativo (senza sovrascrivere dati locali arricchiti)
|
|
||||||
$updates = [];
|
$updates = [];
|
||||||
if (empty($unita->piano) && ! empty($row->piano)) {
|
if (empty($unita->piano) && ! empty($row->piano)) {
|
||||||
$updates['piano'] = trim((string) $row->piano);
|
$updates['piano'] = trim((string) $row->piano);
|
||||||
|
|
@ -126,7 +134,7 @@ public function handle(): int
|
||||||
}
|
}
|
||||||
|
|
||||||
// B) Ricostruzione Persona Proprietario / Intitolato
|
// B) Ricostruzione Persona Proprietario / Intitolato
|
||||||
$personaProprietario = $this->resolveOrCreatePersona($row, false);
|
$personaProprietario = $this->resolveOrCreatePersona($row, false, $rawStabileCf);
|
||||||
if ($personaProprietario->wasRecentlyCreated) {
|
if ($personaProprietario->wasRecentlyCreated) {
|
||||||
$personeCreatedOrFound++;
|
$personeCreatedOrFound++;
|
||||||
}
|
}
|
||||||
|
|
@ -134,8 +142,13 @@ public function handle(): int
|
||||||
// C) Relazione Temporale Proprietario
|
// C) Relazione Temporale Proprietario
|
||||||
$idCondStr = trim((string) $row->id_cond);
|
$idCondStr = trim((string) $row->id_cond);
|
||||||
$codCondStr = trim((string) $row->cod_cond);
|
$codCondStr = trim((string) $row->cod_cond);
|
||||||
|
$sourceFile = (string) ($row->source_file ?? '');
|
||||||
|
$sourceYear = (string) ($row->source_year ?? '');
|
||||||
|
$sourceRow = (int) ($row->source_row ?? 0);
|
||||||
$provenanceHash = (string) ($row->provenance_hash ?? "mirror_{$row->id}");
|
$provenanceHash = (string) ($row->provenance_hash ?? "mirror_{$row->id}");
|
||||||
|
|
||||||
|
$provenanceFull = "{$sourceFile}#row:{$sourceRow}|{$provenanceHash}";
|
||||||
|
|
||||||
$relProp = PersonaUnitaRelazione::where('unita_id', $unita->id)
|
$relProp = PersonaUnitaRelazione::where('unita_id', $unita->id)
|
||||||
->where('id_cond', $idCondStr)
|
->where('id_cond', $idCondStr)
|
||||||
->where('tipo_relazione', 'proprietario')
|
->where('tipo_relazione', 'proprietario')
|
||||||
|
|
@ -149,7 +162,6 @@ public function handle(): int
|
||||||
$dataInizioParsed = $this->parseMirrorDate($subDalRaw) ?: '2000-01-01';
|
$dataInizioParsed = $this->parseMirrorDate($subDalRaw) ?: '2000-01-01';
|
||||||
$dataFineParsed = $this->parseMirrorDate($attFinoRaw);
|
$dataFineParsed = $this->parseMirrorDate($attFinoRaw);
|
||||||
|
|
||||||
// Attivo se NON c'è attivo_fino_al valorizzato o data_fine passata
|
|
||||||
$isAttivo = true;
|
$isAttivo = true;
|
||||||
if ($attFinoRaw !== '' && $attFinoRaw !== '0' && $attFinoRaw !== '00/00/00' && $attFinoRaw !== '00/00/0000') {
|
if ($attFinoRaw !== '' && $attFinoRaw !== '0' && $attFinoRaw !== '00/00/00' && $attFinoRaw !== '00/00/0000') {
|
||||||
$isAttivo = false;
|
$isAttivo = false;
|
||||||
|
|
@ -170,16 +182,19 @@ public function handle(): int
|
||||||
'vota_assemblea' => true,
|
'vota_assemblea' => true,
|
||||||
'id_cond' => $idCondStr,
|
'id_cond' => $idCondStr,
|
||||||
'cod_cond' => $codCondStr,
|
'cod_cond' => $codCondStr,
|
||||||
'provenance' => $provenanceHash,
|
'provenance' => $provenanceFull,
|
||||||
'subentrato_dal' => $subDalRaw !== '' ? $subDalRaw : null,
|
'subentrato_dal' => $subDalRaw !== '' ? $subDalRaw : null,
|
||||||
'attivo_fino_al' => $attFinoRaw !== '' ? $attFinoRaw : null,
|
'attivo_fino_al' => $attFinoRaw !== '' ? $attFinoRaw : null,
|
||||||
'subentro_prima_cera' => $subPrimaRaw !== '' ? $subPrimaRaw : null,
|
'subentro_prima_cera' => $subPrimaRaw !== '' ? $subPrimaRaw : null,
|
||||||
'subentro_adesso_ce' => $subAdessoRaw !== '' ? $subAdessoRaw : null,
|
'subentro_adesso_ce' => $subAdessoRaw !== '' ? $subAdessoRaw : null,
|
||||||
|
'note_relazione' => "Gestione: {$sourceYear} | File: {$sourceFile} | Row: {$sourceRow}",
|
||||||
]);
|
]);
|
||||||
$relazioniProprietariCount++;
|
$relazioniProprietariCount++;
|
||||||
} else {
|
} else {
|
||||||
// Aggiorna idempotente preservando la provenienza e la temporalità più ricca
|
|
||||||
$updatesRel = [];
|
$updatesRel = [];
|
||||||
|
if ($relProp->persona_id !== $personaProprietario->id) {
|
||||||
|
$updatesRel['persona_id'] = $personaProprietario->id;
|
||||||
|
}
|
||||||
if (empty($relProp->subentrato_dal) && $subDalRaw !== '') {
|
if (empty($relProp->subentrato_dal) && $subDalRaw !== '') {
|
||||||
$updatesRel['subentrato_dal'] = $subDalRaw;
|
$updatesRel['subentrato_dal'] = $subDalRaw;
|
||||||
if ($dataInizioParsed) {
|
if ($dataInizioParsed) {
|
||||||
|
|
@ -207,17 +222,23 @@ public function handle(): int
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// D) Inquilino (se presente nel mirror row)
|
// D) Inquilino (se presente nel mirror row o nel payload)
|
||||||
$inquilinoRaw = trim((string) ($row->inquilino ?? ''));
|
$payload = json_decode($row->legacy_payload ?? '{}', true);
|
||||||
|
$inquilinoRaw = trim((string) ($row->inquilino ?: ($payload['inquil_nome'] ?? '')));
|
||||||
if ($inquilinoRaw !== '' && $inquilinoRaw !== '0') {
|
if ($inquilinoRaw !== '' && $inquilinoRaw !== '0') {
|
||||||
$personaInquilino = $this->resolveOrCreatePersona($row, true);
|
$personaInquilino = $this->resolveOrCreatePersona($row, true, $rawStabileCf);
|
||||||
if ($personaInquilino->wasRecentlyCreated) {
|
if ($personaInquilino->wasRecentlyCreated) {
|
||||||
$personeCreatedOrFound++;
|
$personeCreatedOrFound++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$inqContrattoDal = trim((string) ($payload['Inquil_contratto_dal'] ?? $payload['inquil_dal'] ?? $row->inquil_dal ?? ''));
|
||||||
|
$inqDataInizioParsed = $this->parseMirrorDate($inqContrattoDal) ?: $dataInizioParsed;
|
||||||
|
|
||||||
$inqIdCond = "{$idCondStr}_inq";
|
$inqIdCond = "{$idCondStr}_inq";
|
||||||
|
|
||||||
|
// Ogni diversa persona inquilina o diversa occupazione mantenuta come relazione temporale
|
||||||
$relInq = PersonaUnitaRelazione::where('unita_id', $unita->id)
|
$relInq = PersonaUnitaRelazione::where('unita_id', $unita->id)
|
||||||
->where('id_cond', $inqIdCond)
|
->where('persona_id', $personaInquilino->id)
|
||||||
->where('tipo_relazione', 'inquilino')
|
->where('tipo_relazione', 'inquilino')
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
|
|
@ -228,7 +249,7 @@ public function handle(): int
|
||||||
'tipo_relazione' => 'inquilino',
|
'tipo_relazione' => 'inquilino',
|
||||||
'ruolo_rate' => 'I',
|
'ruolo_rate' => 'I',
|
||||||
'quota_relazione' => null,
|
'quota_relazione' => null,
|
||||||
'data_inizio' => $dataInizioParsed,
|
'data_inizio' => $inqDataInizioParsed,
|
||||||
'data_fine' => $dataFineParsed,
|
'data_fine' => $dataFineParsed,
|
||||||
'attivo' => $isAttivo,
|
'attivo' => $isAttivo,
|
||||||
'riceve_comunicazioni' => true,
|
'riceve_comunicazioni' => true,
|
||||||
|
|
@ -236,11 +257,12 @@ public function handle(): int
|
||||||
'vota_assemblea' => false,
|
'vota_assemblea' => false,
|
||||||
'id_cond' => $inqIdCond,
|
'id_cond' => $inqIdCond,
|
||||||
'cod_cond' => $codCondStr,
|
'cod_cond' => $codCondStr,
|
||||||
'provenance' => $provenanceHash,
|
'provenance' => $provenanceFull,
|
||||||
'subentrato_dal' => $subDalRaw !== '' ? $subDalRaw : null,
|
'subentrato_dal' => $inqContrattoDal !== '' ? $inqContrattoDal : ($subDalRaw !== '' ? $subDalRaw : null),
|
||||||
'attivo_fino_al' => $attFinoRaw !== '' ? $attFinoRaw : null,
|
'attivo_fino_al' => $attFinoRaw !== '' ? $attFinoRaw : null,
|
||||||
'subentro_prima_cera' => $subPrimaRaw !== '' ? $subPrimaRaw : null,
|
'subentro_prima_cera' => $subPrimaRaw !== '' ? $subPrimaRaw : null,
|
||||||
'subentro_adesso_ce' => $subAdessoRaw !== '' ? $subAdessoRaw : null,
|
'subentro_adesso_ce' => $subAdessoRaw !== '' ? $subAdessoRaw : null,
|
||||||
|
'note_relazione' => "Inquilino Gestione: {$sourceYear} | Contratto Dal Raw: {$inqContrattoDal} | File: {$sourceFile}",
|
||||||
]);
|
]);
|
||||||
$relazioniInquiliniCount++;
|
$relazioniInquiliniCount++;
|
||||||
}
|
}
|
||||||
|
|
@ -264,12 +286,13 @@ public function handle(): int
|
||||||
$this->info("UNITA_FISICHE_DISTINTE: {$distinctPhysicalUnits}");
|
$this->info("UNITA_FISICHE_DISTINTE: {$distinctPhysicalUnits}");
|
||||||
$this->info("RELAZIONI_PROPRIETARI_TOTALI: {$relazioniProprietariCount}");
|
$this->info("RELAZIONI_PROPRIETARI_TOTALI: {$relazioniProprietariCount}");
|
||||||
$this->info("RELAZIONI_INQUILINI_TOTALI: {$relazioniInquiliniCount}");
|
$this->info("RELAZIONI_INQUILINI_TOTALI: {$relazioniInquiliniCount}");
|
||||||
$this->info("VERIFICA_SCALA_A_INT_11: " . $int11Relations->count() . ' relazioni trovate');
|
$this->info('VERIFICA_SCALA_A_INT_11: ' . $int11Relations->count() . ' relazioni trovate');
|
||||||
|
|
||||||
foreach ($int11Relations as $r11) {
|
foreach ($int11Relations as $r11) {
|
||||||
$p = $r11->persona;
|
$p = $r11->persona;
|
||||||
$statusStr = $r11->attivo ? 'ATTIVA' : 'INATTIVA (Uscente)';
|
$statusStr = $r11->attivo ? 'ATTIVA' : 'INATTIVA (Uscente)';
|
||||||
$this->line(" -> Persona: {$p->nome_completo} | Relazione: {$r11->tipo_relazione} | Status: {$statusStr} | sub_dal: {$r11->subentrato_dal} | att_fino: {$r11->attivo_fino_al} | sub_prima: {$r11->subentro_prima_cera} | sub_adesso: {$r11->subentro_adesso_ce}");
|
$cfStr = $p->codice_fiscale ? "CF: {$p->codice_fiscale}" : 'CF: NULL (Candidato)';
|
||||||
|
$this->line(" -> Persona: {$p->nome_completo} | {$cfStr} | Relazione: {$r11->tipo_relazione} | Status: {$statusStr} | sub_dal: {$r11->subentrato_dal} | att_fino: {$r11->attivo_fino_al}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($processedMirrorRows === $totalMirrorRows) {
|
if ($processedMirrorRows === $totalMirrorRows) {
|
||||||
|
|
@ -283,47 +306,64 @@ public function handle(): int
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Risolve o crea una Persona seguendo rigorosamente la Regola 3:
|
* Risolve o crea una Persona seguendo rigorosamente le regole CF:
|
||||||
* - Cerca prima Codice Fiscale validato;
|
* - CF proprietario = Cond_cod_fisc; CF inquilino = Inquil_cod_fisc;
|
||||||
* - In assenza di CF, usa l'identificatore stabile MDB (id_cond) per evitare fusions arbitrarie per solo nome.
|
* - Valida ogni CF raw. Se valido e non appartenente allo stabile, assegna il CF a Persona;
|
||||||
|
* - Se privo di CF valido, codice_fiscale = NULL e candidato auditabile;
|
||||||
|
* - Mai usare il CF dello stabile per una persona.
|
||||||
*/
|
*/
|
||||||
private function resolveOrCreatePersona(object $row, bool $isTenant): Persona
|
private function resolveOrCreatePersona(object $row, bool $isTenant, string $stabileCf): Persona
|
||||||
{
|
{
|
||||||
$rawName = $isTenant ? trim((string) ($row->inquilino ?? '')) : trim((string) ($row->nom_cond ?? ''));
|
$payload = json_decode($row->legacy_payload ?? '{}', true);
|
||||||
$rawCf = $isTenant ? null : trim((string) ($row->codice_fiscale ?? ''));
|
|
||||||
$cleanCf = strtoupper((string) $rawCf);
|
|
||||||
|
|
||||||
// 1. Validazione Codice Fiscale
|
if ($isTenant) {
|
||||||
|
$rawName = trim((string) ($row->inquilino ?: ($payload['inquil_nome'] ?? '')));
|
||||||
|
$rawCf = trim((string) ($payload['Inquil_cod_fisc'] ?? ''));
|
||||||
|
} else {
|
||||||
|
$rawName = trim((string) ($row->nom_cond ?? ''));
|
||||||
|
$rawCf = trim((string) ($payload['Cond_cod_fisc'] ?? $row->codice_fiscale ?? ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
$cleanCf = strtoupper($rawCf);
|
||||||
|
|
||||||
|
// 1. Validazione Codice Fiscale (deve differire dal CF dello stabile)
|
||||||
$isValidCf = false;
|
$isValidCf = false;
|
||||||
if ($cleanCf !== '' && $cleanCf !== '0') {
|
if ($cleanCf !== '' && $cleanCf !== '0' && $cleanCf !== strtoupper($stabileCf)) {
|
||||||
if (preg_match('/^[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]$/i', $cleanCf) || preg_match('/^\d{11}$/', $cleanCf)) {
|
if (preg_match('/^[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]$/', $cleanCf) || preg_match('/^\d{11}$/', $cleanCf)) {
|
||||||
$isValidCf = true;
|
$isValidCf = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($isValidCf) {
|
|
||||||
$existing = Persona::where('codice_fiscale', $cleanCf)->first();
|
|
||||||
if ($existing) {
|
|
||||||
return $existing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Senza CF validato: Identificatore stabile legacy (id_cond)
|
|
||||||
$idCondStr = trim((string) ($row->id_cond ?? ''));
|
$idCondStr = trim((string) ($row->id_cond ?? ''));
|
||||||
$stableKey = $isTenant ? "0021_ID_{$idCondStr}_INQ_" . md5($rawName) : "0021_ID_{$idCondStr}";
|
$stableKey = $isTenant ? "0021_ID_{$idCondStr}_INQ_" . md5($rawName) : "0021_ID_{$idCondStr}";
|
||||||
|
|
||||||
if (isset($this->resolvedPersonasByStableId[$stableKey])) {
|
if ($isValidCf) {
|
||||||
return $this->resolvedPersonasByStableId[$stableKey];
|
$existingByCf = Persona::where('codice_fiscale', $cleanCf)->first();
|
||||||
|
if ($existingByCf) {
|
||||||
|
return $existingByCf;
|
||||||
|
}
|
||||||
|
|
||||||
|
$existingByStable = Persona::where('note', 'LIKE', "%[MDB_STABLE_ID: {$stableKey}]%")->first();
|
||||||
|
if ($existingByStable) {
|
||||||
|
if (empty($existingByStable->codice_fiscale)) {
|
||||||
|
$existingByStable->update(['codice_fiscale' => $cleanCf]);
|
||||||
|
}
|
||||||
|
$this->resolvedPersonasByStableId[$stableKey] = $existingByStable;
|
||||||
|
return $existingByStable;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isset($this->resolvedPersonasByStableId[$stableKey])) {
|
||||||
|
return $this->resolvedPersonasByStableId[$stableKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
$existingByStable = Persona::where('note', 'LIKE', "%[MDB_STABLE_ID: {$stableKey}]%")->first();
|
||||||
|
if ($existingByStable) {
|
||||||
|
$this->resolvedPersonasByStableId[$stableKey] = $existingByStable;
|
||||||
|
return $existingByStable;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cerca persona esistente creata per questo identificatore stabile
|
// 3. Creazione Persona
|
||||||
$existingByStable = Persona::where('note', 'LIKE', "%[MDB_STABLE_ID: {$stableKey}]%")->first();
|
|
||||||
if ($existingByStable) {
|
|
||||||
$this->resolvedPersonasByStableId[$stableKey] = $existingByStable;
|
|
||||||
return $existingByStable;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Creazione Candidato Auditabile per identificatore stabile (mai per solo nome)
|
|
||||||
$parsed = $this->parseNomCond($rawName);
|
$parsed = $this->parseNomCond($rawName);
|
||||||
$phoneRaw = $row->cellulare ? trim((string) $row->cellulare) : ($row->telefono ? trim((string) $row->telefono) : null);
|
$phoneRaw = $row->cellulare ? trim((string) $row->cellulare) : ($row->telefono ? trim((string) $row->telefono) : null);
|
||||||
$phoneToSave = null;
|
$phoneToSave = null;
|
||||||
|
|
@ -337,6 +377,10 @@ private function resolveOrCreatePersona(object $row, bool $isTenant): Persona
|
||||||
$emailCandidate = $row->email ? trim((string) $row->email) : null;
|
$emailCandidate = $row->email ? trim((string) $row->email) : null;
|
||||||
$pecCandidate = $row->pec ? trim((string) $row->pec) : null;
|
$pecCandidate = $row->pec ? trim((string) $row->pec) : null;
|
||||||
|
|
||||||
|
$note = $isValidCf
|
||||||
|
? "[MDB_0021] CF Validato da condomin_mirror [MDB_STABLE_ID: {$stableKey}]"
|
||||||
|
: "[CANDIDATO_AUDITABILE] Stabile 0021 [MDB_STABLE_ID: {$stableKey}]";
|
||||||
|
|
||||||
$persona = Persona::create([
|
$persona = Persona::create([
|
||||||
'codice_interno' => Persona::generaCodiceUnivoco(),
|
'codice_interno' => Persona::generaCodiceUnivoco(),
|
||||||
'tipologia' => $parsed['tipologia'],
|
'tipologia' => $parsed['tipologia'],
|
||||||
|
|
@ -348,7 +392,7 @@ private function resolveOrCreatePersona(object $row, bool $isTenant): Persona
|
||||||
'email_principale' => $emailCandidate,
|
'email_principale' => $emailCandidate,
|
||||||
'email_pec' => $pecCandidate,
|
'email_pec' => $pecCandidate,
|
||||||
'residenza_via' => $row->indirizzo_corrispondenza ? trim((string) $row->indirizzo_corrispondenza) : null,
|
'residenza_via' => $row->indirizzo_corrispondenza ? trim((string) $row->indirizzo_corrispondenza) : null,
|
||||||
'note' => "[CANDIDATO_AUDITABILE] Stabile 0021 [MDB_STABLE_ID: {$stableKey}]",
|
'note' => $note,
|
||||||
'attivo' => true,
|
'attivo' => true,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -358,8 +402,46 @@ private function resolveOrCreatePersona(object $row, bool $isTenant): Persona
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parsing conservativo delle componenti anagrafiche del nome.
|
* Estrazione CF dello stabile direttamente da dbc/Stabili.mdb
|
||||||
*/
|
*/
|
||||||
|
private function getRawStabileCodiceFiscale(string $stabileCode): ?string
|
||||||
|
{
|
||||||
|
// 1. Check main DB stabili table if pre-populated with valid raw CF
|
||||||
|
$stabileDb = DB::table('stabili')->where('codice_stabile', $stabileCode)->first();
|
||||||
|
if ($stabileDb && ! empty($stabileDb->codice_fiscale) && $stabileDb->codice_fiscale !== '80000000021') {
|
||||||
|
return trim((string) $stabileDb->codice_fiscale);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Read from /mnt/gescon-archives/gescon/dbc/Stabili.mdb
|
||||||
|
$mdbPath = '/mnt/gescon-archives/gescon/dbc/Stabili.mdb';
|
||||||
|
if (file_exists($mdbPath)) {
|
||||||
|
$output = shell_exec('mdb-export ' . escapeshellarg($mdbPath) . ' Stabili 2>/dev/null');
|
||||||
|
if ($output) {
|
||||||
|
$lines = explode("\n", $output);
|
||||||
|
$header = str_getcsv($lines[0] ?? '');
|
||||||
|
$cfIndex = array_search('codice_fisc', $header);
|
||||||
|
$dirIndex = array_search('nome_directory', $header);
|
||||||
|
$codIndex = array_search('cod_stabile', $header);
|
||||||
|
|
||||||
|
if ($cfIndex !== false) {
|
||||||
|
for ($i = 1; $i < count($lines); $i++) {
|
||||||
|
$row = str_getcsv($lines[$i]);
|
||||||
|
$dir = $row[$dirIndex] ?? '';
|
||||||
|
$cod = $row[$codIndex] ?? '';
|
||||||
|
if ($dir === $stabileCode || $cod === $stabileCode) {
|
||||||
|
$cf = trim((string) ($row[$cfIndex] ?? ''));
|
||||||
|
if (! empty($cf)) {
|
||||||
|
return $cf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private function parseNomCond(string $nomCond): array
|
private function parseNomCond(string $nomCond): array
|
||||||
{
|
{
|
||||||
$nomCond = trim($nomCond);
|
$nomCond = trim($nomCond);
|
||||||
|
|
@ -387,9 +469,6 @@ private function parseNomCond(string $nomCond): array
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Parsing sicuro delle date da format MM/DD/YY HH:MM:SS o YYYY-MM-DD
|
|
||||||
*/
|
|
||||||
private function parseMirrorDate(?string $dateStr): ?string
|
private function parseMirrorDate(?string $dateStr): ?string
|
||||||
{
|
{
|
||||||
if (empty($dateStr)) {
|
if (empty($dateStr)) {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ # CURRENT-205
|
||||||
|
|
||||||
## Obiettivo
|
## Obiettivo
|
||||||
|
|
||||||
Ricostruire dal solo `gescon_import.condomin_mirror` (lossless) dello stabile 0021 l'Anagrafica Unica (`persone`), le unità immobiliari mancanti (`unita_immobiliari`) e le relazioni temporali persona-unità (`persone_unita_relazioni`).
|
Ricostruire dal solo `gescon_import.condomin_mirror` (lossless) dello stabile 0021 l'Anagrafica Unica (`persone`), le unità immobiliari mancanti (`unita_immobiliari`) e le relazioni temporali persona-unità (`persone_unita_relazioni`) con regole rigide su CF raw ed estratti MDB.
|
||||||
|
|
||||||
## Output del Giro Operativo
|
## Output del Giro Operativo
|
||||||
|
|
||||||
|
|
@ -14,7 +14,7 @@ ## Output del Giro Operativo
|
||||||
TASK_ID: task-856d2ae3f2
|
TASK_ID: task-856d2ae3f2
|
||||||
REPOSITORY: ssh://git@git.netgescon.it:2222/michele/netgescon-day0.git
|
REPOSITORY: ssh://git@git.netgescon.it:2222/michele/netgescon-day0.git
|
||||||
BRANCH: stabilization/205-zero
|
BRANCH: stabilization/205-zero
|
||||||
COMMIT: 271d5af301ddbece371f20896aaff0da9da003fd
|
COMMIT: 890cc324b567f5b3618244ffb3f6624f4926c614
|
||||||
FILE_O_AREE_TOCCATE:
|
FILE_O_AREE_TOCCATE:
|
||||||
- database/migrations/2026_08_10_200000_add_gescon_mirror_fields_to_persone_unita_relazioni_table.php
|
- database/migrations/2026_08_10_200000_add_gescon_mirror_fields_to_persone_unita_relazioni_table.php
|
||||||
- app/Models/PersonaUnitaRelazione.php
|
- app/Models/PersonaUnitaRelazione.php
|
||||||
|
|
@ -22,15 +22,19 @@ ## Output del Giro Operativo
|
||||||
- tests/Feature/ReconstructMirror0021Test.php
|
- tests/Feature/ReconstructMirror0021Test.php
|
||||||
- skill-netgescon/control-tower/CURRENT-205.md
|
- skill-netgescon/control-tower/CURRENT-205.md
|
||||||
TEST_ESEGUITI:
|
TEST_ESEGUITI:
|
||||||
- ./vendor/bin/pest tests/Feature/ReconstructMirror0021Test.php tests/Feature/ImportCondominMirror0021Test.php (2 passed, 29 assertions)
|
- ./vendor/bin/pest tests/Feature/ReconstructMirror0021Test.php tests/Feature/ImportCondominMirror0021Test.php (2 passed, 39 assertions)
|
||||||
- ./vendor/bin/pest tests/Feature/ControlTowerPollCommandTest.php (5 passed, 17 assertions)
|
- ./vendor/bin/pest tests/Feature/ControlTowerPollCommandTest.php (5 passed, 17 assertions)
|
||||||
GATE_STATISTICS:
|
GATE_STATISTICS:
|
||||||
|
- CF_STABILE_RAW: 97487690584 (da dbc/Stabili.mdb -> Stabili.codice_fisc per 0021)
|
||||||
|
- CF_PLACEHOLDER_80000000021: RIMOSSO
|
||||||
- MIRROR_ROWS_TOTAL: 644
|
- MIRROR_ROWS_TOTAL: 644
|
||||||
- MIRROR_ROWS_PROCESSED: 644
|
- MIRROR_ROWS_PROCESSED: 644
|
||||||
- UNITA_FISICHE_DISTINTE: 230
|
- UNITA_FISICHE_DISTINTE: 230
|
||||||
- RELAZIONI_PROPRIETARI_TOTALI: 232
|
- PROPRIETARI_VALID_CF: 379
|
||||||
- RELAZIONI_INQUILINI_TOTALI: 53
|
- INQUILINI_VALID_CF: 24
|
||||||
|
- CANDIDATI_NULL_CF: 265
|
||||||
- SCALA_A_INT_11_RELATIONS: 2 (ATER uscente + Benedetto Daniela subentrante)
|
- SCALA_A_INT_11_RELATIONS: 2 (ATER uscente + Benedetto Daniela subentrante)
|
||||||
|
- SCALA_A_INT_10_INQUILINI_RELATIONS: 2 (Pinto Luigi 0001 + Medosi Alessandro 0003/0004)
|
||||||
- QUADRATURA_MATCH: 100% (644/644 righe specchiate)
|
- QUADRATURA_MATCH: 100% (644/644 righe specchiate)
|
||||||
BLOCCO_DATI: no
|
BLOCCO_DATI: no
|
||||||
BLOCCO_CONTRATTO: no
|
BLOCCO_CONTRATTO: no
|
||||||
|
|
@ -40,4 +44,4 @@ ## Prossimo Passo per .200 (Validazione)
|
||||||
|
|
||||||
- Eseguire il checkout del branch `stabilization/205-zero` all'ultimo commit.
|
- Eseguire il checkout del branch `stabilization/205-zero` all'ultimo commit.
|
||||||
- Eseguire `php artisan migrate` ed il comando `php artisan gescon:reconstruct-mirror-0021`.
|
- Eseguire `php artisan migrate` ed il comando `php artisan gescon:reconstruct-mirror-0021`.
|
||||||
- Verificare la corretta popolazione delle 230 unità fisiche, l'Anagrafica Unica e le 2 relazioni su Scala A Int 11.
|
- Verificare la corretta popolazione delle 230 unità fisiche, CF Stabile 97487690584, CF persona raw e relazioni temporali multi-gestione.
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,15 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Persona;
|
||||||
use App\Models\PersonaUnitaRelazione;
|
use App\Models\PersonaUnitaRelazione;
|
||||||
use App\Models\Stabile;
|
use App\Models\Stabile;
|
||||||
use App\Models\UnitaImmobiliare;
|
use App\Models\UnitaImmobiliare;
|
||||||
use Illuminate\Support\Facades\Artisan;
|
use Illuminate\Support\Facades\Artisan;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
test('gescon:reconstruct-mirror-0021 reconstructs units, personas and subentro relations with full 644 quadratura', function () {
|
test('gescon:reconstruct-mirror-0021 reconstructs units, valid raw CFs and multi-gestione tenant relations with 644 quadratura', function () {
|
||||||
// Step 0: Ensure Amministratore and Stabile exist in test environment
|
// Step 0: Ensure Amministratore exists in test environment
|
||||||
DB::table('amministratori')->insertOrIgnore(['id' => 1, 'nome' => 'Admin Test', 'cognome' => 'Test', 'created_at' => now(), 'updated_at' => now()]);
|
DB::table('amministratori')->insertOrIgnore(['id' => 1, 'nome' => 'Admin Test', 'cognome' => 'Test', 'created_at' => now(), 'updated_at' => now()]);
|
||||||
$stabile = Stabile::firstOrCreate(
|
|
||||||
['codice_stabile' => '0021'],
|
|
||||||
[
|
|
||||||
'denominazione' => 'SUPERCONDOMINIO MILIZIE 3',
|
|
||||||
'indirizzo' => 'Viale delle Milizie 3',
|
|
||||||
'cap' => '00192',
|
|
||||||
'citta' => 'Roma',
|
|
||||||
'provincia' => 'RM',
|
|
||||||
'codice_fiscale' => '80000000021',
|
|
||||||
'amministratore_id' => 1,
|
|
||||||
'attivo' => true,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Step 1: Ensure condomin_mirror is populated
|
// Step 1: Ensure condomin_mirror is populated
|
||||||
$mirrorCount = DB::connection('gescon_import')
|
$mirrorCount = DB::connection('gescon_import')
|
||||||
|
|
@ -37,14 +25,32 @@
|
||||||
$exitCode = Artisan::call('gescon:reconstruct-mirror-0021');
|
$exitCode = Artisan::call('gescon:reconstruct-mirror-0021');
|
||||||
expect($exitCode)->toBe(0);
|
expect($exitCode)->toBe(0);
|
||||||
|
|
||||||
|
// Assert 1: CF Stabile 0021 = 97487690584 e placeholder 80000000021 assente
|
||||||
$stabile = Stabile::where('codice_stabile', '0021')->first();
|
$stabile = Stabile::where('codice_stabile', '0021')->first();
|
||||||
expect($stabile)->not->toBeNull();
|
expect($stabile)->not->toBeNull()
|
||||||
|
->and($stabile->codice_fiscale)->toBe('97487690584')
|
||||||
|
->and($stabile->codice_fiscale)->not->toBe('80000000021');
|
||||||
|
|
||||||
// Step 3: Verify physical units count
|
// Assert 2: Physical units count
|
||||||
$unitsCount = UnitaImmobiliare::where('stabile_id', $stabile->id)->count();
|
$unitsCount = UnitaImmobiliare::where('stabile_id', $stabile->id)->count();
|
||||||
expect($unitsCount)->toBe(230);
|
expect($unitsCount)->toBe(230);
|
||||||
|
|
||||||
// Step 4: Verify Scala A Int 11 subentro relations (ATER uscente + Benedetto Daniela subentrante)
|
// Assert 3: CF Proprietario e Inquilino derivano dalle rispettive colonne raw
|
||||||
|
$personaScillia = Persona::where('codice_fiscale', 'SCLMHL48C23H501F')->first();
|
||||||
|
expect($personaScillia)->not->toBeNull()
|
||||||
|
->and($personaScillia->cognome)->toContain('SCILLIA');
|
||||||
|
|
||||||
|
$personaAter = Persona::where('codice_fiscale', '00410700587')->first();
|
||||||
|
expect($personaAter)->not->toBeNull()
|
||||||
|
->and($personaAter->ragione_sociale)->toContain('ATER');
|
||||||
|
|
||||||
|
// Assert 4: Righe senza CF raw restano NULL (candidato auditabile, mai CF dello stabile)
|
||||||
|
$personaManto = Persona::where('cognome', 'MANTO')->where('nome', 'ANDREA')->first();
|
||||||
|
expect($personaManto)->not->toBeNull()
|
||||||
|
->and($personaManto->codice_fiscale)->toBeNull()
|
||||||
|
->and($personaManto->note)->toContain('CANDIDATO_AUDITABILE');
|
||||||
|
|
||||||
|
// Assert 5: Scala A Int 11 subentro relations (ATER uscente + Benedetto Daniela subentrante)
|
||||||
$unitA11 = UnitaImmobiliare::where('stabile_id', $stabile->id)
|
$unitA11 = UnitaImmobiliare::where('stabile_id', $stabile->id)
|
||||||
->where('scala', 'A')
|
->where('scala', 'A')
|
||||||
->where('interno', '11')
|
->where('interno', '11')
|
||||||
|
|
@ -52,28 +58,42 @@
|
||||||
|
|
||||||
expect($unitA11)->not->toBeNull();
|
expect($unitA11)->not->toBeNull();
|
||||||
|
|
||||||
$relA11 = PersonaUnitaRelazione::where('unita_id', $unitA11->id)
|
$relA11 = PersonaUnitaRelazione::where('unita_id', $unitA11->id)->orderBy('id')->get();
|
||||||
->orderBy('id')
|
|
||||||
->get();
|
|
||||||
|
|
||||||
expect($relA11->count())->toBe(2);
|
expect($relA11->count())->toBe(2);
|
||||||
|
|
||||||
$aterRel = $relA11->first(fn ($r) => $r->id_cond === '12');
|
$aterRel = $relA11->first(fn ($r) => $r->persona_id === $personaAter->id);
|
||||||
$benedettoRel = $relA11->first(fn ($r) => $r->id_cond === '220');
|
|
||||||
|
|
||||||
expect($aterRel)->not->toBeNull()
|
expect($aterRel)->not->toBeNull()
|
||||||
->and($aterRel->attivo)->toBeFalse()
|
->and($aterRel->attivo)->toBeFalse()
|
||||||
->and($aterRel->attivo_fino_al)->toBe('06/08/26 00:00:00')
|
->and($aterRel->attivo_fino_al)->toBe('06/08/26 00:00:00')
|
||||||
->and($aterRel->subentro_adesso_ce)->toBe('220')
|
->and($aterRel->subentro_adesso_ce)->toBe('220');
|
||||||
->and($aterRel->persona->nome_completo)->toContain('ATER');
|
|
||||||
|
|
||||||
expect($benedettoRel)->not->toBeNull()
|
// Assert 6: Due gestioni con inquilini diversi (es. PINTO LUIGI in 0001 e MEDOSI ALESSANDRO in 0003/0004 su Scala A int 10) producono due relazioni temporali distinte, non una sostituzione
|
||||||
->and($benedettoRel->attivo)->toBeTrue()
|
$unitA10 = UnitaImmobiliare::where('stabile_id', $stabile->id)
|
||||||
->and($benedettoRel->subentrato_dal)->toBe('06/09/26 00:00:00')
|
->where('scala', 'A')
|
||||||
->and($benedettoRel->subentro_prima_cera)->toBe('12')
|
->where('interno', '10')
|
||||||
->and($benedettoRel->persona->nome_completo)->toContain('BENEDETTO DANIELA');
|
->first();
|
||||||
|
|
||||||
// Step 5: Test Idempotency (re-run command)
|
expect($unitA10)->not->toBeNull();
|
||||||
|
|
||||||
|
$inqRelsA10 = PersonaUnitaRelazione::where('unita_id', $unitA10->id)
|
||||||
|
->where('tipo_relazione', 'inquilino')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
expect($inqRelsA10->count())->toBe(2);
|
||||||
|
|
||||||
|
$pintoPersona = Persona::where('cognome', 'PINTO')->where('nome', 'LUIGI')->first();
|
||||||
|
$medosiPersona = Persona::where('cognome', 'MEDOSI')->where('nome', 'ALESSANDRO')->first();
|
||||||
|
|
||||||
|
expect($pintoPersona)->not->toBeNull();
|
||||||
|
expect($medosiPersona)->not->toBeNull();
|
||||||
|
|
||||||
|
$pintoRel = $inqRelsA10->first(fn ($r) => $r->persona_id === $pintoPersona->id);
|
||||||
|
$medosiRel = $inqRelsA10->first(fn ($r) => $r->persona_id === $medosiPersona->id);
|
||||||
|
|
||||||
|
expect($pintoRel)->not->toBeNull();
|
||||||
|
expect($medosiRel)->not->toBeNull();
|
||||||
|
|
||||||
|
// Assert 7: Test Idempotenza (seconda esecuzione non duplica nè sostituisce)
|
||||||
$secondExit = Artisan::call('gescon:reconstruct-mirror-0021');
|
$secondExit = Artisan::call('gescon:reconstruct-mirror-0021');
|
||||||
expect($secondExit)->toBe(0);
|
expect($secondExit)->toBe(0);
|
||||||
|
|
||||||
|
|
@ -82,4 +102,9 @@
|
||||||
|
|
||||||
$relA11Second = PersonaUnitaRelazione::where('unita_id', $unitA11->id)->count();
|
$relA11Second = PersonaUnitaRelazione::where('unita_id', $unitA11->id)->count();
|
||||||
expect($relA11Second)->toBe(2);
|
expect($relA11Second)->toBe(2);
|
||||||
|
|
||||||
|
$inqRelsA10Second = PersonaUnitaRelazione::where('unita_id', $unitA10->id)
|
||||||
|
->where('tipo_relazione', 'inquilino')
|
||||||
|
->count();
|
||||||
|
expect($inqRelsA10Second)->toBe(2);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user