📋 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
1021 lines
53 KiB
Markdown
1021 lines
53 KiB
Markdown
# 💻 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! 🏆
|