📋 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
23 KiB
23 KiB
💰 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
- CHIUSURA GESTIONE: Movimenti di chiusura a Stato Patrimoniale e Conto Economico
- 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
- 📤 TRASFERIMENTO DOCUMENTAZIONE: Copia questo file sulla VM
- 💾 IMPLEMENTAZIONE DATABASE: Eseguire le migration
- 🎨 SVILUPPO INTERFACCIA: Creare le schermate
- 🔄 INTEGRAZIONE: Collegare con sistema esistente
- 📊 TEST: Validare con dati reali
Questa documentazione fornisce tutte le specifiche per implementare il sistema contabile avanzato con gestioni multiple e partita doppia classica! 🚀💰