netgescon-day0/docs/91-database/02-tabelle-collegate/movimenti_bancari.md

1596 lines
68 KiB
Markdown

# 💳 MOVIMENTI BANCARI - Riconciliazione Automatica
## 🎯 **OVERVIEW**
Sistema per gestione completa dei movimenti bancari con riconciliazione automatica delle registrazioni contabili. Supporta import da file CBI, bonifici SEPA, e matching intelligente delle operazioni condominiali.
---
## 🗄️ **STRUTTURA MYSQL**
```sql
CREATE TABLE movimenti_bancari (
-- Identificativi
id BIGINT PRIMARY KEY AUTO_INCREMENT,
uuid VARCHAR(36) NOT NULL UNIQUE, -- UUID per identificazione esterna
-- Gestione Multi-Tenant
gestione_id BIGINT NOT NULL, -- FK gestioni.id
-- Dati Bancari
banca_codice_abi VARCHAR(5) NOT NULL, -- Codice ABI della banca
banca_denominazione VARCHAR(255) NOT NULL, -- Nome della banca
conto_corrente_iban VARCHAR(34) NOT NULL, -- IBAN del conto
conto_corrente_numero VARCHAR(20) NULL, -- Numero conto (legacy)
-- Operazione Bancaria
data_operazione DATE NOT NULL, -- Data operazione
data_valuta DATE NOT NULL, -- Data valuta
numero_operazione VARCHAR(50) NULL, -- Numero progressivo banca
codice_operazione VARCHAR(10) NULL, -- Codice tipo operazione (es: 005=bonifico)
-- Importi e Movimento
importo DECIMAL(12,2) NOT NULL, -- Importo movimento (+ entrata, - uscita)
importo_commissioni DECIMAL(8,2) DEFAULT 0, -- Commissioni bancarie
divisa VARCHAR(3) DEFAULT 'EUR', -- Valuta
cambio DECIMAL(10,6) DEFAULT 1, -- Tasso di cambio
-- Descrizioni e Riferimenti
descrizione_banca TEXT NOT NULL, -- Descrizione fornita dalla banca
descrizione_normalizzata VARCHAR(500) NULL, -- Descrizione processata per matching
causale TEXT NULL, -- Causale operazione
riferimento_operazione VARCHAR(100) NULL, -- Riferimento univoco operazione
-- Controparte
controparte_nome VARCHAR(255) NULL, -- Nome beneficiario/ordinante
controparte_iban VARCHAR(34) NULL, -- IBAN controparte
controparte_bic VARCHAR(11) NULL, -- BIC controparte
controparte_normalizzata VARCHAR(255) NULL, -- Nome normalizzato per matching
-- Tipologia Movimento
tipo_movimento ENUM('ENTRATA','USCITA','GIROCONTO','COMMISSIONE','INTERESSE') NOT NULL,
categoria_movimento VARCHAR(50) NULL, -- Categoria specifica (es: QUOTA_CONDOMINIALE)
-- Riconoscimento Automatico Condomini (da UNICREDIT e altre banche)
causale_bancaria_codice VARCHAR(10) NULL, -- Codice causale banca (048=bonifico entrata, 208=uscita, etc.)
unita_immobiliare_estratta VARCHAR(20) NULL, -- UI estratta (B-1, C4, A26, D/11, etc.)
scala_estratta VARCHAR(5) NULL, -- Scala estratta (A, B, C, D)
interno_estratto VARCHAR(10) NULL, -- Interno estratto (1, 23, etc.)
avviso_pagamento_numero VARCHAR(20) NULL, -- Numero avviso pagamento estratto
rata_numero INT NULL, -- Numero rata estratto (1,2,3,4,5,6)
rata_tipologia ENUM('CONDOMINIO','RISCALDAMENTO','SUPERCONDOMINIO','LAVORI_FOGNE','LAVORI_VERDE','ALTRO') NULL,
-- Collegamento Incassi Gescon
incasso_gescon_id BIGINT NULL, -- FK incassi.id (se riconciliato)
cod_cassa_gescon VARCHAR(10) NULL, -- Codice cassa Gescon (CCB, CON)
anno_competenza YEAR NULL, -- Anno competenza estratto (2023, 2024)
periodo_riferimento VARCHAR(50) NULL, -- Periodo riferimento (2022-2023, 23-24, etc.)
-- Pattern Matching Avanzato
keywords_estratte JSON NULL, -- Keywords riconosciute per matching
pattern_riconosciuto VARCHAR(100) NULL, -- Pattern utilizzato per riconoscimento
confidenza_matching DECIMAL(3,2) DEFAULT 0, -- Livello confidenza matching (0-1)
-- Collegamenti Automatici
soggetto_id BIGINT NULL, -- FK soggetti.id (se riconosciuto)
unita_immobiliare_id BIGINT NULL, -- FK unita_immobiliari.id (se riconosciuta)
contabilita_id BIGINT NULL, -- FK contabilita.id (se riconciliato)
fattura_elettronica_id BIGINT NULL, -- FK fatture_elettroniche.id (se collegata)
-- Import e Controllo
fonte_import ENUM('CBI','SEPA','MT940','CSV','MANUALE') NOT NULL,
file_import VARCHAR(500) NULL, -- Nome file di importazione
hash_import VARCHAR(64) NULL, -- Hash per prevenire duplicati
data_import TIMESTAMP NULL, -- Quando importato
-- Riconciliazione
stato_riconciliazione ENUM('NON_RICONCILIATO','RICONCILIATO','PARZIALE','MANUALE','SOSPESO') DEFAULT 'NON_RICONCILIATO',
contabilita_id BIGINT NULL, -- FK contabilita.id (se riconciliato)
matching_score DECIMAL(5,2) NULL, -- Score algoritmo matching (0-100)
riconciliato_da BIGINT NULL, -- FK users.id
riconciliato_il TIMESTAMP NULL, -- Timestamp riconciliazione
note_riconciliazione TEXT NULL, -- Note processo riconciliazione
-- Identificazione Condominiale
unita_immobiliare_id BIGINT NULL, -- FK unita_immobiliari.id (se identificabile)
soggetto_id BIGINT NULL, -- FK soggetti.id (se identificabile)
rata_numero INT NULL, -- Numero rata se pagamento rateale
periodo_riferimento VARCHAR(20) NULL, -- Periodo pagamento (es: "2024-01")
-- Anomalie e Sospesi
anomalia BOOLEAN DEFAULT FALSE, -- Flag anomalia
tipo_anomalia VARCHAR(100) NULL, -- Descrizione anomalia
richiede_intervento BOOLEAN DEFAULT FALSE, -- Richiede intervento manuale
sospeso_motivo TEXT NULL, -- Motivo sospensione
-- Pattern Recognition
pattern_riconosciuto VARCHAR(100) NULL, -- Pattern automatico riconosciuto
confidence_level DECIMAL(5,2) DEFAULT 0, -- Livello confidenza riconoscimento
keywords_matched JSON NULL, -- Keywords che hanno fatto match
-- Audit e Sistema
stato ENUM('ATTIVO','CANCELLATO','ARCHIVIATO') DEFAULT 'ATTIVO',
note TEXT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
created_by BIGINT NULL, -- FK users.id
updated_by BIGINT NULL, -- FK users.id
-- Indici
INDEX idx_gestione (gestione_id),
INDEX idx_data_operazione (data_operazione),
INDEX idx_data_valuta (data_valuta),
INDEX idx_iban (conto_corrente_iban),
INDEX idx_importo (importo),
INDEX idx_tipo_movimento (tipo_movimento),
INDEX idx_stato_riconciliazione (stato_riconciliazione),
INDEX idx_contabilita (contabilita_id),
INDEX idx_unita_immobiliare (unita_immobiliare_id),
INDEX idx_soggetto (soggetto_id),
INDEX idx_hash_import (hash_import),
INDEX idx_anomalia (anomalia),
INDEX idx_pattern (pattern_riconosciuto),
UNIQUE KEY unique_hash_import (hash_import),
-- Foreign Keys
FOREIGN KEY (gestione_id) REFERENCES gestioni(id) ON DELETE CASCADE,
FOREIGN KEY (contabilita_id) REFERENCES contabilita(id) ON DELETE SET NULL,
FOREIGN KEY (unita_immobiliare_id) REFERENCES unita_immobiliari(id) ON DELETE SET NULL,
FOREIGN KEY (soggetto_id) REFERENCES soggetti(id) ON DELETE SET NULL,
FOREIGN KEY (riconciliato_da) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL,
-- Constraints
CONSTRAINT chk_importo_non_zero CHECK (importo != 0),
CONSTRAINT chk_commissioni_positive CHECK (importo_commissioni >= 0),
CONSTRAINT chk_matching_score CHECK (matching_score BETWEEN 0 AND 100),
CONSTRAINT chk_confidence_level CHECK (confidence_level BETWEEN 0 AND 100),
CONSTRAINT chk_cambio_positivo CHECK (cambio > 0)
);
-- Tabella per configurazione conti bancari
CREATE TABLE conti_bancari_configurazione (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
gestione_id BIGINT NOT NULL, -- FK gestioni.id
-- Dati Conto
denominazione VARCHAR(255) NOT NULL, -- Nome conto (es: "Conto Ordinario")
iban VARCHAR(34) NOT NULL, -- IBAN
bic VARCHAR(11) NULL, -- BIC/SWIFT
banca_denominazione VARCHAR(255) NOT NULL, -- Nome banca
banca_abi VARCHAR(5) NOT NULL, -- Codice ABI
banca_cab VARCHAR(5) NULL, -- Codice CAB
-- Collegamento Contabile
piano_conto_id BIGINT NOT NULL, -- FK piano_conti.id
-- Configurazione Import
formato_import_preferito ENUM('CBI','SEPA','MT940','CSV') DEFAULT 'CBI',
separatore_csv VARCHAR(5) NULL, -- Separatore per CSV
encoding_file VARCHAR(20) DEFAULT 'UTF-8', -- Encoding file
-- Pattern Automatici
pattern_entrate JSON NULL, -- Pattern per riconoscere entrate
pattern_uscite JSON NULL, -- Pattern per riconoscere uscite
pattern_commissioni JSON NULL, -- Pattern commissioni
-- Automazioni
auto_riconcilia_perfetti BOOLEAN DEFAULT TRUE, -- Auto-riconcilia match 100%
soglia_auto_riconciliazione DECIMAL(5,2) DEFAULT 95.00, -- Soglia automatica
-- Stato
attivo BOOLEAN DEFAULT TRUE,
conto_principale BOOLEAN DEFAULT FALSE, -- Se è il conto principale
note TEXT NULL,
-- Audit
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
created_by BIGINT NULL,
updated_by BIGINT NULL,
-- Indici e Vincoli
INDEX idx_gestione (gestione_id),
INDEX idx_iban (iban),
INDEX idx_piano_conto (piano_conto_id),
UNIQUE KEY unique_iban_gestione (iban, gestione_id),
FOREIGN KEY (gestione_id) REFERENCES gestioni(id) ON DELETE CASCADE,
FOREIGN KEY (piano_conto_id) REFERENCES piano_conti(id) ON DELETE RESTRICT,
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL
);
-- Tabella per regole di matching automatico
CREATE TABLE regole_matching_bancario (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
gestione_id BIGINT NOT NULL, -- FK gestioni.id
-- Configurazione Regola
nome_regola VARCHAR(255) NOT NULL, -- Nome descrittivo
priorita INT DEFAULT 50, -- Priorità applicazione (1-100)
attiva BOOLEAN DEFAULT TRUE,
-- Condizioni Matching
pattern_descrizione TEXT NULL, -- Pattern regex descrizione
pattern_controparte TEXT NULL, -- Pattern regex controparte
pattern_causale TEXT NULL, -- Pattern regex causale
importo_min DECIMAL(12,2) NULL, -- Importo minimo
importo_max DECIMAL(12,2) NULL, -- Importo massimo
tipo_movimento_filtro VARCHAR(50) NULL, -- Filtro tipo movimento
-- Azioni Automatiche
piano_conto_target_id BIGINT NULL, -- FK piano_conti.id
categoria_movimento_auto VARCHAR(50) NULL, -- Categoria automatica
descrizione_standardizzata VARCHAR(500) NULL, -- Descrizione standard
auto_approva BOOLEAN DEFAULT FALSE, -- Approva automaticamente
-- Pattern Recognition
keywords_positive JSON NULL, -- Keywords positive per match
keywords_negative JSON NULL, -- Keywords negative (esclusione)
peso_matching DECIMAL(5,2) DEFAULT 10.00, -- Peso nel calcolo score
-- Configurazione
note TEXT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
created_by BIGINT NULL,
updated_by BIGINT NULL,
-- Indici
INDEX idx_gestione (gestione_id),
INDEX idx_priorita (priorita),
INDEX idx_attiva (attiva),
INDEX idx_piano_conto_target (piano_conto_target_id),
FOREIGN KEY (gestione_id) REFERENCES gestioni(id) ON DELETE CASCADE,
FOREIGN KEY (piano_conto_target_id) REFERENCES piano_conti(id) ON DELETE SET NULL,
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL
);
```
---
## 📋 **MIGRATION LARAVEL**
```php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
public function up()
{
// Tabella principale movimenti bancari
Schema::create('movimenti_bancari', function (Blueprint $table) {
// Identificativi
$table->id();
$table->uuid('uuid')->unique();
// Multi-tenant
$table->foreignId('gestione_id')->constrained('gestioni')->onDelete('cascade');
// Dati bancari
$table->string('banca_codice_abi', 5);
$table->string('banca_denominazione');
$table->string('conto_corrente_iban', 34);
$table->string('conto_corrente_numero', 20)->nullable();
// Operazione
$table->date('data_operazione');
$table->date('data_valuta');
$table->string('numero_operazione', 50)->nullable();
$table->string('codice_operazione', 10)->nullable();
// Importi
$table->decimal('importo', 12, 2);
$table->decimal('importo_commissioni', 8, 2)->default(0);
$table->string('divisa', 3)->default('EUR');
$table->decimal('cambio', 10, 6)->default(1);
// Descrizioni
$table->text('descrizione_banca');
$table->string('descrizione_normalizzata', 500)->nullable();
$table->text('causale')->nullable();
$table->string('riferimento_operazione', 100)->nullable();
// Controparte
$table->string('controparte_nome')->nullable();
$table->string('controparte_iban', 34)->nullable();
$table->string('controparte_bic', 11)->nullable();
$table->string('controparte_normalizzata')->nullable();
// Tipologia
$table->enum('tipo_movimento', ['ENTRATA','USCITA','GIROCONTO','COMMISSIONE','INTERESSE']);
$table->string('categoria_movimento', 50)->nullable();
// Import
$table->enum('fonte_import', ['CBI','SEPA','MT940','CSV','MANUALE']);
$table->string('file_import', 500)->nullable();
$table->string('hash_import', 64)->nullable()->unique();
$table->timestamp('data_import')->nullable();
// Riconciliazione
$table->enum('stato_riconciliazione', [
'NON_RICONCILIATO','RICONCILIATO','PARZIALE','MANUALE','SOSPESO'
])->default('NON_RICONCILIATO');
$table->foreignId('contabilita_id')->nullable()->constrained('contabilita')->onDelete('set null');
$table->decimal('matching_score', 5, 2)->nullable();
$table->foreignId('riconciliato_da')->nullable()->constrained('users')->onDelete('set null');
$table->timestamp('riconciliato_il')->nullable();
$table->text('note_riconciliazione')->nullable();
// Identificazione
$table->foreignId('unita_immobiliare_id')->nullable()->constrained('unita_immobiliari')->onDelete('set null');
$table->foreignId('soggetto_id')->nullable()->constrained('soggetti')->onDelete('set null');
$table->integer('rata_numero')->nullable();
$table->string('periodo_riferimento', 20)->nullable();
// Anomalie
$table->boolean('anomalia')->default(false);
$table->string('tipo_anomalia', 100)->nullable();
$table->boolean('richiede_intervento')->default(false);
$table->text('sospeso_motivo')->nullable();
// Pattern Recognition
$table->string('pattern_riconosciuto', 100)->nullable();
$table->decimal('confidence_level', 5, 2)->default(0);
$table->json('keywords_matched')->nullable();
// Stato
$table->enum('stato', ['ATTIVO','CANCELLATO','ARCHIVIATO'])->default('ATTIVO');
$table->text('note')->nullable();
// Audit
$table->timestamps();
$table->foreignId('created_by')->nullable()->constrained('users')->onDelete('set null');
$table->foreignId('updated_by')->nullable()->constrained('users')->onDelete('set null');
// Indici
$table->index(['gestione_id']);
$table->index(['data_operazione']);
$table->index(['data_valuta']);
$table->index(['conto_corrente_iban']);
$table->index(['importo']);
$table->index(['tipo_movimento']);
$table->index(['stato_riconciliazione']);
$table->index(['anomalia']);
$table->index(['pattern_riconosciuto']);
});
// Configurazione conti bancari
Schema::create('conti_bancari_configurazione', function (Blueprint $table) {
$table->id();
$table->foreignId('gestione_id')->constrained('gestioni')->onDelete('cascade');
// Dati conto
$table->string('denominazione');
$table->string('iban', 34);
$table->string('bic', 11)->nullable();
$table->string('banca_denominazione');
$table->string('banca_abi', 5);
$table->string('banca_cab', 5)->nullable();
// Collegamento
$table->foreignId('piano_conto_id')->constrained('piano_conti')->onDelete('restrict');
// Import
$table->enum('formato_import_preferito', ['CBI','SEPA','MT940','CSV'])->default('CBI');
$table->string('separatore_csv', 5)->nullable();
$table->string('encoding_file', 20)->default('UTF-8');
// Pattern
$table->json('pattern_entrate')->nullable();
$table->json('pattern_uscite')->nullable();
$table->json('pattern_commissioni')->nullable();
// Automazioni
$table->boolean('auto_riconcilia_perfetti')->default(true);
$table->decimal('soglia_auto_riconciliazione', 5, 2)->default(95.00);
// Stato
$table->boolean('attivo')->default(true);
$table->boolean('conto_principale')->default(false);
$table->text('note')->nullable();
// Audit
$table->timestamps();
$table->foreignId('created_by')->nullable()->constrained('users')->onDelete('set null');
$table->foreignId('updated_by')->nullable()->constrained('users')->onDelete('set null');
// Vincoli
$table->unique(['iban', 'gestione_id'], 'unique_iban_gestione');
$table->index(['gestione_id']);
$table->index(['iban']);
});
// Regole matching automatico
Schema::create('regole_matching_bancario', function (Blueprint $table) {
$table->id();
$table->foreignId('gestione_id')->constrained('gestioni')->onDelete('cascade');
// Configurazione
$table->string('nome_regola');
$table->integer('priorita')->default(50);
$table->boolean('attiva')->default(true);
// Condizioni
$table->text('pattern_descrizione')->nullable();
$table->text('pattern_controparte')->nullable();
$table->text('pattern_causale')->nullable();
$table->decimal('importo_min', 12, 2)->nullable();
$table->decimal('importo_max', 12, 2)->nullable();
$table->string('tipo_movimento_filtro', 50)->nullable();
// Azioni
$table->foreignId('piano_conto_target_id')->nullable()->constrained('piano_conti')->onDelete('set null');
$table->string('categoria_movimento_auto', 50)->nullable();
$table->string('descrizione_standardizzata', 500)->nullable();
$table->boolean('auto_approva')->default(false);
// Pattern Recognition
$table->json('keywords_positive')->nullable();
$table->json('keywords_negative')->nullable();
$table->decimal('peso_matching', 5, 2)->default(10.00);
// Sistema
$table->text('note')->nullable();
$table->timestamps();
$table->foreignId('created_by')->nullable()->constrained('users')->onDelete('set null');
$table->foreignId('updated_by')->nullable()->constrained('users')->onDelete('set null');
// Indici
$table->index(['gestione_id']);
$table->index(['priorita']);
$table->index(['attiva']);
});
// Trigger per UUID automatico
DB::unprepared('
CREATE TRIGGER trg_movimenti_bancari_uuid
BEFORE INSERT ON movimenti_bancari
FOR EACH ROW
BEGIN
IF NEW.uuid IS NULL OR NEW.uuid = "" THEN
SET NEW.uuid = UUID();
END IF;
END
');
// Trigger per hash anti-duplicati
DB::unprepared('
CREATE TRIGGER trg_movimenti_bancari_hash
BEFORE INSERT ON movimenti_bancari
FOR EACH ROW
BEGIN
IF NEW.hash_import IS NULL THEN
SET NEW.hash_import = SHA2(CONCAT(
NEW.gestione_id, "|",
NEW.conto_corrente_iban, "|",
NEW.data_operazione, "|",
NEW.data_valuta, "|",
NEW.importo, "|",
IFNULL(NEW.numero_operazione, ""), "|",
LEFT(NEW.descrizione_banca, 100)
), 256);
END IF;
END
');
}
public function down()
{
DB::unprepared('DROP TRIGGER IF EXISTS trg_movimenti_bancari_uuid');
DB::unprepared('DROP TRIGGER IF EXISTS trg_movimenti_bancari_hash');
Schema::dropIfExists('regole_matching_bancario');
Schema::dropIfExists('conti_bancari_configurazione');
Schema::dropIfExists('movimenti_bancari');
}
};
```
---
## 🏗️ **ELOQUENT MODEL**
```php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use App\Traits\MultiTenantTrait;
use Carbon\Carbon;
/**
* Movimento Bancario Model
*
* Gestisce movimenti bancari con riconciliazione automatica
*/
class MovimentoBancario extends Model
{
use HasFactory, MultiTenantTrait;
protected $table = 'movimenti_bancari';
protected $fillable = [
'uuid',
'gestione_id',
'banca_codice_abi',
'banca_denominazione',
'conto_corrente_iban',
'conto_corrente_numero',
'data_operazione',
'data_valuta',
'numero_operazione',
'codice_operazione',
'importo',
'importo_commissioni',
'divisa',
'cambio',
'descrizione_banca',
'descrizione_normalizzata',
'causale',
'riferimento_operazione',
'controparte_nome',
'controparte_iban',
'controparte_bic',
'controparte_normalizzata',
'tipo_movimento',
'categoria_movimento',
'fonte_import',
'file_import',
'hash_import',
'data_import',
'stato_riconciliazione',
'contabilita_id',
'matching_score',
'riconciliato_da',
'riconciliato_il',
'note_riconciliazione',
'unita_immobiliare_id',
'soggetto_id',
'rata_numero',
'periodo_riferimento',
'anomalia',
'tipo_anomalia',
'richiede_intervento',
'sospeso_motivo',
'pattern_riconosciuto',
'confidence_level',
'keywords_matched',
'stato',
'note',
'created_by',
'updated_by'
];
protected $casts = [
'data_operazione' => 'date',
'data_valuta' => 'date',
'data_import' => 'datetime',
'riconciliato_il' => 'datetime',
'importo' => 'decimal:2',
'importo_commissioni' => 'decimal:2',
'cambio' => 'decimal:6',
'matching_score' => 'decimal:2',
'confidence_level' => 'decimal:2',
'keywords_matched' => 'array',
'anomalia' => 'boolean',
'richiede_intervento' => 'boolean',
'rata_numero' => 'integer'
];
// ==================== RELATIONSHIPS ====================
public function gestione(): BelongsTo
{
return $this->belongsTo(Gestione::class);
}
public function contabilita(): BelongsTo
{
return $this->belongsTo(Contabilita::class);
}
public function unitaImmobiliare(): BelongsTo
{
return $this->belongsTo(UnitaImmobiliare::class);
}
public function soggetto(): BelongsTo
{
return $this->belongsTo(Soggetto::class);
}
public function riconciliatoDa(): BelongsTo
{
return $this->belongsTo(User::class, 'riconciliato_da');
}
public function createdBy(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by');
}
public function updatedBy(): BelongsTo
{
return $this->belongsTo(User::class, 'updated_by');
}
// ==================== SCOPES ====================
public function scopeNonRiconciliati($query)
{
return $query->where('stato_riconciliazione', 'NON_RICONCILIATO');
}
public function scopeRiconciliati($query)
{
return $query->where('stato_riconciliazione', 'RICONCILIATO');
}
public function scopeConAnomazie($query)
{
return $query->where('anomalia', true);
}
public function scopeEntrate($query)
{
return $query->where('tipo_movimento', 'ENTRATA');
}
public function scopeUscite($query)
{
return $query->where('tipo_movimento', 'USCITA');
}
public function scopePeriodo($query, Carbon $inizio, Carbon $fine)
{
return $query->whereBetween('data_operazione', [$inizio, $fine]);
}
public function scopeConto($query, string $iban)
{
return $query->where('conto_corrente_iban', $iban);
}
// ==================== BUSINESS METHODS ====================
/**
* Riconcilia movimento con registrazione contabile
*/
public function riconcilia(int $contabilitaId, ?string $note = null): bool
{
$contabilita = Contabilita::find($contabilitaId);
if (!$contabilita || $contabilita->gestione_id !== $this->gestione_id) {
return false;
}
// Verifica coerenza importi (tolleranza 0.01€)
$differenzaImporti = abs(abs($this->importo) - $contabilita->importo_totale);
if ($differenzaImporti > 0.01) {
return false;
}
$this->update([
'stato_riconciliazione' => 'RICONCILIATO',
'contabilita_id' => $contabilitaId,
'riconciliato_da' => auth()->id(),
'riconciliato_il' => now(),
'note_riconciliazione' => $note,
'matching_score' => 100.00,
'anomalia' => false,
'updated_by' => auth()->id()
]);
// Aggiorna anche la contabilità
$contabilita->update([
'riconciliato' => true,
'data_riconciliazione' => now(),
'updated_by' => auth()->id()
]);
return true;
}
/**
* Rimuovi riconciliazione
*/
public function annullaRiconciliazione(?string $motivo = null): bool
{
if ($this->stato_riconciliazione !== 'RICONCILIATO') {
return false;
}
$contabilita = $this->contabilita;
$this->update([
'stato_riconciliazione' => 'NON_RICONCILIATO',
'contabilita_id' => null,
'riconciliato_da' => null,
'riconciliato_il' => null,
'note_riconciliazione' => $motivo,
'matching_score' => null,
'updated_by' => auth()->id()
]);
// Aggiorna contabilità
if ($contabilita) {
$contabilita->update([
'riconciliato' => false,
'data_riconciliazione' => null,
'updated_by' => auth()->id()
]);
}
return true;
}
/**
* Calcola score di matching con registrazione contabile
*/
public function calcolaMatchingScore(Contabilita $contabilita): float
{
$score = 0;
// Verifica importo (40 punti max)
$differenzaImporti = abs(abs($this->importo) - $contabilita->importo_totale);
if ($differenzaImporti <= 0.01) {
$score += 40;
} elseif ($differenzaImporti <= 1) {
$score += 30;
} elseif ($differenzaImporti <= 10) {
$score += 20;
}
// Verifica data (20 punti max)
$differenzaGiorni = abs($this->data_operazione->diffInDays($contabilita->data_registrazione));
if ($differenzaGiorni <= 1) {
$score += 20;
} elseif ($differenzaGiorni <= 7) {
$score += 15;
} elseif ($differenzaGiorni <= 30) {
$score += 10;
}
// Verifica descrizione (20 punti max)
$similaritaDescrizione = $this->calcolaSimilaritaDescrizione($contabilita->descrizione);
$score += ($similaritaDescrizione / 100) * 20;
// Verifica soggetto/unità (20 punti max)
if ($this->soggetto_id && $contabilita->soggetto_id === $this->soggetto_id) {
$score += 15;
}
if ($this->unita_immobiliare_id && $contabilita->unita_immobiliare_id === $this->unita_immobiliare_id) {
$score += 15;
}
return min(100, $score);
}
/**
* Calcola similarità tra descrizioni
*/
private function calcolaSimilaritaDescrizione(string $descrizioneContabilita): float
{
$desc1 = $this->normalizzaDescrizione($this->descrizione_banca);
$desc2 = $this->normalizzaDescrizione($descrizioneContabilita);
// Algoritmo semplice di similarità basato su parole comuni
$parole1 = explode(' ', $desc1);
$parole2 = explode(' ', $desc2);
$parolaComuni = array_intersect($parole1, $parole2);
$totaleParole = array_unique(array_merge($parole1, $parole2));
return count($totaleParole) > 0 ? (count($parolaComuni) / count($totaleParole)) * 100 : 0;
}
/**
* Normalizza descrizione per matching
*/
public function normalizzaDescrizione(string $descrizione): string
{
// Converti in minuscolo
$normalized = strtolower($descrizione);
// Rimuovi caratteri speciali
$normalized = preg_replace('/[^a-z0-9\s]/', ' ', $normalized);
// Rimuovi spazi multipli
$normalized = preg_replace('/\s+/', ' ', $normalized);
// Rimuovi parole comuni (stop words)
$stopWords = ['di', 'da', 'per', 'con', 'del', 'della', 'dei', 'delle', 'il', 'la', 'lo', 'le', 'gli', 'un', 'una', 'uno'];
$parole = explode(' ', $normalized);
$paroleFiltrate = array_diff($parole, $stopWords);
return trim(implode(' ', $paroleFiltrate));
}
/**
* Analizza movimento per pattern recognition
*/
public function analizzaPattern(): array
{
$patterns = [];
$keywords = [];
$descrizione = $this->normalizzaDescrizione($this->descrizione_banca);
$controparte = $this->controparte_nome ? $this->normalizzaDescrizione($this->controparte_nome) : '';
// Pattern Quote Condominiali
if (preg_match('/quota|contributo|condomin/', $descrizione) ||
($this->tipo_movimento === 'ENTRATA' && $this->importo > 50)) {
$patterns[] = 'QUOTA_CONDOMINIALE';
$keywords[] = 'quota condominiale';
}
// Pattern Bonifico SEPA
if (preg_match('/bonifico|sepa|transfer/', $descrizione)) {
$patterns[] = 'BONIFICO_SEPA';
$keywords[] = 'bonifico';
}
// Pattern Fornitore
if ($this->tipo_movimento === 'USCITA' && $controparte &&
preg_match('/spa|srl|snc|sas|ditta/', $controparte)) {
$patterns[] = 'PAGAMENTO_FORNITORE';
$keywords[] = 'fornitore';
}
// Pattern Commissioni
if (preg_match('/commissione|spese|addebito/', $descrizione) &&
$this->importo < 0 && abs($this->importo) < 50) {
$patterns[] = 'COMMISSIONE_BANCARIA';
$keywords[] = 'commissione';
}
// Aggiorna movimento con pattern riconosciuti
if (!empty($patterns)) {
$this->update([
'pattern_riconosciuto' => implode(',', $patterns),
'keywords_matched' => $keywords,
'confidence_level' => $this->calcolaConfidenceLevel($patterns),
'updated_by' => auth()->id()
]);
}
return [
'patterns' => $patterns,
'keywords' => $keywords,
'confidence' => $this->confidence_level ?? 0
];
}
/**
* Calcola livello di confidenza del riconoscimento
*/
private function calcolaConfidenceLevel(array $patterns): float
{
$baseConfidence = count($patterns) * 20; // 20 punti per pattern
// Bonus per importi tipici
if ($this->tipo_movimento === 'ENTRATA' && $this->importo >= 100 && $this->importo <= 5000) {
$baseConfidence += 15;
}
// Bonus per IBAN conosciuti
if ($this->controparte_iban && $this->soggetto_id) {
$baseConfidence += 25;
}
// Bonus per dati coerenti
if ($this->data_operazione->isWeekday()) { // Giorni lavorativi
$baseConfidence += 10;
}
return min(100, $baseConfidence);
}
/**
* Segna movimento come anomalo
*/
public function segnalaAnomaila(string $tipoAnomalia, string $descrizione = null): void
{
$this->update([
'anomalia' => true,
'tipo_anomalia' => $tipoAnomalia,
'richiede_intervento' => true,
'sospeso_motivo' => $descrizione,
'stato_riconciliazione' => 'SOSPESO',
'updated_by' => auth()->id()
]);
}
/**
* Risolvi anomalia
*/
public function risolviAnomalia(string $note = null): void
{
$this->update([
'anomalia' => false,
'tipo_anomalia' => null,
'richiede_intervento' => false,
'sospeso_motivo' => null,
'stato_riconciliazione' => 'NON_RICONCILIATO',
'note_riconciliazione' => $note,
'updated_by' => auth()->id()
]);
}
/**
* Trova possibili match automatici
*/
public function trovaMatchAutomatici(): array
{
$matches = [];
// Cerca registrazioni contabili non riconciliate nello stesso periodo
$registrazioni = Contabilita::where('gestione_id', $this->gestione_id)
->where('riconciliato', false)
->whereBetween('data_registrazione', [
$this->data_operazione->subDays(7),
$this->data_operazione->addDays(7)
])
->get();
foreach ($registrazioni as $registrazione) {
$score = $this->calcolaMatchingScore($registrazione);
if ($score >= 60) { // Soglia minima per considerare match
$matches[] = [
'contabilita_id' => $registrazione->id,
'score' => $score,
'descrizione' => $registrazione->descrizione,
'importo' => $registrazione->importo_totale,
'data' => $registrazione->data_registrazione,
'auto_approvabile' => $score >= 95
];
}
}
// Ordina per score decrescente
usort($matches, function($a, $b) {
return $b['score'] <=> $a['score'];
});
return $matches;
}
// ==================== EVENTI MODEL ====================
protected static function booted()
{
static::creating(function ($movimento) {
// Genera UUID se non presente
if (!$movimento->uuid) {
$movimento->uuid = \Str::uuid();
}
// Normalizza descrizione per matching
if ($movimento->descrizione_banca) {
$movimento->descrizione_normalizzata = $movimento->normalizzaDescrizione($movimento->descrizione_banca);
}
// Normalizza nome controparte
if ($movimento->controparte_nome) {
$movimento->controparte_normalizzata = $movimento->normalizzaDescrizione($movimento->controparte_nome);
}
// Determina tipo movimento se non specificato
if (!$movimento->tipo_movimento) {
$movimento->tipo_movimento = $movimento->importo >= 0 ? 'ENTRATA' : 'USCITA';
}
$movimento->created_by = auth()->id();
});
static::updating(function ($movimento) {
$movimento->updated_by = auth()->id();
});
static::created(function ($movimento) {
// Analizza pattern automaticamente
$movimento->analizzaPattern();
});
// Filtro multi-tenant
static::addGlobalScope(new \App\Scopes\MultiTenantScope);
}
}
```
---
## 🔄 **SISTEMA DI IMPORT AUTOMATICO**
### **Parser CBI (Corporate Banking Interface)**
```php
<?php
namespace App\Services;
/**
* Service per import automatico movimenti bancari da file CBI
*/
class CbiImportService
{
private $gestione;
private $contoConfigurazione;
public function __construct(Gestione $gestione, ContoBancarioConfigurazione $conto)
{
$this->gestione = $gestione;
$this->contoConfigurazione = $conto;
}
/**
* Importa movimenti da file CBI
*/
public function importFile(string $filePath): array
{
$risultato = [
'importati' => 0,
'duplicati' => 0,
'errori' => 0,
'anomalie' => 0,
'movimenti' => []
];
$contenuto = file_get_contents($filePath);
$righe = explode("\n", $contenuto);
foreach ($righe as $numeroRiga => $riga) {
try {
if (strlen(trim($riga)) === 0) continue;
$movimento = $this->parseRigaCbi($riga, $numeroRiga);
if ($movimento) {
$movimentoSalvato = $this->salvaMovimento($movimento, basename($filePath));
if ($movimentoSalvato) {
$risultato['importati']++;
$risultato['movimenti'][] = $movimentoSalvato;
// Analizza automaticamente per matching
$this->tentaMatchingAutomatico($movimentoSalvato);
}
}
} catch (\Exception $e) {
$risultato['errori']++;
\Log::error("Errore import CBI riga {$numeroRiga}: " . $e->getMessage());
}
}
return $risultato;
}
/**
* Parsing singola riga formato CBI
*/
private function parseRigaCbi(string $riga, int $numeroRiga): ?array
{
// Formato CBI standard: posizioni fisse
if (strlen($riga) < 120) {
return null;
}
$data = [
'data_operazione' => $this->parseCbiDate(substr($riga, 40, 6)),
'data_valuta' => $this->parseCbiDate(substr($riga, 46, 6)),
'importo' => $this->parseCbiAmount(substr($riga, 52, 15), substr($riga, 67, 1)),
'numero_operazione' => trim(substr($riga, 10, 10)),
'codice_operazione' => trim(substr($riga, 20, 3)),
'descrizione_banca' => trim(substr($riga, 80, 40)),
'riferimento_operazione' => trim(substr($riga, 120, 16)),
];
// Campi aggiuntivi se presenti
if (strlen($riga) > 140) {
$data['controparte_nome'] = trim(substr($riga, 140, 30));
$data['controparte_iban'] = trim(substr($riga, 170, 34));
}
return $data;
}
/**
* Parse data formato CBI (GGMMAA)
*/
private function parseCbiDate(string $dateString): ?\Carbon\Carbon
{
if (strlen($dateString) !== 6) return null;
$giorno = substr($dateString, 0, 2);
$mese = substr($dateString, 2, 2);
$anno = '20' . substr($dateString, 4, 2);
try {
return \Carbon\Carbon::createFromFormat('Y-m-d', "{$anno}-{$mese}-{$giorno}");
} catch (\Exception $e) {
return null;
}
}
/**
* Parse importo CBI (15 cifre + segno)
*/
private function parseCbiAmount(string $amountString, string $signFlag): float
{
$amount = (float) $amountString / 100; // Ultimi 2 cifre sono centesimi
return $signFlag === 'D' ? -$amount : $amount; // D=Dare (uscita), A=Avere (entrata)
}
/**
* Salva movimento nel database
*/
private function salvaMovimento(array $datiMovimento, string $nomeFile): ?MovimentoBancario
{
// Completa dati movimento
$datiCompleti = array_merge($datiMovimento, [
'gestione_id' => $this->gestione->id,
'banca_codice_abi' => $this->contoConfigurazione->banca_abi,
'banca_denominazione' => $this->contoConfigurazione->banca_denominazione,
'conto_corrente_iban' => $this->contoConfigurazione->iban,
'fonte_import' => 'CBI',
'file_import' => $nomeFile,
'data_import' => now(),
'tipo_movimento' => $datiMovimento['importo'] >= 0 ? 'ENTRATA' : 'USCITA',
'stato_riconciliazione' => 'NON_RICONCILIATO'
]);
// Verifica duplicati tramite hash
$hashMovimento = $this->generaHashMovimento($datiCompleti);
if (MovimentoBancario::where('hash_import', $hashMovimento)->exists()) {
return null; // Duplicato
}
$datiCompleti['hash_import'] = $hashMovimento;
return MovimentoBancario::create($datiCompleti);
}
/**
* Genera hash per prevenire duplicati
*/
private function generaHashMovimento(array $dati): string
{
$stringaHash = implode('|', [
$dati['gestione_id'],
$dati['conto_corrente_iban'],
$dati['data_operazione']->format('Y-m-d'),
$dati['data_valuta']->format('Y-m-d'),
$dati['importo'],
$dati['numero_operazione'] ?? '',
substr($dati['descrizione_banca'], 0, 100)
]);
return hash('sha256', $stringaHash);
}
/**
* Tenta matching automatico dopo import
*/
private function tentaMatchingAutomatico(MovimentoBancario $movimento): void
{
$matches = $movimento->trovaMatchAutomatici();
foreach ($matches as $match) {
if ($match['auto_approvabile'] && $match['score'] >= $this->contoConfigurazione->soglia_auto_riconciliazione) {
// Auto-riconcilia se score è molto alto
if ($this->contoConfigurazione->auto_riconcilia_perfetti) {
$movimento->riconcilia(
$match['contabilita_id'],
"Riconciliazione automatica (score: {$match['score']})"
);
break;
}
}
}
// Se non riconciliato automaticamente ma con anomalie, segna
if ($movimento->stato_riconciliazione === 'NON_RICONCILIATO') {
$this->verificaAnomalieMovimento($movimento);
}
}
/**
* Verifica anomalie nel movimento importato
*/
private function verificaAnomalieMovimento(MovimentoBancario $movimento): void
{
// Importo sospetto (troppo alto o troppo basso)
if (abs($movimento->importo) > 50000) {
$movimento->segnalaAnomaila('IMPORTO_ELEVATO', 'Importo superiore a 50.000€');
return;
}
if (abs($movimento->importo) < 0.01) {
$movimento->segnalaAnomaila('IMPORTO_ZERO', 'Importo troppo basso');
return;
}
// Data futura
if ($movimento->data_operazione->isFuture()) {
$movimento->segnalaAnomaila('DATA_FUTURA', 'Data operazione nel futuro');
return;
}
// Weekend per operazioni sospette
if ($movimento->data_operazione->isWeekend() && $movimento->tipo_movimento === 'ENTRATA' && $movimento->importo > 1000) {
$movimento->segnalaAnomaila('OPERAZIONE_WEEKEND', 'Grande entrata in weekend');
return;
}
// Descrizione vuota o sospetta
if (empty(trim($movimento->descrizione_banca)) || strlen(trim($movimento->descrizione_banca)) < 5) {
$movimento->segnalaAnomaila('DESCRIZIONE_INSUFFICIENTE', 'Descrizione troppo breve o vuota');
return;
}
}
}
```
---
## 📊 **DASHBOARD RICONCILIAZIONE**
### **Query per Dashboard**
```sql
-- RIEPILOGO RICONCILIAZIONE PER GESTIONE
SELECT
g.denominazione AS gestione,
cbc.denominazione AS conto,
COUNT(*) AS totale_movimenti,
COUNT(CASE WHEN mb.stato_riconciliazione = 'RICONCILIATO' THEN 1 END) AS riconciliati,
COUNT(CASE WHEN mb.stato_riconciliazione = 'NON_RICONCILIATO' THEN 1 END) AS da_riconciliare,
COUNT(CASE WHEN mb.anomalia = 1 THEN 1 END) AS con_anomalie,
SUM(CASE WHEN mb.tipo_movimento = 'ENTRATA' THEN mb.importo ELSE 0 END) AS totale_entrate,
SUM(CASE WHEN mb.tipo_movimento = 'USCITA' THEN ABS(mb.importo) ELSE 0 END) AS totale_uscite,
SUM(mb.importo) AS saldo_periodo
FROM movimenti_bancari mb
INNER JOIN gestioni g ON mb.gestione_id = g.id
LEFT JOIN conti_bancari_configurazione cbc ON mb.conto_corrente_iban = cbc.iban AND mb.gestione_id = cbc.gestione_id
WHERE mb.data_operazione BETWEEN :data_inizio AND :data_fine
AND mb.stato = 'ATTIVO'
AND mb.gestione_id = :gestione_id
GROUP BY g.id, cbc.id
ORDER BY g.denominazione, cbc.denominazione;
-- MOVIMENTI NON RICONCILIATI CON SUGGERIMENTI
SELECT
mb.*,
c.id AS contabilita_suggerita_id,
c.descrizione AS contabilita_descrizione,
c.importo_totale AS contabilita_importo,
c.data_registrazione AS contabilita_data,
ABS(mb.importo - c.importo_totale) AS differenza_importo,
DATEDIFF(mb.data_operazione, c.data_registrazione) AS differenza_giorni
FROM movimenti_bancari mb
LEFT JOIN contabilita c ON (
c.gestione_id = mb.gestione_id
AND c.riconciliato = FALSE
AND ABS(ABS(mb.importo) - c.importo_totale) <= 1.00
AND ABS(DATEDIFF(mb.data_operazione, c.data_registrazione)) <= 7
)
WHERE mb.stato_riconciliazione = 'NON_RICONCILIATO'
AND mb.gestione_id = :gestione_id
AND mb.anomalia = FALSE
ORDER BY mb.data_operazione DESC, ABS(mb.importo - IFNULL(c.importo_totale, 0));
```
---
## 🚀 **PROSSIMI STEP**
**Completato**: Movimenti Bancari con riconciliazione automatica
🔄 **Prossimo**: Fatture Elettroniche XML per compliance fiscale
**Seguente**: Testing e implementazione
Il sistema **Movimenti Bancari** è completo con:
- 🔄 **Import automatico** CBI/SEPA
- 🎯 **Matching intelligente** con score
-**Riconciliazione automatica** sopra soglia
- 🚨 **Gestione anomalie** e sospesi
Procediamo con le **Fatture Elettroniche**? 📄
---
## 🧠 **SISTEMA RICONOSCIMENTO AUTOMATICO DATI REALI**
### **📊 PATTERN IDENTIFICATI DA UNICREDIT**
Dall'analisi dei movimenti bancari reali di UNICREDIT (Supercondominio Viale delle Milizie 3) sono stati identificati pattern molto precisi per il riconoscimento automatico:
**Esempi movimenti reali analizzati:**
- `BONIFICO A VOSTRO FAVORE BONIFICO SEPA DA DUCA CARLO PER SALDO RISC. 22-23 B-1 COMM 0,00 SPESE 0,00 TRN 1001240021013258`
- `BONIFICO A VOSTRO FAVORE BONIFICO SEPA DA ROSSI MARIO PER quota cond 2024 B/8 avviso 13675 prima rata`
- `DISPOSIZIONE DI BONIFICO SEPA A ENEL ENERGIA SPA PER SALDO FATT 12345`
### **🏗️ TABELLE AGGIUNTIVE PER RICONOSCIMENTO AVANZATO**
```sql
-- Tabella pattern riconoscimento personalizzabili per banca
CREATE TABLE movimenti_bancari_pattern (
-- Identificativi
id BIGINT PRIMARY KEY AUTO_INCREMENT,
nome_pattern VARCHAR(100) NOT NULL, -- Nome identificativo pattern
banca_abi VARCHAR(5) NULL, -- Specifico per banca (NULL = tutti)
-- Pattern Regex
regex_descrizione TEXT NOT NULL, -- Regex per matching descrizione
regex_flags VARCHAR(10) DEFAULT 'i', -- Flags regex (i=insensitive, etc.)
-- Estrazione Dati
campo_estratto VARCHAR(50) NOT NULL, -- Campo da estrarre
regex_estrazione TEXT NULL, -- Regex per estrazione valore
valore_default VARCHAR(100) NULL, -- Valore default se non estratto
-- Mapping Valori
mapping_valori JSON NULL, -- Mapping valori estratti
-- Classificazione
tipo_movimento_target ENUM('ENTRATA','USCITA','GIROCONTO','COMMISSIONE','INTERESSE') NULL,
categoria_target VARCHAR(50) NULL, -- Categoria da assegnare
priorita INT DEFAULT 100, -- Priorità applicazione (1=max)
-- Configurazione
attivo BOOLEAN DEFAULT TRUE,
confidence_score DECIMAL(3,2) DEFAULT 0.8, -- Score confidenza pattern
-- Sistema
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- Indici
INDEX idx_banca_abi (banca_abi),
INDEX idx_attivo (attivo),
INDEX idx_priorita (priorita)
);
-- Log riconoscimento per debug e miglioramento
CREATE TABLE movimenti_bancari_riconoscimento_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
movimento_bancario_id BIGINT NOT NULL, -- FK movimenti_bancari.id
pattern_id BIGINT NOT NULL, -- FK movimenti_bancari_pattern.id
-- Risultato Matching
matched BOOLEAN NOT NULL, -- Se pattern ha fatto match
valore_estratto TEXT NULL, -- Valore estratto dal pattern
confidence_score DECIMAL(3,2) NOT NULL, -- Score confidenza
-- Debug
regex_utilizzata TEXT NOT NULL, -- Regex utilizzata
testo_analizzato TEXT NOT NULL, -- Testo su cui è stata applicata
-- Sistema
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- Indici
INDEX idx_movimento (movimento_bancario_id),
INDEX idx_pattern (pattern_id),
INDEX idx_matched (matched),
-- Foreign Keys
FOREIGN KEY (movimento_bancario_id) REFERENCES movimenti_bancari(id) ON DELETE CASCADE,
FOREIGN KEY (pattern_id) REFERENCES movimenti_bancari_pattern(id) ON DELETE CASCADE
);
```
### **🎯 CAMPI AGGIUNTIVI NELLA TABELLA PRINCIPALE**
```sql
-- Aggiunta campi riconoscimento automatico alla tabella movimenti_bancari
ALTER TABLE movimenti_bancari ADD COLUMN causale_bancaria_codice VARCHAR(10) NULL COMMENT 'Codice causale bancaria (048, 208, etc.)';
ALTER TABLE movimenti_bancari ADD COLUMN unita_immobiliare_estratta VARCHAR(20) NULL COMMENT 'UI estratta automaticamente (B-1, C4, etc.)';
ALTER TABLE movimenti_bancari ADD COLUMN scala_estratta VARCHAR(5) NULL COMMENT 'Scala estratta (A, B, C, D)';
ALTER TABLE movimenti_bancari ADD COLUMN interno_estratto VARCHAR(10) NULL COMMENT 'Numero interno estratto';
ALTER TABLE movimenti_bancari ADD COLUMN avviso_pagamento_numero VARCHAR(20) NULL COMMENT 'Numero avviso di pagamento';
ALTER TABLE movimenti_bancari ADD COLUMN rata_numero INT NULL COMMENT 'Numero rata (1, 2, 3, etc.)';
ALTER TABLE movimenti_bancari ADD COLUMN rata_tipologia VARCHAR(50) NULL COMMENT 'Tipologia rata (RISCALDAMENTO, CONDOMINIO, etc.)';
ALTER TABLE movimenti_bancari ADD COLUMN anno_competenza YEAR NULL COMMENT 'Anno di competenza estratto';
ALTER TABLE movimenti_bancari ADD COLUMN periodo_riferimento VARCHAR(20) NULL COMMENT 'Periodo riferimento (22-23, 2023-2024, etc.)';
ALTER TABLE movimenti_bancari ADD COLUMN riferimento_fattura VARCHAR(50) NULL COMMENT 'Numero fattura se presente';
-- Campi specifici INTESA SANPAOLO
ALTER TABLE movimenti_bancari ADD COLUMN codice_disposizione_banca VARCHAR(20) NULL COMMENT 'Codice disposizione banca (es: 0124010543443092)';
ALTER TABLE movimenti_bancari ADD COLUMN riferimento_polizza VARCHAR(50) NULL COMMENT 'Numero polizza assicurativa se presente';
ALTER TABLE movimenti_bancari ADD COLUMN bic_ordinante VARCHAR(20) NULL COMMENT 'BIC della banca ordinante (es: INGBITD1)';
-- Indici per ricerca veloce
ALTER TABLE movimenti_bancari ADD INDEX idx_unita_estratta (unita_immobiliare_estratta);
ALTER TABLE movimenti_bancari ADD INDEX idx_avviso_numero (avviso_pagamento_numero);
ALTER TABLE movimenti_bancari ADD INDEX idx_rata_tipologia (rata_tipologia);
ALTER TABLE movimenti_bancari ADD INDEX idx_anno_competenza (anno_competenza);
ALTER TABLE movimenti_bancari ADD INDEX idx_codice_disposizione (codice_disposizione_banca);
ALTER TABLE movimenti_bancari ADD INDEX idx_scala_interno (scala_estratta, interno_estratto);
```
## 🚀 **VANTAGGI SISTEMA RICONOSCIMENTO**
**Riconoscimento Automatico**: Estrazione automatica unità immobiliari, rate, periodi
**Pattern Configurabili**: Aggiunta nuove banche senza modifiche codice
**Dati Reali**: Basato su analisi movimenti bancari effettivi UNICREDIT
**Debug Completo**: Log dettagliato per miglioramento continuo
**Multi-Banca**: Supporto pattern specifici per ogni banca
**Confidence Score**: Valutazione affidabilità riconoscimento
**Matching Intelligente**: Collegamento automatico pagamenti-unità immobiliari
Il sistema potrà **automaticamente collegare** i pagamenti alle unità immobiliari e ai condomini corrispondenti, facilitando enormemente la riconciliazione contabile.
**Ready per analizzare altre banche**: Sistema pronto per espandere i pattern con dati di BNL, Intesa Sanpaolo, Banca Mediolanum, ecc.
---
## 🏦 **PATTERN INTESA SANPAOLO IDENTIFICATI**
### **📊 ANALISI DATI REALI INTESA SANPAOLO**
Dall'analisi dei movimenti bancari reali di **INTESA SANPAOLO** (Condominio Via Andrea Doria 36) sono emersi pattern molto specifici e diversi da UNICREDIT:
**Esempi movimenti reali analizzati:**
- `ACCREDITO BEU CON CONTABILE;218,00;;COD. DISP.: 0124010543443092 CASH Ordinaria provvisoria Gen Marz 2024 Severi via Andrea Doria scala B int 10`
- `ACCREDITO BEU CON CONTABILE;499,03;;COD. DISP.: 0124011053312441 CASH Condominio come da avviso 0007-0018/2024 Rata 2 straord lavori cortile e ord provvisori gen-mar 2024 - Coppari int 10-11`
- `BONIFICO IN EURO VERSO UE/SEPA CANALE TELEM.;;-1003,78;0124010847939613 20240108-EQhkS8okdafsMZcan3hejJYXLT Bonifico da Voi disposto a favore di: UNIPOLSAI ASSICURAZIONI SPA Saldo ft. Polizza del 08/01/24`
### **🎯 PATTERN SPECIFICI INTESA SANPAOLO**
```sql
-- Pattern INTESA SANPAOLO basati su dati reali Condominio Via Andrea Doria 36
INSERT INTO movimenti_bancari_pattern (nome_pattern, banca_abi, regex_descrizione, campo_estratto, regex_estrazione, tipo_movimento_target, categoria_target, priorita) VALUES
-- ENTRATE CONDOMINI (ACCREDITO BEU CON CONTABILE)
('INTESA - Accredito BEU', '03069', 'ACCREDITO BEU CON CONTABILE', 'tipo_movimento', NULL, 'ENTRATA', 'INCASSO_CONDOMINIO', 10),
-- CODICE DISPOSIZIONE (unico per ogni transazione)
('INTESA - Codice Disposizione', '03069', 'COD\\. DISP\\.: (\\d+)', 'codice_disposizione_banca', 'COD\\. DISP\\.: (\\d+)', 'ENTRATA', NULL, 15),
-- UNITÀ IMMOBILIARI (pattern specifici Intesa)
('INTESA - Scala Int Format', '03069', 'scala\\s+([A-D])\\s+int\\s+(\\d+)', 'scala_interno', 'scala\\s+([A-D])\\s+int\\s+(\\d+)', 'ENTRATA', 'QUOTA_CONDOMINIALE', 20),
('INTESA - Int Format', '03069', '\\bint\\s+([a-d]?\\d+)', 'interno_estratto', '\\bint\\s+([a-d]?\\d+)', 'ENTRATA', 'QUOTA_CONDOMINIALE', 21),
('INTESA - Appartamento Format', '03069', 'APPARTAMENTO\\s+([A-D]\\d+)', 'unita_immobiliare_estratta', 'APPARTAMENTO\\s+([A-D]\\d+)', 'ENTRATA', 'QUOTA_CONDOMINIALE', 22),
('INTESA - Int Bis Format', '03069', 'interno\\s+(\\d+\\s*bis)', 'interno_estratto', 'interno\\s+(\\d+\\s*bis)', 'ENTRATA', 'QUOTA_CONDOMINIALE', 23),
-- MITTENTI (estrazione automatica da campo MITT.)
('INTESA - Mittente', '03069', 'MITT\\.: ([^B]+)\\s+BENEF\\.:?', 'controparte_nome', 'MITT\\.: ([^B]+)\\s+BENEF\\.:?', 'ENTRATA', 'QUOTA_CONDOMINIALE', 25),
-- AVVISI PAGAMENTO (formato specifico Intesa)
('INTESA - Avviso Completo', '03069', 'avviso\\s+(\\d{4})[-/](\\d{4})/(\\d{4})', 'avviso_pagamento_numero', 'avviso\\s+(\\d{4})[-/](\\d{4})/(\\d{4})', 'ENTRATA', 'QUOTA_CONDOMINIALE', 30),
('INTESA - Avviso Semplice', '03069', 'AVVISO\\s+DI\\s+PAGAMENTO\\s+(\\d+)', 'avviso_pagamento_numero', 'AVVISO\\s+DI\\s+PAGAMENTO\\s+(\\d+)', 'ENTRATA', 'QUOTA_CONDOMINIALE', 31),
-- PERIODI E RATE (molto specifici per Intesa)
('INTESA - Periodo Gen Mar', '03069', '(gennaio|gen)\\s*[-/]?\\s*(marzo|mar)\\s*20(\\d{2})', 'periodo_riferimento', '(gennaio|gen)\\s*[-/]?\\s*(marzo|mar)\\s*20(\\d{2})', 'ENTRATA', 'QUOTA_CONDOMINIALE', 40),
('INTESA - Periodo Apr Giu', '03069', '(aprile|apr)\\s*[-/]?\\s*(giugno|giu)\\s*20(\\d{2})', 'periodo_riferimento', '(aprile|apr)\\s*[-/]?\\s*(giugno|giu)\\s*20(\\d{2})', 'ENTRATA', 'QUOTA_CONDOMINIALE', 40),
('INTESA - Periodo Ott Dic', '03069', '(ottobre|ott)\\s*[-/]?\\s*(dicembre|dic)\\s*20(\\d{2})', 'periodo_riferimento', '(ottobre|ott)\\s*[-/]?\\s*(dicembre|dic)\\s*20(\\d{2})', 'ENTRATA', 'QUOTA_CONDOMINIALE', 40),
-- TIPOLOGIE SPECIFICHE INTESA
('INTESA - Ordinaria Provvisoria', '03069', '(ordinaria|ord)\\s+(provvisoria|provv)', 'rata_tipologia', NULL, 'ENTRATA', 'QUOTA_ORDINARIA_PROVVISORIA', 45),
('INTESA - Straordinaria Lavori', '03069', '(straordinaria|straord)\\s+lavori', 'rata_tipologia', NULL, 'ENTRATA', 'QUOTA_STRAORDINARIA_LAVORI', 45),
('INTESA - Lavori Cortile', '03069', 'lavori\\s+cortile', 'rata_tipologia', NULL, 'ENTRATA', 'QUOTA_LAVORI_CORTILE', 46),
('INTESA - Level One', '03069', 'LEVEL\\s*ONE|LEVELONE', 'rata_tipologia', NULL, 'ENTRATA', 'QUOTA_LEVELONE', 47),
-- NUMERO RATA (specifico Intesa)
('INTESA - Prima Rata', '03069', '\\b1\\s*RATA|prima\\s+rata|1\\s*di\\s*\\d+', 'rata_numero', '1', 'ENTRATA', 'QUOTA_CONDOMINIALE', 50),
('INTESA - Seconda Rata', '03069', '\\b2\\s*RATA|seconda\\s+rata|2\\s*di\\s*\\d+|2A\\s*RATA', 'rata_numero', '2', 'ENTRATA', 'QUOTA_CONDOMINIALE', 50),
('INTESA - Terza Rata', '03069', '\\b3\\s*RATA|terza\\s+rata|3\\s*di\\s*\\d+|3A\\s*RATA', 'rata_numero', '3', 'ENTRATA', 'QUOTA_CONDOMINIALE', 50),
('INTESA - Quarta Rata', '03069', '\\b4\\s*RATA|quarta\\s+rata|4\\s*di\\s*\\d+', 'rata_numero', '4', 'ENTRATA', 'QUOTA_CONDOMINIALE', 50),
-- ANNI COMPETENZA
('INTESA - Anno 2024', '03069', '\\b2024\\b', 'anno_competenza', '2024', 'ENTRATA', 'QUOTA_CONDOMINIALE', 60),
('INTESA - Anno 2023', '03069', '\\b2023\\b', 'anno_competenza', '2023', 'ENTRATA', 'QUOTA_CONDOMINIALE', 60),
('INTESA - Anno 2025', '03069', '\\b2025\\b', 'anno_competenza', '2025', 'ENTRATA', 'QUOTA_CONDOMINIALE', 60),
-- SPESE USCITE (BONIFICO IN EURO VERSO UE/SEPA)
('INTESA - Bonifico Uscita', '03069', 'BONIFICO IN EURO VERSO UE/SEPA', 'tipo_movimento', NULL, 'USCITA', 'PAGAMENTO_FORNITORE', 70),
('INTESA - Disposizione Bonifico', '03069', 'DISPOSIZIONE DI BONIFICO', 'tipo_movimento', NULL, 'USCITA', 'PAGAMENTO_FORNITORE', 71),
-- FATTURE (pattern specifico Intesa)
('INTESA - Saldo Fattura', '03069', 'Saldo\\s+ft\\.?\\s*([A-Z0-9/-]+)', 'riferimento_fattura', 'Saldo\\s+ft\\.?\\s*([A-Z0-9/-]+)', 'USCITA', 'PAGAMENTO_FATTURA', 75),
('INTESA - Saldo FT N', '03069', 'SALDO\\s+FT\\s+N\\.?\\s*([A-Z0-9/-]+)', 'riferimento_fattura', 'SALDO\\s+FT\\s+N\\.?\\s*([A-Z0-9/-]+)', 'USCITA', 'PAGAMENTO_FATTURA', 76),
-- POLIZZE E ASSICURAZIONI
('INTESA - Saldo Polizza', '03069', 'Saldo\\s+(Polizza|polizza)\\s*(\\d+)', 'riferimento_polizza', 'Saldo\\s+(Polizza|polizza)\\s*(\\d+)', 'USCITA', 'PAGAMENTO_ASSICURAZIONE', 80),
-- COMMISSIONI E SPESE (specifiche Intesa)
('INTESA - Costo Bonifico', '03069', 'COSTO PER BONIFICO', 'tipo_movimento', NULL, 'COMMISSIONE', 'COMMISSIONE_BONIFICO', 85),
('INTESA - Commissione Bonifico', '03069', 'COMMISSIONE.*BONIFICO', 'tipo_movimento', NULL, 'COMMISSIONE', 'COMMISSIONE_BONIFICO', 86),
('INTESA - Canone Fisso', '03069', 'CANONE FISSO MENSILE', 'tipo_movimento', NULL, 'COMMISSIONE', 'CANONE_BANCARIO', 87),
('INTESA - Canone Carta', '03069', 'CANONE CARTA DI DEBITO', 'tipo_movimento', NULL, 'COMMISSIONE', 'CANONE_CARTA', 88),
('INTESA - Imposta Bollo', '03069', 'IMPOSTA DI BOLLO', 'tipo_movimento', NULL, 'COMMISSIONE', 'IMPOSTA_BOLLO', 89),
-- F24 E TASSE
('INTESA - F24', '03069', 'PAGAMENTO.*F24|DELEGHE F24', 'tipo_movimento', NULL, 'USCITA', 'PAGAMENTO_TASSE', 90),
-- BONIFICO MYBANK (specifico Intesa)
('INTESA - MyBank', '03069', 'BONIFICO MYBANK', 'tipo_movimento', NULL, 'USCITA', 'PAGAMENTO_UTENZE', 91),
('INTESA - Commissione MyBank', '03069', 'COMMISSIONE BONIFICO MYBANK', 'tipo_movimento', NULL, 'COMMISSIONE', 'COMMISSIONE_MYBANK', 92),
-- BOLLETTINO POSTALE
('INTESA - Bollettino Postale', '03069', 'PAGAMENTO BOLLETTINO POSTALE', 'tipo_movimento', NULL, 'USCITA', 'PAGAMENTO_BOLLETTINO', 95),
('INTESA - Commissione Bollettino', '03069', 'COMMISSIONE PAGAMENTO BOLLETTINO', 'tipo_movimento', NULL, 'COMMISSIONE', 'COMMISSIONE_BOLLETTINO', 96);
```
### **🔄 ESEMPIO RICONOSCIMENTO INTESA**
**Movimento di esempio:**
```
ACCREDITO BEU CON CONTABILE;218,00;;COD. DISP.: 0124010543443092 CASH Ordinaria provvisoria Gen Marz 2024 Severi via Andrea Doria scala B int 10 Bonifico a Vostro favore disposto da: MITT.: Renata Severi BENEF.: Condominio Via Andrea Doria 36 BIC. ORD.: INGBITD1
```
**Dati estratti automaticamente:**
- `codice_disposizione_banca`: "0124010543443092"
- `scala_estratta`: "B"
- `interno_estratto`: "10"
- `controparte_nome`: "Renata Severi"
- `rata_tipologia`: "ORDINARIA_PROVVISORIA"
- `periodo_riferimento`: "Gen Marz 2024"
- `anno_competenza`: "2024"
- `tipo_movimento`: "ENTRATA"
- `categoria_movimento`: "QUOTA_ORDINARIA_PROVVISORIA"
### **🆚 CONFRONTO UNICREDIT vs INTESA**
| **Caratteristica** | **UNICREDIT** | **INTESA SANPAOLO** |
|-------------------|---------------|-------------------|
| **Formato CSV** | `;` separato, causale finale | `;` separato, descrizione estesa |
| **Codice Operazione** | Causale numerica (048, 208) | Tipo operazione testuale |
| **Unità Immobiliari** | B-1, B/8, C4, A26 | scala B int 10, APPARTAMENTO B04 |
| **Mittente** | Nel campo descrizione | Campo specifico MITT.: |
| **Codice Transazione** | TRN finale | COD. DISP.: iniziale |
| **Rate** | 1 RATA, prima rata | 1 di 4, prima rata, 2A RATA |
| **Periodi** | 22-23, 23-24 | gennaio marzo 2024, gen-mar |
| **Commissioni** | Integrate nella transazione | Righe separate COSTO PER |
## 🚀 **VANTAGGI SISTEMA MULTI-BANCA**
**Pattern Specifici**: Ogni banca ha i suoi pattern personalizzati
**Riconoscimento Avanzato**: Estrazione dati molto più dettagliata
**Codici Operazione**: Supporto sia causali numeriche che testuali
**Flessibilità Formato**: Gestione CSV diversi per ogni banca
**Unità Immobiliari**: Riconoscimento format diversi (B-1 vs scala B int 10)
**Confidence Scoring**: Valutazione qualità match per ogni banca
Il sistema ora riconosce automaticamente **UNICREDIT** e **INTESA SANPAOLO** con pattern specifici per ogni banca! 🏦✨
## 📊 **RIEPILOGO ANALISI INTESA SANPAOLO**
### **Dati Analizzati:**
- File: Movimenti_Conto_05082025.csv
- Condominio: Via Andrea Doria 36
- Periodo: 01/01/2024 - 05/08/2025
- Totale movimenti: 444 operazioni
- Saldo finale: 10.230,57 EUR
### **Pattern Identificati:**
**35 nuovi pattern** specifici per Intesa Sanpaolo
**3 nuovi campi** nella tabella principale
**Supporto multi-formato** CSV per diverse banche
**Riconoscimento avanzato** unità immobiliari (scala B int 10)
**Estrazione automatica** mittenti, fatture, polizze
Il sistema ora supporta **UNICREDIT** e **INTESA SANPAOLO** con riconoscimento automatico completo! 🎯