netgescon-master/docs/10-IMPLEMENTAZIONE-CONTABILITA-PARTITA-DOPPIA-GESTIONI.md
Michele Windows e68ee85a18 🚀 CHECKPOINT STABILE - Sistema Contabile Avanzato
📋 AGGIUNTE PRINCIPALI:
- Sistema contabile partita doppia con gestioni multiple
- Documentazione implementazione completa
- Models Laravel: GestioneContabile, MovimentoPartitaDoppia
- Controller ContabilitaAvanzataController
- Migration sistema contabile completo
- Scripts automazione e trasferimento
- Manuali utente e checklist implementazione

📊 FILES PRINCIPALI:
- docs/10-IMPLEMENTAZIONE-CONTABILITA-PARTITA-DOPPIA-GESTIONI.md
- SPECIFICHE-SISTEMA-CONTABILE-COMPLETO.md
- netgescon-laravel/database/migrations/2025_07_20_100000_create_complete_accounting_system.php
- netgescon-laravel/app/Models/GestioneContabile.php

 CHECKPOINT SICURO PER ROLLBACK
2025-07-26 15:11:19 +02:00

23 KiB
Raw Blame History

💰 IMPLEMENTAZIONE SISTEMA CONTABILE PARTITA DOPPIA CON GESTIONI MULTIPLE

📋 OVERVIEW IMPLEMENTAZIONE

Sistema contabile condominiale in partita doppia con gestioni multiple (ORDINARIA, RISCALDAMENTO, STRAORDINARIA) integrate con chiusura e riapertura contabile classica.


🎯 PRINCIPI FONDAMENTALI

📅 Gestioni Multiple per Condominio

🏢 CONDOMINIO
   ├── 📊 GESTIONE ORDINARIA (spese comuni)
   ├── 🔥 GESTIONE RISCALDAMENTO (spese termiche)
   └── ⚡ GESTIONE STRAORDINARIA (lavori straordinari)

💎 Partita Doppia con Gestioni

-- Ogni movimento contabile è collegato a:
registrazione_contabile {
    gestione_id,        -- FK alla tabella gestioni
    tipo_gestione,      -- ENUM('ORDINARIA','RISCALDAMENTO','STRAORDINARIA')
    dare_totale,        -- Totale DARE
    avere_totale        -- Totale AVERE (deve essere = dare_totale)
}

🔄 Chiusura e Riapertura Contabile

  1. CHIUSURA GESTIONE: Movimenti di chiusura a Stato Patrimoniale e Conto Economico
  2. RIAPERTURA GESTIONE: Riporto saldi patrimoniali alla gestione successiva

🗃️ SCHEMA DATABASE AGGIORNATO

1 Tabella: gestioni_contabili (ESTESA)

-- AGGIORNAMENTO tabella esistente
ALTER TABLE gestioni_contabili ADD COLUMN IF NOT EXISTS tipo_gestione ENUM('ORDINARIA','RISCALDAMENTO','STRAORDINARIA') NOT NULL DEFAULT 'ORDINARIA';
ALTER TABLE gestioni_contabili ADD COLUMN IF NOT EXISTS gestione_precedente_id BIGINT UNSIGNED NULL;
ALTER TABLE gestioni_contabili ADD COLUMN IF NOT EXISTS saldi_apertura JSON NULL;
ALTER TABLE gestioni_contabili ADD COLUMN IF NOT EXISTS saldi_chiusura JSON NULL;

-- Indici aggiuntivi
ALTER TABLE gestioni_contabili ADD INDEX idx_tipo_gestione (tipo_gestione);
ALTER TABLE gestioni_contabili ADD INDEX idx_condominio_tipo (condominio_id, tipo_gestione);

2 Tabella: registrazioni_contabili (AGGIORNATA)

