netgescon-master/docs/specifiche/DATA_ARCHITECTURE.md

624 lines
19 KiB
Markdown

# DATA_ARCHITECTURE.md - NetGesCon Laravel
**Creato**: 8 Luglio 2025
**Ultimo aggiornamento**: 8 Luglio 2025
**Versione**: 1.0
## 🎯 **SCOPO DEL DOCUMENTO**
Documentazione dell'architettura dati, flussi applicativi, modelli Eloquent e relazioni per facilitare lo sviluppo e la manutenzione del sistema.
---
## 🏗️ **ARCHITETTURA APPLICATIVA**
### 📂 **STRUTTURA DIRECTORY MODELS**
```
app/Models/
├── User.php # Laravel standard + amministratore_id
├── Amministratore.php # Master gestori condomini
├── Stabile.php # Edifici/Condomini
├── UnitaImmobiliare.php # Singole unità immobiliari
├── TipoUtilizzo.php # Tipologie utilizzo (Abitazione, Garage, etc.)
├── TabellaMillesimale.php # Tabelle millesimali per ripartizione
├── AnagraficaCondominiale.php # Anagrafica unificata (persone fisiche/giuridiche)
├── ContattoAnagrafica.php # Contatti multipli per anagrafica
├── DirittoReale.php # Quote proprietà su unità
├── ContrattoLocazione.php # Contratti affitto/locazione
├── RipartizioneSpeseInquilini.php # Configurazione spese inquilino/proprietario
├── VoceSpesa.php # Voci di spesa condominiali
├── RipartizioneSpese.php # Ripartizione spese per stabile
├── DettaglioRipartizioneSpese.php # Dettaglio ripartizione per unità
├── PianoRateizzazione.php # Piani di rateizzazione
├── Rata.php # Singole rate di pagamento
├── MovimentoContabile.php # Gestione contabilità
└── Allegato.php # Documenti allegati
```
### 🔄 **FLUSSI DATI PRINCIPALI**
#### **FLUSSO CREAZIONE ANAGRAFICA COMPLETA**:
```
1. Crea/Seleziona Amministratore
2. Crea/Seleziona Stabile
3. Crea Unità Immobiliare (con millesimi)
4. Crea Anagrafica Condominiale
5. Aggiunge Contatti (email, telefoni, etc.)
6. Crea Diritti Reali (quota proprietà)
7. Eventuale Contratto Locazione (se affittata)
```
#### **FLUSSO RIPARTIZIONE SPESE**:
```
1. Definisce Voce di Spesa (con tabella millesimale default)
2. Crea Ripartizione Spese per importo totale
3. Calcola Dettaglio Ripartizione per ogni unità
4. Genera Piano Rateizzazione (se necessario)
5. Crea Rate individuali con scadenze
6. Monitora pagamenti e solleciti
```
#### **FLUSSO GESTIONE LOCAZIONI**:
```
1. Identifica Unità da locare
2. Verifica Diritti Reali del Proprietario
3. Crea Anagrafica Inquilino (se non esiste)
4. Crea Contratto Locazione (locatore ↔ conduttore)
5. Configura Ripartizione Spese secondo Confedilizia
6. Setup automatico rinnovi e aggiornamenti ISTAT
```
#### **FLUSSO CONTABILE**:
```
1. Registra Movimento (entrata/uscita)
2. Collega a Stabile e Categoria
3. Eventuale collegamento ad Anagrafica (fornitore/cliente)
4. Allega documenti (fatture, ricevute)
5. Workflow: bozza → confermato → registrato
6. Export prima nota / definitivo
```
---
## 🔗 **MODELLI ELOQUENT E RELAZIONI**
### **`User.php`** - Gestione autenticazione
```php
// Relazioni:
public function amministratore() {
return $this->belongsTo(Amministratore::class);
}
// Scopes:
public function scopeAdmin($query) {
return $query->where('role', 'admin');
}
public function scopeSuperAdmin($query) {
return $query->where('role', 'super-admin');
}
// Accessors:
public function getFullNameAttribute() {
return $this->name;
}
```
### **`Amministratore.php`** - Master amministratori
```php
// Relazioni:
public function stabili() {
return $this->hasMany(Stabile::class);
}
public function users() {
return $this->hasMany(User::class);
}
public function anagrafiche() {
return $this->hasMany(AnagraficaCondominiale::class);
}
// Scopes:
public function scopeAttivi($query) {
return $query->whereNull('deleted_at');
}
public function scopeDistribuiti($query) {
return $query->where('stato_sincronizzazione', 'distribuito');
}
// Mutators & Accessors:
public function getNomeCompletoAttribute() {
return $this->nome_amministratore . ' ' . $this->cognome_amministratore;
}
protected function setCodiceFiscaleAttribute($value) {
$this->attributes['codice_fiscale'] = strtoupper($value);
}
```
### **`Stabile.php`** - Edifici/Condomini
```php
// Relazioni:
public function amministratore() {
return $this->belongsTo(Amministratore::class);
}
public function unitaImmobiliari() {
return $this->hasMany(UnitaImmobiliare::class);
}
public function movimentiContabili() {
return $this->hasMany(MovimentoContabile::class);
}
// Scopes:
public function scopeConAscensore($query) {
return $query->where('ascensore', true);
}
public function scopePerCitta($query, $citta) {
return $query->where('citta', $citta);
}
// Accessors:
public function getIndirizzoCompletoAttribute() {
return $this->indirizzo . ', ' . $this->cap . ' ' . $this->citta . ' (' . $this->provincia . ')';
}
public function getTotalePostiAutoAttribute() {
return $this->posti_auto_coperti + $this->posti_auto_scoperti;
}
```
### **`UnitaImmobiliare.php`** - Unità immobiliari
```php
// Relazioni:
public function stabile() {
return $this->belongsTo(Stabile::class);
}
public function tipoUtilizzo() {
return $this->belongsTo(TipoUtilizzo::class);
}
public function dirittiReali() {
return $this->hasMany(DirittoReale::class);
}
public function contrattiLocazione() {
return $this->hasMany(ContrattoLocazione::class);
}
// Relazioni avanzate:
public function proprietari() {
return $this->belongsToMany(AnagraficaCondominiale::class, 'diritti_reali')
->withPivot('percentuale_proprieta', 'tipo_diritto')
->wherePivot('attivo', true);
}
public function inquiliniAttuali() {
return $this->hasMany(ContrattoLocazione::class)
->where('stato_contratto', 'attivo')
->with('conduttore');
}
// Scopes:
public function scopeLibere($query) {
return $query->where('stato_occupazione', 'libera');
}
public function scopeInAffitto($query) {
return $query->where('stato_occupazione', 'in_affitto');
}
// Accessors:
public function getDenominazioneCompletaAttribute() {
return $this->denominazione . ' - Piano ' . $this->piano;
}
public function getTotaleMillesimiAttribute() {
return $this->millesimi_proprieta + $this->millesimi_scale +
$this->millesimi_ascensore + $this->millesimi_riscaldamento;
}
```
### **`AnagraficaCondominiale.php`** - Anagrafica unificata
```php
// Relazioni:
public function amministratore() {
return $this->belongsTo(Amministratore::class);
}
public function contatti() {
return $this->hasMany(ContattoAnagrafica::class, 'anagrafica_id');
}
public function dirittiReali() {
return $this->hasMany(DirittoReale::class, 'anagrafica_id');
}
public function contrattiComeLocatore() {
return $this->hasMany(ContrattoLocazione::class, 'locatore_id');
}
public function contrattiComeConduttore() {
return $this->hasMany(ContrattoLocazione::class, 'conduttore_id');
}
// Relazioni avanzate:
public function unitaPossedute() {
return $this->belongsToMany(UnitaImmobiliare::class, 'diritti_reali')
->withPivot('percentuale_proprieta', 'tipo_diritto')
->wherePivot('attivo', true);
}
public function unitaInLocazione() {
return $this->hasMany(ContrattoLocazione::class, 'conduttore_id')
->where('stato_contratto', 'attivo')
->with('unitaImmobiliare');
}
// Scopes:
public function scopePersoneFisiche($query) {
return $query->where('tipo_soggetto', 'persona_fisica');
}
public function scopePersoneGiuridiche($query) {
return $query->where('tipo_soggetto', 'persona_giuridica');
}
public function scopeProprietari($query) {
return $query->whereHas('dirittiReali', function($q) {
$q->where('attivo', true)->where('tipo_diritto', 'proprieta');
});
}
public function scopeInquilini($query) {
return $query->whereHas('contrattiComeConduttore', function($q) {
$q->where('stato_contratto', 'attivo');
});
}
// Accessors:
public function getNomeCompletoAttribute() {
return $this->tipo_soggetto === 'persona_fisica'
? $this->nome . ' ' . $this->cognome
: $this->ragione_sociale;
}
public function getIndirizzoCompletoAttribute() {
$indirizzo = $this->indirizzo_residenza ?: $this->indirizzo_domicilio;
$cap = $this->cap_residenza ?: $this->cap_domicilio;
$citta = $this->citta_residenza ?: $this->citta_domicilio;
$provincia = $this->provincia_residenza ?: $this->provincia_domicilio;
return $indirizzo . ', ' . $cap . ' ' . $citta . ' (' . $provincia . ')';
}
// Mutators:
protected function setCodiceFiscaleAttribute($value) {
$this->attributes['codice_fiscale'] = strtoupper($value);
}
protected function setPartitaIvaAttribute($value) {
$this->attributes['partita_iva'] = preg_replace('/[^0-9]/', '', $value);
}
```
### **`DirittoReale.php`** - Quote proprietà
```php
// Relazioni:
public function unitaImmobiliare() {
return $this->belongsTo(UnitaImmobiliare::class);
}
public function anagrafica() {
return $this->belongsTo(AnagraficaCondominiale::class, 'anagrafica_id');
}
// Scopes:
public function scopeAttivi($query) {
return $query->where('attivo', true)
->where(function($q) {
$q->whereNull('data_fine_validita')
->orWhere('data_fine_validita', '>=', now());
});
}
public function scopeProprieta($query) {
return $query->where('tipo_diritto', 'proprieta');
}
// Accessors:
public function getQuotaFormattataAttribute() {
return $this->quota_numeratore . '/' . $this->quota_denominatore;
}
public function getPercentualeFormattataAttribute() {
return number_format($this->percentuale_proprieta, 2) . '%';
}
// Mutators:
protected function setPercentualeProprietaAttribute($value) {
$this->attributes['percentuale_proprieta'] = round($value, 2);
// Auto-calcolo quota se percentuale impostata
$this->attributes['quota_numeratore'] = round($value * 10);
$this->attributes['quota_denominatore'] = 1000;
}
```
### **`VoceSpesa.php`** - Voci di spesa condominiali
```php
// Relazioni:
public function stabile() {
return $this->belongsTo(Stabile::class);
}
public function tabellaMillesimaleDefault() {
return $this->belongsTo(TabellaMillesimale::class, 'tabella_millesimale_default_id');
}
public function ripartizioniSpese() {
return $this->hasMany(RipartizioneSpese::class);
}
// Scopes:
public function scopeAttive($query) {
return $query->where('attiva', true);
}
public function scopePerTipo($query, $tipo) {
return $query->where('tipo_gestione', $tipo);
}
public function scopePerCategoria($query, $categoria) {
return $query->where('categoria', $categoria);
}
// Accessors:
public function getCodiceCompletoAttribute() {
return $this->codice . ' - ' . $this->descrizione;
}
public function getHasRitenutoAttribute() {
return !is_null($this->ritenuta_acconto_default) && $this->ritenuta_acconto_default > 0;
}
```
### **`RipartizioneSpese.php`** - Ripartizione spese per stabile
```php
// Relazioni:
public function voceSpesa() {
return $this->belongsTo(VoceSpesa::class);
}
public function stabile() {
return $this->belongsTo(Stabile::class);
}
public function tabellaMillesimale() {
return $this->belongsTo(TabellaMillesimale::class);
}
public function dettagliRipartizione() {
return $this->hasMany(DettaglioRipartizioneSpese::class);
}
public function pianiRateizzazione() {
return $this->hasMany(PianoRateizzazione::class);
}
public function creatoDa() {
return $this->belongsTo(User::class, 'creato_da');
}
// Scopes:
public function scopePerStato($query, $stato) {
return $query->where('stato', $stato);
}
public function scopePerAnno($query, $anno) {
return $query->whereYear('data_ripartizione', $anno);
}
public function scopeApprovate($query) {
return $query->where('stato', 'approvata');
}
// Accessors:
public function getImportoResiduoAttribute() {
return $this->importo_totale - $this->importo_ripartito;
}
public function getPercentualeRipartizioneAttribute() {
return $this->importo_totale > 0 ?
($this->importo_ripartito / $this->importo_totale) * 100 : 0;
}
```
### **`DettaglioRipartizioneSpese.php`** - Dettaglio ripartizione per unità
```php
// Relazioni:
public function ripartizioneSpese() {
return $this->belongsTo(RipartizioneSpese::class);
}
public function unitaImmobiliare() {
return $this->belongsTo(UnitaImmobiliare::class);
}
public function anagraficaCondominiale() {
return $this->belongsTo(AnagraficaCondominiale::class);
}
// Scopes:
public function scopeConRettifica($query) {
return $query->whereNotNull('importo_rettificato');
}
public function scopePerUnita($query, $unitaId) {
return $query->where('unita_immobiliare_id', $unitaId);
}
// Accessors:
public function getImportoFinaleAttribute() {
return $this->importo_rettificato ?? $this->importo_calcolato;
}
public function getHasRettificaAttribute() {
return !is_null($this->importo_rettificato);
}
```
### **`PianoRateizzazione.php`** - Piani di rateizzazione
```php
// Relazioni:
public function ripartizione() {
return $this->belongsTo(RipartizioneSpese::class, 'ripartizione_spese_id');
}
public function stabile() {
return $this->belongsTo(Stabile::class);
}
public function rate() {
return $this->hasMany(Rata::class, 'piano_rateizzazione_id');
}
public function creatoDa() {
return $this->belongsTo(User::class, 'creato_da');
}
// Scopes:
public function scopeAttivi($query) {
return $query->where('stato', 'attivo');
}
public function scopePerFrequenza($query, $frequenza) {
return $query->where('frequenza', $frequenza);
}
// Accessors:
public function getImportoRataBaseAttribute() {
return $this->numero_rate > 0 ?
round($this->importo_totale / $this->numero_rate, 2) : 0;
}
public function getDataUltimaRataAttribute() {
return Carbon::parse($this->data_prima_rata)
->addMonths(($this->numero_rate - 1) * $this->getIntervalloMesi());
}
```
### **`Rata.php`** - Singole rate di pagamento
```php
// Relazioni:
public function pianoRateizzazione() {
return $this->belongsTo(PianoRateizzazione::class);
}
public function ripartizione() {
return $this->belongsTo(RipartizioneSpese::class, 'ripartizione_spese_id');
}
public function registratoDa() {
return $this->belongsTo(User::class, 'registrato_da');
}
// Scopes:
public function scopeScadute($query) {
return $query->where('data_scadenza', '<', now())
->where('stato', 'attiva');
}
public function scopeInScadenza($query, $giorni = 30) {
return $query->whereBetween('data_scadenza', [now(), now()->addDays($giorni)])
->where('stato', 'attiva');
}
public function scopePagate($query) {
return $query->where('stato', 'pagata');
}
// Accessors:
public function getImportoResiduoAttribute() {
return $this->importo_rata - ($this->importo_pagato ?? 0);
}
public function getGiorniRitardoAttribute() {
return $this->data_scadenza < now() ?
now()->diffInDays($this->data_scadenza) : 0;
}
public function getStatoScadenzaAttribute() {
if ($this->stato === 'pagata') return 'pagata';
if ($this->data_scadenza < now()) return 'scaduto';
if ($this->data_scadenza <= now()->addDays(7)) return 'in_scadenza';
return 'attiva';
}
```
### **`ContrattoLocazione.php`** - Gestione affitti
```php
// Relazioni:
public function unitaImmobiliare() {
return $this->belongsTo(UnitaImmobiliare::class);
}
public function locatore() {
return $this->belongsTo(AnagraficaCondominiale::class, 'locatore_id');
}
public function conduttore() {
return $this->belongsTo(AnagraficaCondominiale::class, 'conduttore_id');
}
// Scopes:
public function scopeAttivi($query) {
return $query->where('stato_contratto', 'attivo');
}
public function scopeInScadenza($query, $giorni = 60) {
return $query->where('data_fine', '<=', now()->addDays($giorni))
->where('stato_contratto', 'attivo');
}
public function scopePerTipo($query, $tipo) {
return $query->where('tipo_contratto', $tipo);
}
// Accessors:
public function getDurataFormattataAttribute() {
if ($this->data_fine) {
$mesi = $this->data_inizio->diffInMonths($this->data_fine);
return $mesi . ' mesi';
}
return 'Indeterminato';
}
public function getCanoneTotaleAttribute() {
return $this->canone_mensile + $this->spese_condominiali;
}
public function getStatoScadenzaAttribute() {
if (!$this->data_fine) return 'indeterminato';
$giorni = now()->diffInDays($this->data_fine, false);
if ($giorni < 0) return 'scaduto';
if ($giorni <= 30) return 'in_scadenza';
if ($giorni <= 60) return 'prossimo_scadenza';
return 'attivo';
}
// Mutators:
protected function setCanoneMensileAttribute($value) {
$this->attributes['canone_mensile'] = round($value, 2);
}
```
---
## 🚀 **FACTORY E SEEDER**
### **Factory Standard**:
```php
// AnagraficaCondominiale Factory
public function definition() {
return [
'amministratore_id' => Amministratore::factory(),
'codice_anagrafica' => $this->generateCodice('ANA'),
'tipo_soggetto' => $this->faker->randomElement(['persona_fisica', 'persona_giuridica']),
'nome' => $this->faker->firstName(),
'cognome' => $this->faker->lastName(),
'codice_fiscale' => $this->faker->regexify('[A-Z]{6}[0-9]{2}[A-Z][0-9]{2}[A-Z][0-9]{3}[A-Z]'),
'indirizzo_residenza' => $this->faker->streetAddress(),
'cap_residenza' => $this->faker->postcode(),
'citta_residenza' => $this->faker->city(),
'provincia_residenza' => $this->faker->stateAbbr(),
'attivo' => true,
];
}
// Stato specifici
public function personaFisica() {
return $this->state(['tipo_soggetto' => 'persona_fisica']);
}
public function personaGiuridica() {
return $this->state([
'tipo_soggetto' => 'persona_giuridica',
'ragione_sociale' => $this->faker->company(),
'partita_iva' => $this->faker->numerify('###########'),
]);
}
```
### **Seeder Completi**:
```php
// DatabaseSeeder.php
public function run() {
$this->call([
TipiUtilizzoSeeder::class, # Tipologie base
RipartizioneSpeseSeeder::class, # Configurazione Confedilizia
AmministratoriSeeder::class, # Dati base amministratori
StabiliSeeder::class, # Edifici esempio
UnitaImmobiliariSeeder::class, # Unità complete
AnagraficaSeeder::class, # Persone fisiche/giuridiche
DirittiRealiSeeder::class, # Quote proprietà
ContrattiLocazioneSeeder::class, # Affitti attivi
MovimentiContabiliSeeder::class, # Prima nota esempio
]);
}
```
---
## 🎯 **RIFERIMENTI INCROCIATI**
- **DATABASE_SCHEMA.md**: ↗️ Schema SQL completo, relazioni, convenzioni
- **PROGRESS_LOG.md**: ↗️ Storico implementazione, errori risolti, test
- **API_ENDPOINTS.md**: *(DA CREARE)* ↗️ Endpoint REST per CRUD operazioni
- **UI_COMPONENTS.md**: *(DA CREARE)* ↗️ Componenti UI e binding modelli
- **DEVELOPMENT_IDEAS.md**: *(DA CREARE)* ↗️ Feature avanzate, ottimizzazioni
---
*Documento creato: 8 Luglio 2025 - Guida architetttura e modelli Eloquent*