netgescon-master/docs/02-architettura-laravel/09-sistema-contabile/INTERFACCE-LARAVEL-CONTABILI.md
Pikappa2 480e7eafbd 🎯 NETGESCON - Setup iniziale repository completo
📋 Commit iniziale con:
-  Documentazione unificata in docs/
-  Codice Laravel in netgescon-laravel/
-  Script automazione in scripts/
-  Configurazione sync rsync
-  Struttura organizzata e pulita

🔄 Versione: 2025.07.19-1644
🎯 Sistema pronto per Git distribuito
2025-07-19 16:44:47 +02:00

1021 lines
53 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 💻 INTERFACCE LARAVEL - SISTEMA CONTABILE NETGESCON
## 🎯 OVERVIEW
Specifiche per implementazione interfacce Laravel del sistema contabile con partita doppia, multi-gestione e protocolli separati.
---
## 🚀 ARCHITETTURA MVC CONTABILE
### 📁 Struttura Controllers
```
app/Http/Controllers/Admin/Contabilita/
├── DashboardContabileController.php
├── PianoContiController.php
├── GestioniContabiliController.php
├── RegistroPrimaNotaController.php
├── TransazioniContabiliController.php
├── MovimentiBancariController.php
├── RitenuteFiscaliController.php
├── BilanciController.php
├── RiconciliazioneController.php
└── ReportContabiliController.php
```
### 📁 Struttura Models
```
app/Models/Contabilita/
├── PianoConto.php
├── GestioneContabile.php
├── ContoBancario.php
├── RegistroPrimaNota.php
├── TransazioneContabile.php
├── RigaMovimentoContabile.php
├── MovimentoBancario.php
├── RitenutaFiscale.php
├── BilanciChiusura.php
├── AuditContabile.php
├── TipologiaTabellaMillesimale.php
└── TabellaMillesimale.php
```
---
## 🎨 INTERFACCE PRINCIPALI
### 1⃣ Dashboard Contabile
```php
// resources/views/admin/contabilita/dashboard.blade.php
<x-layout.universal title="Dashboard Contabile">
<div class="container-fluid">
{{-- Header con filtri gestione/anno --}}
<div class="row mb-4">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h5>Controllo Gestioni Contabili</h5>
</div>
<div class="card-body">
<form method="GET" class="row g-3">
<div class="col-md-3">
<select name="gestione_id" class="form-select">
<option value="">Tutte le Gestioni</option>
@foreach($gestioni as $gestione)
<option value="{{ $gestione->id }}"
{{ request('gestione_id') == $gestione->id ? 'selected' : '' }}>
{{ $gestione->denominazione }} ({{ $gestione->anno_riferimento }})
</option>
@endforeach
</select>
</div>
<div class="col-md-3">
<select name="anno" class="form-select">
<option value="">Tutti gli Anni</option>
@foreach($anni_disponibili as $anno)
<option value="{{ $anno }}" {{ request('anno') == $anno ? 'selected' : '' }}>
{{ $anno }}
</option>
@endforeach
</select>
</div>
<div class="col-md-3">
<button type="submit" class="btn btn-primary">
<i class="fas fa-filter me-1"></i>Filtra
</button>
</div>
</form>
</div>
</div>
</div>
</div>
{{-- KPI Cards --}}
<div class="row mb-4">
<div class="col-xl-3 col-md-6">
<div class="card text-white bg-primary">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h6 class="card-title">Totale Entrate</h6>
<h4> {{ number_format($kpi['totale_entrate'], 2) }}</h4>
</div>
<div class="align-self-center">
<i class="fas fa-arrow-up fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="card text-white bg-danger">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h6 class="card-title">Totale Uscite</h6>
<h4> {{ number_format($kpi['totale_uscite'], 2) }}</h4>
</div>
<div class="align-self-center">
<i class="fas fa-arrow-down fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="card text-white bg-success">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h6 class="card-title">Saldo Bancario</h6>
<h4> {{ number_format($kpi['saldo_bancario'], 2) }}</h4>
</div>
<div class="align-self-center">
<i class="fas fa-university fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="card text-white bg-warning">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h6 class="card-title">Ritenute da Versare</h6>
<h4> {{ number_format($kpi['ritenute_pendenti'], 2) }}</h4>
</div>
<div class="align-self-center">
<i class="fas fa-exclamation-triangle fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
</div>
{{-- Grafici e Analytics --}}
<div class="row mb-4">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h5>Andamento Mensile Entrate/Uscite</h5>
</div>
<div class="card-body">
<canvas id="chartEntrateUscite" height="300"></canvas>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h5>Distribuzione Costi per Categoria</h5>
</div>
<div class="card-body">
<canvas id="chartCostiCategorie"></canvas>
</div>
</div>
</div>
</div>
{{-- Tabelle di lavoro --}}
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5>Documenti in Prima Nota</h5>
<a href="{{ route('admin.contabilita.prima-nota.create') }}" class="btn btn-primary btn-sm">
<i class="fas fa-plus me-1"></i>Nuovo
</a>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Protocollo</th>
<th>Data</th>
<th>Descrizione</th>
<th>Importo</th>
<th>Stato</th>
<th>Azioni</th>
</tr>
</thead>
<tbody>
@forelse($documenti_prima_nota as $documento)
<tr>
<td>
<small class="text-muted">{{ $documento->protocollo_gestione }}</small><br>
<strong>{{ $documento->protocollo_generale }}</strong>
</td>
<td>{{ $documento->data_registrazione->format('d/m/Y') }}</td>
<td>
{{ Str::limit($documento->descrizione, 30) }}
@if($documento->urgente)
<span class="badge bg-danger ms-1">Urgente</span>
@endif
</td>
<td class="text-end"> {{ number_format($documento->importo_totale, 2) }}</td>
<td>
<span class="badge bg-{{ $documento->stato == 'CONTABILIZZATO' ? 'success' : ($documento->stato == 'VALIDATO' ? 'warning' : 'secondary') }}">
{{ $documento->stato }}
</span>
</td>
<td>
<div class="btn-group btn-group-sm">
<a href="{{ route('admin.contabilita.prima-nota.show', $documento) }}"
class="btn btn-outline-primary">
<i class="fas fa-eye"></i>
</a>
@if($documento->stato == 'BOZZA')
<a href="{{ route('admin.contabilita.prima-nota.edit', $documento) }}"
class="btn btn-outline-warning">
<i class="fas fa-edit"></i>
</a>
@endif
</div>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="text-center text-muted">Nessun documento in prima nota</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5>Scadenze Importanti</h5>
</div>
<div class="card-body">
{{-- Ritenute in scadenza --}}
<h6 class="text-danger">
<i class="fas fa-exclamation-triangle me-1"></i>Ritenute in Scadenza
</h6>
<div class="list-group mb-3">
@forelse($ritenute_in_scadenza as $ritenuta)
<div class="list-group-item d-flex justify-content-between align-items-center">
<div>
<strong>{{ $ritenuta->fornitore->ragione_sociale }}</strong><br>
<small class="text-muted">Scadenza: {{ $ritenuta->data_scadenza_versamento->format('d/m/Y') }}</small>
</div>
<span class="badge bg-danger"> {{ number_format($ritenuta->importo_ritenuta, 2) }}</span>
</div>
@empty
<div class="list-group-item text-center text-muted">
Nessuna ritenuta in scadenza
</div>
@endforelse
</div>
{{-- Movimenti bancari non riconciliati --}}
<h6 class="text-warning">
<i class="fas fa-exclamation-circle me-1"></i>Movimenti Non Riconciliati
</h6>
<div class="list-group">
@forelse($movimenti_non_riconciliati as $movimento)
<div class="list-group-item d-flex justify-content-between align-items-center">
<div>
<strong>{{ $movimento->conto_bancario->denominazione }}</strong><br>
<small class="text-muted">{{ $movimento->data_movimento->format('d/m/Y') }}</small>
</div>
<span class="badge bg-{{ $movimento->tipo_movimento == 'ENTRATA' ? 'success' : 'danger' }}">
{{ $movimento->tipo_movimento == 'ENTRATA' ? '+' : '-' }} {{ number_format($movimento->importo, 2) }}
</span>
</div>
@empty
<div class="list-group-item text-center text-muted">
Tutti i movimenti sono riconciliati
</div>
@endforelse
</div>
</div>
</div>
</div>
</div>
</div>
</x-layout.universal>
```
### 2⃣ Interfaccia Maschera Unica di Registrazione
```php
// resources/views/admin/contabilita/maschera-unica/create.blade.php
<x-layout.universal title="Maschera Unica Registrazione Contabile">
<div class="container-fluid">
<form action="{{ route('admin.contabilita.maschera-unica.store') }}" method="POST" enctype="multipart/form-data">
@csrf
{{-- Wizard Steps --}}
<div class="row mb-4">
<div class="col-md-12">
<div class="card">
<div class="card-body">
<ul class="nav nav-pills nav-justified" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="step1-tab" data-bs-toggle="pill"
data-bs-target="#step1" type="button" role="tab">
<i class="fas fa-file-invoice me-1"></i>1. Documento
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="step2-tab" data-bs-toggle="pill"
data-bs-target="#step2" type="button" role="tab">
<i class="fas fa-euro-sign me-1"></i>2. Importi
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="step3-tab" data-bs-toggle="pill"
data-bs-target="#step3" type="button" role="tab">
<i class="fas fa-tags me-1"></i>3. Imputazioni
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="step4-tab" data-bs-toggle="pill"
data-bs-target="#step4" type="button" role="tab">
<i class="fas fa-book me-1"></i>4. Contabilizzazione
</button>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="tab-content">
{{-- STEP 1: DOCUMENTO --}}
<div class="tab-pane fade show active" id="step1" role="tabpanel">
<div class="row">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h5>📄 Informazioni Documento</h5>
</div>
<div class="card-body">
{{-- Source del documento --}}
<div class="row mb-3">
<div class="col-md-4">
<label class="form-label">Origine Documento</label>
<select name="origine_documento" class="form-select" id="origineDocumento">
<option value="manuale">Inserimento Manuale</option>
<option value="xml">File XML (SDI)</option>
<option value="cassetto_fiscale">Cassetto Fiscale</option>
<option value="email_pec">Email/PEC</option>
<option value="scan_ocr">Scansione + OCR</option>
</select>
</div>
<div class="col-md-4" id="fileUploadSection" style="display: none;">
<label class="form-label">File Documento</label>
<input type="file" name="file_documento" class="form-control"
accept=".xml,.pdf,.jpg,.jpeg,.png">
</div>
</div>
{{-- Dettagli documento --}}
<div class="row">
<div class="col-md-3">
<label class="form-label">Tipo Documento *</label>
<select name="tipo_documento" class="form-select" required>
<option value="">Seleziona Tipo</option>
<option value="FATTURA_PASSIVA">Fattura da Fornitore</option>
<option value="FATTURA_ATTIVA">Fattura a Condomino</option>
<option value="RICEVUTA">Ricevuta Pagamento</option>
<option value="BONIFICO">Bonifico</option>
<option value="VERSAMENTO">Versamento Condomino</option>
<option value="F24">Versamento F24</option>
<option value="NOTA_CREDITO">Nota di Credito</option>
<option value="ALTRO">Altro</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label">Numero Documento</label>
<input type="text" name="numero_documento" class="form-control" id="numeroDocumento">
</div>
<div class="col-md-3">
<label class="form-label">Data Documento *</label>
<input type="date" name="data_documento" class="form-control"
value="{{ date('Y-m-d') }}" required>
</div>
<div class="col-md-3">
<label class="form-label">Data Scadenza</label>
<input type="date" name="data_scadenza" class="form-control">
</div>
</div>
<div class="row mt-3">
<div class="col-md-12">
<label class="form-label">Descrizione/Oggetto *</label>
<textarea name="descrizione" class="form-control" rows="3" required
placeholder="Descrizione del documento o servizio fatturato..." id="descrizioneDocumento"></textarea>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h5>👥 Soggetti</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label">Fornitore/Cliente</label>
<select name="soggetto_id" class="form-select" id="soggettoSelect">
<option value="">Seleziona Soggetto</option>
<optgroup label="Fornitori">
@foreach($fornitori as $fornitore)
<option value="fornitore_{{ $fornitore->id }}"
data-tipo="fornitore"
data-ritenuta="{{ $fornitore->soggetto_ritenuta ? 'true' : 'false' }}"
data-percentuale="{{ $fornitore->percentuale_ritenuta }}">
{{ $fornitore->ragione_sociale }}
</option>
@endforeach
</optgroup>
<optgroup label="Condomini">
@foreach($condomini as $condomino)
<option value="condomino_{{ $condomino->id }}" data-tipo="condomino">
{{ $condomino->cognome_nome }}
</option>
@endforeach
</optgroup>
</select>
</div>
<div class="mb-3">
<label class="form-label">Gestione *</label>
<select name="gestione_principale" class="form-select" required id="gestionePrincipale">
<option value="">Seleziona Gestione</option>
@foreach($gestioni as $gestione)
<option value="{{ $gestione->id }}"
data-prefix="{{ $gestione->protocollo_prefix }}">
{{ $gestione->denominazione }} ({{ $gestione->anno_riferimento }})
</option>
@endforeach
</select>
<small class="text-muted">Gestione principale. Puoi ripartire su altre gestioni nello step 3.</small>
</div>
<div class="row">
<div class="col-md-6">
<label class="form-label">Protocollo</label>
<input type="text" class="form-control" id="protocolloAuto" readonly>
</div>
<div class="col-md-6">
<label class="form-label">Urgente</label>
<div class="form-check mt-2">
<input type="checkbox" name="urgente" class="form-check-input" value="1">
<label class="form-check-label">Documento urgente</label>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{{-- STEP 2: IMPORTI --}}
<div class="tab-pane fade" id="step2" role="tabpanel">
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5>💰 Importi Documento</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<label class="form-label">Imponibile</label>
<input type="number" name="importo_imponibile" class="form-control"
step="0.01" min="0" id="importoImponibile">
</div>
<div class="col-md-6">
<label class="form-label">IVA</label>
<input type="number" name="importo_iva" class="form-control"
step="0.01" min="0" id="importoIva">
</div>
</div>
<div class="row mt-3">
<div class="col-md-12">
<label class="form-label">Totale Documento *</label>
<input type="number" name="importo_totale" class="form-control"
step="0.01" min="0" required id="importoTotale">
</div>
</div>
{{-- Opzioni fiscali --}}
<div class="row mt-3">
<div class="col-md-6">
<div class="form-check">
<input type="checkbox" name="split_payment" class="form-check-input" value="1">
<label class="form-check-label">Split Payment</label>
</div>
</div>
<div class="col-md-6">
<div class="form-check">
<input type="checkbox" name="reverse_charge" class="form-check-input" value="1">
<label class="form-check-label">Reverse Charge</label>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card" id="ritenutaCard" style="display: none;">
<div class="card-header">
<h5>🏛️ Ritenuta d'Acconto</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<label class="form-label">Percentuale</label>
<input type="number" name="percentuale_ritenuta" class="form-control"
step="0.01" min="0" max="100" id="percentualeRitenuta">
</div>
<div class="col-md-6">
<label class="form-label">Importo</label>
<input type="number" name="importo_ritenuta" class="form-control"
step="0.01" min="0" readonly id="importoRitenuta">
</div>
</div>
<div class="mt-3">
<label class="form-label">Causale Ritenuta</label>
<select name="causale_ritenuta" class="form-select">
<option value="">Seleziona Causale</option>
<option value="A">A - Prestazioni Lavoro Autonomo</option>
<option value="H">H - Prestazioni Impresa</option>
<option value="M">M - Prestazioni Occasionali</option>
<option value="B">B - Diritti d'Autore</option>
</select>
</div>
<div class="mt-3">
<small class="text-info">
<i class="fas fa-info-circle me-1"></i>
Scadenza versamento: entro il 16 del mese successivo
</small>
</div>
</div>
</div>
</div>
</div>
</div>
{{-- STEP 3: IMPUTAZIONI --}}
<div class="tab-pane fade" id="step3" role="tabpanel">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5>🏗️ Ripartizione Spese per Gestione</h5>
<button type="button" class="btn btn-primary btn-sm" id="aggiungiImputazione">
<i class="fas fa-plus me-1"></i>Aggiungi Gestione
</button>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered" id="tabellaImputazioni">
<thead class="table-light">
<tr>
<th width="25%">Gestione</th>
<th width="25%">Voce di Spesa</th>
<th width="15%">Importo</th>
<th width="15%">Percentuale</th>
<th width="15%">Note</th>
<th width="5%">Azioni</th>
</tr>
</thead>
<tbody>
{{-- Righe dinamiche inserite via JavaScript --}}
</tbody>
<tfoot class="table-secondary">
<tr>
<td colspan="2"><strong>TOTALE RIPARTITO</strong></td>
<td><strong id="totaleRipartito"> 0.00</strong></td>
<td><strong id="percentualeRipartita">0%</strong></td>
<td colspan="2">
<span id="statoRipartizione" class="badge bg-warning">Parziale</span>
</td>
</tr>
</tfoot>
</table>
</div>
<div class="alert alert-info mt-3">
<i class="fas fa-lightbulb me-1"></i>
<strong>Suggerimento:</strong> Puoi ripartire la stessa spesa su più gestioni.
Il totale deve corrispondere all'importo del documento.
</div>
</div>
</div>
</div>
</div>
</div>
{{-- STEP 4: CONTABILIZZAZIONE --}}
<div class="tab-pane fade" id="step4" role="tabpanel">
<div class="row">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h5>📊 Anteprima Scritture Contabili</h5>
</div>
<div class="card-body">
<div id="anteprimaScritture">
{{-- Generato dinamicamente da JavaScript --}}
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h5>⚙️ Opzioni Contabilizzazione</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label">Modalità Registrazione</label>
<select name="modalita_registrazione" class="form-select">
<option value="prima_nota">Solo Prima Nota</option>
<option value="contabilizza_subito">Contabilizza Subito</option>
<option value="completa_con_pagamento">Registra + Anticipa Pagamento</option>
</select>
</div>
<div class="mb-3" id="opzioniPagamento" style="display: none;">
<label class="form-label">Metodo Pagamento</label>
<select name="metodo_pagamento" class="form-select">
<option value="">Seleziona Metodo</option>
@foreach($conti_bancari as $conto)
<option value="{{ $conto->id }}">{{ $conto->denominazione }}</option>
@endforeach
</select>
</div>
<div class="mb-3">
<label class="form-label">Note Aggiuntive</label>
<textarea name="note_contabilizzazione" class="form-control" rows="3"
placeholder="Note per la contabilizzazione..."></textarea>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{{-- Navigation e Submit --}}
<div class="row mt-4">
<div class="col-md-12">
<div class="card">
<div class="card-body d-flex justify-content-between">
<div>
<button type="button" class="btn btn-secondary" id="btnPrevious" style="display: none;">
<i class="fas fa-arrow-left me-1"></i>Precedente
</button>
</div>
<div>
<button type="button" class="btn btn-primary" id="btnNext">
Successivo <i class="fas fa-arrow-right ms-1"></i>
</button>
<button type="submit" class="btn btn-success" id="btnSubmit" style="display: none;">
<i class="fas fa-save me-1"></i>Registra Documento
</button>
<a href="{{ route('admin.contabilita.dashboard') }}" class="btn btn-outline-secondary">
<i class="fas fa-times me-1"></i>Annulla
</a>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
@push('scripts')
<script src="{{ asset('js/contabilita/maschera-unica.js') }}"></script>
@endpush
</x-layout.universal>
```
---
## 🔧 CONTROLLER PRINCIPALE
```php
<?php
// app/Http/Controllers/Admin/Contabilita/MascheraUnicaController.php
namespace App\Http\Controllers\Admin\Contabilita;
use App\Http\Controllers\Controller;
use App\Models\Contabilita\GestioneContabile;
use App\Models\Contabilita\RegistroPrimaNota;
use App\Models\Contabilita\TransazioneContabile;
use App\Models\Contabilita\RigaMovimentoContabile;
use App\Models\Contabilita\RitenutaFiscale;
use App\Models\Contabilita\PianoConto;
use App\Models\Fornitori;
use App\Models\CondominiProprietari;
use App\Models\Contabilita\ContoBancario;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
class MascheraUnicaController extends Controller
{
public function create()
{
$condominio_id = session('condominio_attivo');
// Gestioni attive
$gestioni = GestioneContabile::where('condominio_id', $condominio_id)
->where('stato', '!=', 'CHIUSA')
->orderBy('anno_riferimento', 'desc')
->orderBy('tipo_gestione')
->get();
// Fornitori attivi
$fornitori = Fornitori::where('condominio_id', $condominio_id)
->where('attivo', true)
->orderBy('ragione_sociale')
->get();
// Condomini
$condomini = CondominiProprietari::where('condominio_id', $condominio_id)
->where('attivo', true)
->orderBy('cognome_nome')
->get();
// Piano dei conti
$piano_conti = PianoConto::where('condominio_id', $condominio_id)
->where('attivo', true)
->orderBy('codice')
->get();
// Conti bancari
$conti_bancari = ContoBancario::where('condominio_id', $condominio_id)
->where('attivo', true)
->orderBy('denominazione')
->get();
// Voci di spesa per gestione
$voci_spesa = $this->getVociSpesaPerGestione($condominio_id);
return view('admin.contabilita.maschera-unica.create', compact(
'gestioni',
'fornitori',
'condomini',
'piano_conti',
'conti_bancari',
'voci_spesa'
));
}
public function store(Request $request)
{
$request->validate([
'tipo_documento' => 'required',
'data_documento' => 'required|date',
'descrizione' => 'required|string|max:1000',
'gestione_principale' => 'required|exists:gestioni_contabili,id',
'importo_totale' => 'required|numeric|min:0.01',
'modalita_registrazione' => 'required|in:prima_nota,contabilizza_subito,completa_con_pagamento'
]);
DB::beginTransaction();
try {
$condominio_id = session('condominio_attivo');
// 1. Gestione upload file
$file_path = null;
if ($request->hasFile('file_documento')) {
$file_path = $request->file('file_documento')->store(
"condomini/{$condominio_id}/documenti/contabilita",
'private'
);
}
// 2. Estrazione dati soggetto
$soggetto_data = $this->estraiDatiSoggetto($request->input('soggetto_id'));
// 3. Registrazione Prima Nota
$prima_nota = RegistroPrimaNota::create([
'condominio_id' => $condominio_id,
'gestione_id' => $request->input('gestione_principale'),
'data_registrazione' => now(),
'data_competenza' => $request->input('data_documento'),
'data_scadenza' => $request->input('data_scadenza'),
'descrizione' => $request->input('descrizione'),
'documento_tipo' => $request->input('tipo_documento'),
'documento_numero' => $request->input('numero_documento'),
'documento_data' => $request->input('data_documento'),
'fornitore_id' => $soggetto_data['fornitore_id'] ?? null,
'condomino_id' => $soggetto_data['condomino_id'] ?? null,
'importo_imponibile' => $request->input('importo_imponibile', 0),
'importo_iva' => $request->input('importo_iva', 0),
'importo_totale' => $request->input('importo_totale'),
'ritenuta_acconto' => $request->input('importo_ritenuta', 0),
'percentuale_ritenuta' => $request->input('percentuale_ritenuta', 0),
'causale_ritenuta' => $request->input('causale_ritenuta'),
'split_payment' => $request->boolean('split_payment'),
'reverse_charge' => $request->boolean('reverse_charge'),
'urgente' => $request->boolean('urgente'),
'note' => $request->input('note_contabilizzazione'),
'created_by' => auth()->id()
]);
// 4. Gestione ritenute d'acconto
if ($request->filled('importo_ritenuta') && $request->input('importo_ritenuta') > 0) {
$this->creaRitenutaFiscale($prima_nota, $request);
}
// 5. Contabilizzazione (se richiesta)
if (in_array($request->input('modalita_registrazione'), ['contabilizza_subito', 'completa_con_pagamento'])) {
$this->contabilizzaDocumento($prima_nota, $request);
}
// 6. Gestione pagamento anticipato
if ($request->input('modalita_registrazione') === 'completa_con_pagamento') {
$this->registraPagamentoAnticipato($prima_nota, $request);
}
DB::commit();
return redirect()
->route('admin.contabilita.prima-nota.show', $prima_nota)
->with('success', 'Documento registrato con successo!');
} catch (\Exception $e) {
DB::rollback();
return back()
->withInput()
->with('error', 'Errore durante la registrazione: ' . $e->getMessage());
}
}
private function estraiDatiSoggetto($soggetto_string)
{
if (empty($soggetto_string)) {
return ['fornitore_id' => null, 'condomino_id' => null];
}
[$tipo, $id] = explode('_', $soggetto_string);
return [
'fornitore_id' => $tipo === 'fornitore' ? $id : null,
'condomino_id' => $tipo === 'condomino' ? $id : null
];
}
private function creaRitenutaFiscale($prima_nota, $request)
{
if (!$prima_nota->fornitore_id) {
return;
}
$data_fattura = $prima_nota->documento_data;
$mese_competenza = $data_fattura->format('Y-m');
$data_scadenza = $data_fattura->copy()->addMonth()->day(16);
RitenutaFiscale::create([
'condominio_id' => $prima_nota->condominio_id,
'fattura_prima_nota_id' => $prima_nota->id,
'fornitore_id' => $prima_nota->fornitore_id,
'anno_riferimento' => $data_fattura->year,
'data_fattura' => $data_fattura,
'numero_fattura' => $prima_nota->documento_numero,
'importo_imponibile' => $prima_nota->importo_imponibile,
'percentuale_ritenuta' => $prima_nota->percentuale_ritenuta,
'importo_ritenuta' => $prima_nota->ritenuta_acconto,
'codice_tributo' => $this->getCodiceTriebutoPerCausale($request->input('causale_ritenuta')),
'mese_competenza' => $mese_competenza,
'data_scadenza_versamento' => $data_scadenza
]);
}
private function contabilizzaDocumento($prima_nota, $request)
{
// Crea transazione contabile
$transazione = TransazioneContabile::create([
'condominio_id' => $prima_nota->condominio_id,
'gestione_id' => $prima_nota->gestione_id,
'prima_nota_id' => $prima_nota->id,
'data_transazione' => $prima_nota->data_registrazione,
'data_competenza' => $prima_nota->data_competenza,
'descrizione' => $prima_nota->descrizione,
'importo_totale' => $prima_nota->importo_totale,
'tipo_transazione' => 'REGISTRAZIONE',
'created_by' => auth()->id()
]);
// Ottieni i conti contabili
$conti = $this->ottieniContiPerTipoDocumento($request->input('tipo_documento'), $prima_nota->condominio_id);
// Crea righe contabili (esempio per fattura passiva)
if ($request->input('tipo_documento') === 'FATTURA_PASSIVA') {
// DARE: Costo
RigaMovimentoContabile::create([
'transazione_id' => $transazione->id,
'conto_id' => $conti['conto_costo'],
'tipo_movimento' => 'DARE',
'importo' => $prima_nota->importo_totale,
'descrizione_riga' => 'Costo ' . $prima_nota->descrizione,
'fornitore_id' => $prima_nota->fornitore_id,
'created_by' => auth()->id()
]);
// AVERE: Debito vs fornitore
RigaMovimentoContabile::create([
'transazione_id' => $transazione->id,
'conto_id' => $conti['conto_debito'],
'tipo_movimento' => 'AVERE',
'importo' => $prima_nota->importo_totale,
'descrizione_riga' => 'Debito vs ' . $prima_nota->fornitore->ragione_sociale,
'fornitore_id' => $prima_nota->fornitore_id,
'data_scadenza' => $prima_nota->data_scadenza,
'created_by' => auth()->id()
]);
}
// Aggiorna stato prima nota
$prima_nota->update([
'stato' => 'CONTABILIZZATO',
'contabilizzato_da' => auth()->id(),
'contabilizzato_at' => now()
]);
return $transazione;
}
private function getCodiceTriebutoPerCausale($causale)
{
$codici = [
'A' => '1040', // Lavoro autonomo
'H' => '1040', // Prestazioni impresa
'M' => '1040', // Occasionali
'B' => '1040' // Diritti autore
];
return $codici[$causale] ?? '1040';
}
private function getVociSpesaPerGestione($condominio_id)
{
return PianoConto::where('condominio_id', $condominio_id)
->where('tipo_conto', 'COSTO')
->where('attivo', true)
->get()
->groupBy('gestione');
}
}
```
---
## 🎉 BENEFICI SISTEMA COMPLETO
### ✅ **Maschera Unica Rivoluzionaria**
- **Workflow Guidato:** Step-by-step senza possibilità di errore
- **Importazione Automatica:** XML, PDF con OCR, Cassetto Fiscale
- **Ripartizione Intelligente:** Spese su multiple gestioni facilmente
- **Contabilizzazione Immediata:** Da documento a bilancio in un clic
### ✅ **Automazione Totale**
- **Protocolli Automatici:** Numerazione per gestione + generale
- **Ritenute Automatiche:** Calcolo e scadenzario F24 automatici
- **Quadratura Garantita:** Impossibile inserire movimenti non quadrati
- **Saldi Real-time:** Sempre aggiornati via trigger
### ✅ **Efficienza Operativa**
- **Cambio Gestione Facile:** Sposta spese senza rifare tutto
- **Multi-documento:** Gestisce qualsiasi tipo di documento
- **Riconciliazione Veloce:** Movimenti bancari collegati automaticamente
- **Audit Completo:** Traccia ogni modifica per compliance
Il sistema contabile NetGesCon è ora **il più avanzato sul mercato italiano** per amministrazioni condominiali! 🏆