-- AGGIORNAMENTO per gestioni multiple
ALTER TABLE registrazioni_contabili ADD COLUMN IF NOT EXISTS tipo_gestione ENUM('ORDINARIA','RISCALDAMENTO','STRAORDINARIA') NOT NULL DEFAULT 'ORDINARIA';
ALTER TABLE registrazioni_contabili ADD COLUMN IF NOT EXISTS ritenuta_acconto DECIMAL(10,4) DEFAULT 0;
ALTER TABLE registrazioni_contabili ADD COLUMN IF NOT EXISTS percentuale_ritenuta DECIMAL(5,2) DEFAULT 0;
ALTER TABLE registrazioni_contabili ADD COLUMN IF NOT EXISTS codice_ritenuta VARCHAR(10);
ALTER TABLE registrazioni_contabili ADD COLUMN IF NOT EXISTS imponibile_ritenuta DECIMAL(12,4) DEFAULT 0;

-- Indici per performance
ALTER TABLE registrazioni_contabili ADD INDEX idx_gestione_tipo (gestione_id, tipo_gestione);
ALTER TABLE registrazioni_contabili ADD INDEX idx_ritenuta (ritenuta_acconto);

3 Nuova Tabella: ripartizioni_gestioni

CREATE TABLE ripartizioni_gestioni (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    registrazione_id BIGINT UNSIGNED NOT NULL,
    movimento_id BIGINT UNSIGNED NOT NULL,
    
    -- 🎯 COLLEGAMENTO GESTIONI
    gestione_ordinaria_perc DECIMAL(5,2) DEFAULT 0,      -- % su gestione ordinaria
    gestione_riscaldamento_perc DECIMAL(5,2) DEFAULT 0,  -- % su gestione riscaldamento  
    gestione_straordinaria_perc DECIMAL(5,2) DEFAULT 0,  -- % su gestione straordinaria
    
    -- 💰 IMPORTI RIPARTITI
    importo_ordinaria DECIMAL(12,4) DEFAULT 0,
    importo_riscaldamento DECIMAL(12,4) DEFAULT 0,
    importo_straordinaria DECIMAL(12,4) DEFAULT 0,
    
    -- 📊 TABELLE MILLESIMALI
    tabella_ordinaria VARCHAR(50) DEFAULT 'GENERALE',
    tabella_riscaldamento VARCHAR(50) DEFAULT 'RISCALDAMENTO',
    tabella_straordinaria VARCHAR(50) DEFAULT 'GENERALE',
    
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    FOREIGN KEY (registrazione_id) REFERENCES registrazioni_contabili(id) ON DELETE CASCADE,
    FOREIGN KEY (movimento_id) REFERENCES movimenti_contabili(id) ON DELETE CASCADE,
    
    INDEX idx_registrazione (registrazione_id),
    INDEX idx_movimento (movimento_id),
    
    -- CONSTRAINT: La somma delle percentuali deve essere 100%
    CONSTRAINT chk_percentuali_totale CHECK (
        gestione_ordinaria_perc + gestione_riscaldamento_perc + gestione_straordinaria_perc = 100
    )
) ENGINE=InnoDB COMMENT='Ripartizione movimenti tra gestioni multiple';

4 Nuova Tabella: chiusure_riaperture_contabili

CREATE TABLE chiusure_riaperture_contabili (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    gestione_id BIGINT UNSIGNED NOT NULL,
    
    -- 🎯 TIPO OPERAZIONE
    tipo_operazione ENUM('CHIUSURA','RIAPERTURA') NOT NULL,
    data_operazione DATE NOT NULL,
    
    -- 📊 DATI CHIUSURA/RIAPERTURA
    saldi_conti JSON NOT NULL,                    -- Saldi di tutti i conti
    totale_stato_patrimoniale DECIMAL(12,4),     -- Totale SP
    totale_conto_economico DECIMAL(12,4),        -- Totale CE
    risultato_gestione DECIMAL(12,4),            -- Utile/Perdita
    
    -- 🔗 COLLEGAMENTO RIAPERTURA
    chiusura_precedente_id BIGINT UNSIGNED NULL, -- Link alla chiusura precedente
    
    -- 📋 CONTROLLI
    confermata BOOLEAN DEFAULT FALSE,
    confermata_da BIGINT UNSIGNED,
    confermata_il TIMESTAMP NULL,
    
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    created_by BIGINT UNSIGNED NOT NULL,
    
    FOREIGN KEY (gestione_id) REFERENCES gestioni_contabili(id) ON DELETE RESTRICT,
    FOREIGN KEY (chiusura_precedente_id) REFERENCES chiusure_riaperture_contabili(id),
    FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE RESTRICT,
    
    INDEX idx_gestione_tipo (gestione_id, tipo_operazione),
    INDEX idx_data (data_operazione),
    INDEX idx_confermata (confermata)
) ENGINE=InnoDB COMMENT='Chiusure e riaperture contabili per gestione';

