1596 lines
68 KiB
Markdown
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! 🎯
|
|
|
|
|