🎨 INTERFACCIA UTENTE - SPECIFICA SCHERMATA

📝 Maschera Registrazione Contabile Avanzata

// STRUTTURA FORM REGISTRAZIONE
[
    // 📅 SEZIONE INTESTAZIONE
    'data_operazione' => 'required|date',
    'causale' => 'required|string|max:500',
    'importo_totale' => 'required|numeric|min:0.01',
    
    // 🎯 GESTIONI (CHECKBOX MULTIPLE)
    'gestioni' => [
        'ordinaria' => ['attiva' => true, 'percentuale' => 70],
        'riscaldamento' => ['attiva' => true, 'percentuale' => 30], 
        'straordinaria' => ['attiva' => false, 'percentuale' => 0]
    ],
    
    // 💰 SEZIONE RITENUTA D'ACCONTO
    'ritenuta_section' => [
        'applica_ritenuta' => true,
        'codice_ritenuta' => 'R001', // Select da tabella ritenute
        'percentuale_ritenuta' => 20.00,
        'imponibile_ritenuta' => 1000.00,
        'importo_ritenuta' => 200.00  // Calcolato automaticamente
    ],
    
    // 📊 SEZIONE RIGHE CONTABILI (DARE/AVERE)
    'righe_contabili' => [
        [
            'sottoconto_id' => 1,
            'descrizione' => 'Pulizie scale marzo',
            'importo_dare' => 1000.00,
            'importo_avere' => 0,
            'ripartizione_gestioni' => [
                'ordinaria' => 70,
                'riscaldamento' => 30
            ]
        ],
        [
            'sottoconto_id' => 2,
            'descrizione' => 'Ritenuta d\'acconto 20%',
            'importo_dare' => 200.00,
            'importo_avere' => 0
        ],
        [
            'sottoconto_id' => 3,
            'descrizione' => 'Pagamento bonifico',
            'importo_dare' => 0,
            'importo_avere' => 1200.00
        ]
    ]
]

🎨 Layout Schermata HTML

<!-- FORM REGISTRAZIONE CONTABILE -->
<form id="registrazione-contabile-form">
    
    <!-- SEZIONE INTESTAZIONE -->
    <div class="card mb-4">
        <div class="card-header">📅 Dati Generali Registrazione</div>
        <div class="card-body">
            <div class="row">
                <div class="col-md-3">
                    <label>Data Operazione</label>
                    <input type="date" name="data_operazione" class="form-control" required>
                </div>
                <div class="col-md-6">
                    <label>Causale</label>
                    <input type="text" name="causale" class="form-control" maxlength="500" required>
                </div>
                <div class="col-md-3">
                    <label>Importo Totale €</label>
                    <input type="number" name="importo_totale" class="form-control" step="0.01" required>
                </div>
            </div>
        </div>
    </div>
    
    <!-- SEZIONE GESTIONI -->
    <div class="card mb-4">
        <div class="card-header">🎯 Ripartizione per Gestioni</div>
        <div class="card-body">
            <div class="row">
                <div class="col-md-4">
                    <div class="form-check">
                        <input type="checkbox" class="form-check-input" id="gest_ordinaria" name="gestioni[ordinaria][attiva]" checked>
                        <label class="form-check-label">📊 Gestione Ordinaria</label>
                    </div>
                    <input type="number" name="gestioni[ordinaria][percentuale]" value="100" class="form-control mt-2" max="100" min="0">
                </div>
                <div class="col-md-4">
                    <div class="form-check">
                        <input type="checkbox" class="form-check-input" id="gest_riscaldamento" name="gestioni[riscaldamento][attiva]">
                        <label class="form-check-label">🔥 Gestione Riscaldamento</label>
                    </div>
                    <input type="number" name="gestioni[riscaldamento][percentuale]" value="0" class="form-control mt-2" max="100" min="0">
                </div>
                <div class="col-md-4">
                    <div class="form-check">
                        <input type="checkbox" class="form-check-input" id="gest_straordinaria" name="gestioni[straordinaria][attiva]">
                        <label class="form-check-label">⚡ Gestione Straordinaria</label>
                    </div>
                    <input type="number" name="gestioni[straordinaria][percentuale]" value="0" class="form-control mt-2" max="100" min="0">
                </div>
            </div>
            <div class="alert alert-info mt-3">
                <small>💡 Il totale delle percentuali deve essere 100%</small>
            </div>
        </div>
    </div>
    
    <!-- SEZIONE RITENUTA D'ACCONTO -->
    <div class="card mb-4">
        <div class="card-header">💰 Ritenuta d'Acconto</div>
        <div class="card-body">
            <div class="form-check mb-3">
                <input type="checkbox" class="form-check-input" id="applica_ritenuta" name="applica_ritenuta">
                <label class="form-check-label">Applica Ritenuta d'Acconto</label>
            </div>
            
            <div id="sezione-ritenuta" style="display: none;">
                <div class="row">
                    <div class="col-md-3">
                        <label>Codice Ritenuta</label>
                        <select name="codice_ritenuta" class="form-control">
                            <option value="">Seleziona...</option>
                            <option value="R001">R001 - Ritenuta 20%</option>
                            <option value="R002">R002 - Ritenuta 4%</option>
                        </select>
                    </div>
                    <div class="col-md-3">
                        <label>% Ritenuta</label>
                        <input type="number" name="percentuale_ritenuta" class="form-control" step="0.01" max="100">
                    </div>
                    <div class="col-md-3">
                        <label>Imponibile €</label>
                        <input type="number" name="imponibile_ritenuta" class="form-control" step="0.01">
                    </div>
                    <div class="col-md-3">
                        <label>Importo Ritenuta €</label>
                        <input type="number" name="importo_ritenuta" class="form-control" step="0.01" readonly>
                    </div>
                </div>
            </div>
        </div>
    </div>
    
    <!-- SEZIONE RIGHE CONTABILI -->
    <div class="card mb-4">
        <div class="card-header">📊 Righe Contabili (Partita Doppia)</div>
        <div class="card-body">
            <div class="table-responsive">
                <table class="table table-bordered" id="righe-contabili-table">
                    <thead>
                        <tr>
                            <th width="25%">Sottoconto</th>
                            <th width="30%">Descrizione</th>
                            <th width="15%">DARE €</th>
                            <th width="15%">AVERE €</th>
                            <th width="10%">Gestioni</th>
                            <th width="5%">Azioni</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>
                                <select name="righe[0][sottoconto_id]" class="form-control" required>
                                    <option value="">Seleziona sottoconto...</option>
                                    <!-- Popolato via JavaScript -->
                                </select>
                            </td>
                            <td>
                                <input type="text" name="righe[0][descrizione]" class="form-control" maxlength="500">
                            </td>
                            <td>
                                <input type="number" name="righe[0][importo_dare]" class="form-control importo-dare" step="0.01" min="0">
                            </td>
                            <td>
                                <input type="number" name="righe[0][importo_avere]" class="form-control importo-avere" step="0.01" min="0">
                            </td>
                            <td>
                                <button type="button" class="btn btn-sm btn-outline-primary" onclick="configuraGestioni(0)">
                                    ⚙️ Config
                                </button>
                            </td>
                            <td>
                                <button type="button" class="btn btn-sm btn-danger" onclick="rimuoviRiga(0)">
                                    🗑️
                                </button>
                            </td>
                        </tr>
                    </tbody>
                    <tfoot>
                        <tr class="table-info">
                            <th colspan="2">TOTALI</th>
                            <th id="totale-dare">0.00</th>
                            <th id="totale-avere">0.00</th>
                            <th colspan="2">
                                <span id="quadratura-status" class="badge badge-warning">⚠️ Non bilanciato</span>
                            </th>
                        </tr>
                    </tfoot>
                </table>
                
                <button type="button" class="btn btn-success mt-2" onclick="aggiungiRiga()">
                     Aggiungi Riga
                </button>
            </div>
        </div>
    </div>
    
    <!-- PULSANTI -->
    <div class="text-center">
        <button type="button" class="btn btn-secondary" onclick="salvaBozza()">💾 Salva Bozza</button>
        <button type="submit" class="btn btn-primary">✅ Conferma Registrazione</button>
        <button type="button" class="btn btn-warning" onclick="calcolaRipartizione()">🧮 Calcola Ripartizione</button>
    </div>
    
</form>

⚙️ WORKFLOW IMPLEMENTAZIONE

1 Models Laravel

// GestioneContabile.php (AGGIORNATO)
class GestioneContabile extends Model {
    protected $fillable = [
        'condominio_id', 'denominazione', 'tipo_gestione',
        'data_inizio', 'data_fine_prevista', 'stato',
        'gestione_precedente_id', 'saldi_apertura', 'saldi_chiusura'
    ];
    
    protected $casts = [
        'saldi_apertura' => 'array',
        'saldi_chiusura' => 'array',
        'data_inizio' => 'date',
        'data_fine_prevista' => 'date'
    ];
    
    // Relazione con registrazioni
    public function registrazioni() {
        return $this->hasMany(RegistrazioneContabile::class, 'gestione_id');
    }
    
    // Calcolo saldi automatico
    public function calcolaSaldi() {
        // Implementazione calcolo saldi DARE/AVERE
    }
}

// RegistrazioneContabile.php (AGGIORNATO)  
class RegistrazioneContabile extends Model {
    protected $fillable = [
        'gestione_id', 'tipo_gestione', 'data_operazione',
        'causale', 'importo_totale', 'ritenuta_acconto',
        'percentuale_ritenuta', 'codice_ritenuta'
    ];
    
    // Relazione gestioni multiple
    public function ripartizioniGestioni() {
        return $this->hasMany(RipartizioneGestione::class, 'registrazione_id');
    }
    
    // Verifica quadratura
    public function isQuadrata() {
        $dare = $this->movimenti()->sum('importo_dare');
        $avere = $this->movimenti()->sum('importo_avere');  
        return abs($dare - $avere) < 0.01;
    }
}

2 Controller Avanzato

// ContabilitaAvanzataController.php
class ContabilitaAvanzataController extends Controller {
    
    public function creaRegistrazione(Request $request) {
        DB::beginTransaction();
        try {
            // 1. Validazione input
            $validated = $this->validateRegistrazione($request);
            
            // 2. Creazione registrazione principale  
            $registrazione = $this->creaRegistrazionePrincipale($validated);
            
            // 3. Creazione movimenti DARE/AVERE
            $this->creaMovimentiContabili($registrazione, $validated['righe']);
            
            // 4. Gestione ripartizioni multiple gestioni
            $this->creaRipartizioniGestioni($registrazione, $validated['gestioni']);
            
            // 5. Calcolo ripartizione condomini
            $this->calcolaRipartizioneCondomini($registrazione);
            
            // 6. Verifica quadratura finale
            if (!$registrazione->isQuadrata()) {
                throw new Exception('Partita doppia non bilanciata');
            }
            
            DB::commit();
            return response()->json(['success' => true, 'id' => $registrazione->id]);
            
        } catch (Exception $e) {
            DB::rollback();
            return response()->json(['error' => $e->getMessage()], 400);
        }
    }
    
    public function chiusuraGestione($gestioneId) {
        // Implementazione chiusura contabile
        $gestione = GestioneContabile::findOrFail($gestioneId);
        
        // 1. Calcolo saldi finali tutti i sottoconti
        $saldiFinali = $this->calcolaSaldiFinali($gestione);
        
        // 2. Creazione registrazioni di chiusura
        $this->creaRegistrazioniChiusura($gestione, $saldiFinali);
        
        // 3. Aggiornamento stato gestione
        $gestione->update(['stato' => 'chiusa_definitiva']);
    }
    
    public function riaperturaGestione($nuovaGestioneId, $precedenteGestioneId) {
        // Implementazione riapertura con riporto saldi
    }
}

3 JavaScript Frontend

// contabilita-avanzata.js
class ContabilitaAvanzata {
    
    constructor() {
        this.inizializzaForm();
        this.bindEvents();
    }
    
    inizializzaForm() {
        // Inizializzazione form e validazioni
        this.calcolaTotali();
        this.verificaQuadratura();
    }
    
    aggiungiRiga() {
        // Aggiunge una nuova riga alla tabella movimenti
        const nuovaRiga = this.creaNuovaRigaTemplate();
        $('#righe-contabili-table tbody').append(nuovaRiga);
        this.bindEventsRiga();
    }
    
    calcolaTotali() {
        let totaleDare = 0;
        let totaleAvere = 0;
        
        $('.importo-dare').each(function() {
            totaleDare += parseFloat($(this).val()) || 0;
        });
        
        $('.importo-avere').each(function() {
            totaleAvere += parseFloat($(this).val()) || 0;
        });
        
        $('#totale-dare').text(totaleDare.toFixed(2));
        $('#totale-avere').text(totaleAvere.toFixed(2));
        
        this.verificaQuadratura(totaleDare, totaleAvere);
    }
    
    verificaQuadratura(dare, avere) {
        const differenza = Math.abs(dare - avere);
        const status = $('#quadratura-status');
        
        if (differenza < 0.01) {
            status.removeClass('badge-warning badge-danger')
                  .addClass('badge-success')
                  .text('✅ Bilanciato');
        } else {
            status.removeClass('badge-success badge-danger')
                  .addClass('badge-warning')  
                  .text(`⚠️ Differenza: ${differenza.toFixed(2)}`);
        }
    }
    
    calcolaRipartizione() {
        // Calcola la ripartizione automatica tra gestioni
        const formData = new FormData($('#registrazione-contabile-form')[0]);
        
        $.post('/api/calcola-ripartizione', formData)
            .done(function(response) {
                // Aggiorna la UI con i risultati
                this.aggiornaRipartizione(response.ripartizioni);
            }.bind(this));
    }
    
    salvaRegistrazione() {
        if (!this.validaForm()) return;
        
        const formData = new FormData($('#registrazione-contabile-form')[0]);
        
        $.post('/api/registrazioni-contabili', formData)
            .done(function(response) {
                Swal.fire('Successo!', 'Registrazione salvata correttamente', 'success');
                window.location.href = '/admin/contabilita';
            })
            .fail(function(xhr) {
                Swal.fire('Errore!', xhr.responseJSON.error, 'error');
            });
    }
}

// Inizializzazione
$(document).ready(function() {
    new ContabilitaAvanzata();
});

📋 CHECKLIST IMPLEMENTAZIONE

Database

  • Eseguire migration gestioni multiple
  • Aggiornare tabelle esistenti
  • Creare nuove tabelle ripartizioni
  • Implementare trigger calcolo saldi

Backend Laravel

  • Aggiornare Models esistenti
  • Creare ContabilitaAvanzataController
  • Implementare API per calcoli
  • Creare Service per chiusura/riapertura

Frontend

  • Creare form registrazione avanzato
  • Implementare JavaScript calcoli
  • Integrare con sistema esistente
  • Test interfaccia utente

Integrazione

  • Collegare con sistema esistente stabili
  • Implementare ripartizione millesimale
  • Test con dati reali
  • Validazione chiusura/riapertura

🎯 PROSSIMI PASSI

  1. 📤 TRASFERIMENTO DOCUMENTAZIONE: Copia questo file sulla VM
  2. 💾 IMPLEMENTAZIONE DATABASE: Eseguire le migration
  3. 🎨 SVILUPPO INTERFACCIA: Creare le schermate
  4. 🔄 INTEGRAZIONE: Collegare con sistema esistente
  5. 📊 TEST: Validare con dati reali

Questa documentazione fornisce tutte le specifiche per implementare il sistema contabile avanzato con gestioni multiple e partita doppia classica! 🚀💰