📋 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
876 lines
30 KiB
Markdown
876 lines
30 KiB
Markdown
# NETGESCON - GESTIONE DOCUMENTALE E PROTOCOLLI
|
|
|
|
## 📋 OVERVIEW
|
|
Sistema completo per la gestione documentale dell'amministratore condominiale con protocolli digitali, stampa etichette, archiviazione intelligente e passaggio di consegne automatizzato.
|
|
|
|
## 📁 SISTEMA GESTIONE DOCUMENTALE AVANZATO
|
|
|
|
### 🗂️ Archivio Digitale Centralizzato
|
|
|
|
#### Struttura Gerarchica Documenti
|
|
```
|
|
STABILE [Codice Stabile] - [Denominazione]
|
|
├── 📋 AMMINISTRAZIONE
|
|
│ ├── Contratti Amministrazione
|
|
│ ├── Delibere Assembleari
|
|
│ ├── Verbali Assemblee
|
|
│ ├── Comunicazioni Condomini
|
|
│ └── Corrispondenza Ufficiale
|
|
├── 💰 CONTABILITÀ
|
|
│ ├── Bilanci (Preventivo/Consuntivo)
|
|
│ ├── Libri Contabili
|
|
│ ├── Fatture Passive
|
|
│ ├── Ricevute/Quietanze
|
|
│ └── Documenti Fiscali
|
|
├── 🏢 TECNICO/MANUTENZIONI
|
|
│ ├── Progetti e Capitolati
|
|
│ ├── Preventivi Lavori
|
|
│ ├── Contratti Manutenzione
|
|
│ ├── Certificazioni Impianti
|
|
│ └── Pratiche Edilizie
|
|
├── 👥 CONDOMINI
|
|
│ ├── Anagrafica Proprietari
|
|
│ ├── Contratti Locazione
|
|
│ ├── Comunicazioni Private
|
|
│ ├── Morosità/Solleciti
|
|
│ └── Documenti Personali
|
|
├── 🏠 UNITÀ IMMOBILIARI
|
|
│ ├── Atti di Compravendita
|
|
│ ├── Planimetrie/Visure
|
|
│ ├── Autorizzazioni Uso
|
|
│ ├── Contratti Utenze
|
|
│ └── Perizie/Valutazioni
|
|
└── ⚖️ LEGALE/CONTROVERSIE
|
|
├── Contenziosi Attivi
|
|
├── Contenziosi Passivi
|
|
├── Pareri Legali
|
|
├── Decreti Ingiuntivi
|
|
└── Transazioni
|
|
```
|
|
|
|
#### Database Struttura Documenti
|
|
```sql
|
|
CREATE TABLE documenti_archivio (
|
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
|
stabile_id BIGINT NOT NULL,
|
|
|
|
-- Identificativi documento
|
|
numero_protocollo VARCHAR(20) UNIQUE NOT NULL, -- PR-2025-001
|
|
data_protocollo DATE NOT NULL,
|
|
|
|
-- Categorizzazione
|
|
categoria_principale ENUM('amministrazione', 'contabilita', 'tecnico', 'condomini', 'unita', 'legale'),
|
|
categoria_secondaria VARCHAR(50),
|
|
categoria_terziaria VARCHAR(50),
|
|
|
|
-- Metadati documento
|
|
titolo VARCHAR(200) NOT NULL,
|
|
descrizione TEXT,
|
|
parole_chiave TEXT, -- Separata da virgole per ricerca
|
|
|
|
-- Tipologia e formato
|
|
tipo_documento ENUM('contratto', 'fattura', 'delibera', 'verbale', 'comunicazione', 'certificato', 'altro'),
|
|
formato_originale ENUM('pdf', 'doc', 'docx', 'xls', 'xlsx', 'img', 'altro'),
|
|
|
|
-- Archiviazione fisica
|
|
ubicazione_fisica VARCHAR(100), -- "Faldone 2025-A, Pos. 15"
|
|
codice_etichetta VARCHAR(20), -- Riferimento etichetta stampata
|
|
stato_fisico ENUM('presente', 'prestito', 'mancante', 'digitalizzato'),
|
|
|
|
-- Archiviazione digitale
|
|
percorso_file VARCHAR(500),
|
|
hash_file VARCHAR(64), -- SHA256 per integrità
|
|
dimensione_file BIGINT,
|
|
|
|
-- OCR e indicizzazione
|
|
testo_estratto_ocr LONGTEXT,
|
|
stato_ocr ENUM('pending', 'completed', 'failed', 'not_needed'),
|
|
confidence_ocr DECIMAL(5,2), -- Affidabilità OCR 0-100%
|
|
|
|
-- Relazioni
|
|
persona_id BIGINT NULL, -- Se riferito a persona specifica
|
|
unita_immobiliare_id BIGINT NULL, -- Se riferito a unità specifica
|
|
fornitore_id BIGINT NULL, -- Se riferito a fornitore
|
|
|
|
-- Scadenze e follow-up
|
|
data_scadenza DATE NULL,
|
|
giorni_preavviso_scadenza INT DEFAULT 30,
|
|
richiede_rinnovo BOOLEAN DEFAULT FALSE,
|
|
|
|
-- Sicurezza e privacy
|
|
livello_riservatezza ENUM('pubblico', 'riservato', 'confidenziale', 'segreto'),
|
|
accesso_limitato_ruoli JSON, -- Array ruoli autorizzati
|
|
|
|
-- Audit
|
|
creato_da_user_id BIGINT NOT NULL,
|
|
modificato_da_user_id BIGINT,
|
|
data_creazione TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
data_modifica TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
|
-- Note e commenti
|
|
note_interne TEXT,
|
|
commenti_storia JSON, -- Cronologia commenti/modifiche
|
|
|
|
FOREIGN KEY (stabile_id) REFERENCES stabili(id),
|
|
FOREIGN KEY (persona_id) REFERENCES persone(id),
|
|
FOREIGN KEY (unita_immobiliare_id) REFERENCES unita_immobiliari(id),
|
|
FOREIGN KEY (creato_da_user_id) REFERENCES users(id),
|
|
|
|
INDEX idx_protocollo (numero_protocollo),
|
|
INDEX idx_categoria (categoria_principale, categoria_secondaria),
|
|
INDEX idx_scadenze (data_scadenza),
|
|
FULLTEXT idx_ricerca (titolo, descrizione, parole_chiave, testo_estratto_ocr)
|
|
);
|
|
```
|
|
|
|
### 📄 Sistema OCR e Indicizzazione Automatica
|
|
|
|
#### Estrazione Automatica Metadati
|
|
```php
|
|
class DocumentOCRService {
|
|
|
|
/**
|
|
* Elaborazione automatica documento caricato
|
|
*/
|
|
public function processDocument($documentId) {
|
|
$documento = DocumentoArchivio::find($documentId);
|
|
$filePath = storage_path('app/' . $documento->percorso_file);
|
|
|
|
// 1. Estrazione testo OCR
|
|
$testoEstratto = $this->estraiTestoOCR($filePath);
|
|
|
|
// 2. Riconoscimento tipologia documento
|
|
$tipoDocumento = $this->riconosciTipoDocumento($testoEstratto);
|
|
|
|
// 3. Estrazione dati specifici
|
|
$datiEstratti = $this->estraiDatiSpecifici($testoEstratto, $tipoDocumento);
|
|
|
|
// 4. Categorizzazione automatica
|
|
$categoria = $this->categorizzaAutomaticamente($testoEstratto, $datiEstratti);
|
|
|
|
// 5. Generazione parole chiave
|
|
$paroleChiave = $this->generaParoleChiave($testoEstratto, $datiEstratti);
|
|
|
|
// 6. Aggiornamento database
|
|
$documento->update([
|
|
'testo_estratto_ocr' => $testoEstratto,
|
|
'tipo_documento' => $tipoDocumento,
|
|
'categoria_principale' => $categoria['principale'],
|
|
'categoria_secondaria' => $categoria['secondaria'],
|
|
'parole_chiave' => implode(', ', $paroleChiave),
|
|
'stato_ocr' => 'completed'
|
|
]);
|
|
|
|
return $datiEstratti;
|
|
}
|
|
|
|
/**
|
|
* Riconoscimento tipologia documento da contenuto
|
|
*/
|
|
private function riconosciTipoDocumento($testo) {
|
|
$patterns = [
|
|
'fattura' => [
|
|
'fattura', 'n\.', 'iva', 'imponibile', 'totale',
|
|
'partita iva', 'codice fiscale'
|
|
],
|
|
'contratto' => [
|
|
'contratto', 'sottoscritto', 'parti', 'condizioni',
|
|
'durata', 'corrispettivo'
|
|
],
|
|
'delibera' => [
|
|
'delibera', 'assemblea', 'condominio', 'votazione',
|
|
'favorevoli', 'contrari', 'astenuti'
|
|
],
|
|
'verbale' => [
|
|
'verbale', 'riunione', 'presenti', 'ordine del giorno',
|
|
'discussione', 'decisioni'
|
|
],
|
|
'certificato' => [
|
|
'certificato', 'attestato', 'conformità', 'norma',
|
|
'rilasciato', 'validità'
|
|
]
|
|
];
|
|
|
|
$scores = [];
|
|
foreach ($patterns as $tipo => $keywords) {
|
|
$score = 0;
|
|
foreach ($keywords as $keyword) {
|
|
if (preg_match('/' . preg_quote($keyword, '/') . '/i', $testo)) {
|
|
$score++;
|
|
}
|
|
}
|
|
$scores[$tipo] = $score;
|
|
}
|
|
|
|
return array_keys($scores, max($scores))[0] ?? 'altro';
|
|
}
|
|
|
|
/**
|
|
* Estrazione dati specifici per tipologia
|
|
*/
|
|
private function estraiDatiSpecifici($testo, $tipo) {
|
|
switch ($tipo) {
|
|
case 'fattura':
|
|
return $this->estraiFattura($testo);
|
|
case 'contratto':
|
|
return $this->estraiContratto($testo);
|
|
case 'delibera':
|
|
return $this->estraiDelibera($testo);
|
|
default:
|
|
return [];
|
|
}
|
|
}
|
|
|
|
private function estraiFattura($testo) {
|
|
$dati = [];
|
|
|
|
// Numero fattura
|
|
if (preg_match('/n\.?\s*(\d+\/?\d*)/i', $testo, $matches)) {
|
|
$dati['numero_fattura'] = $matches[1];
|
|
}
|
|
|
|
// Data fattura
|
|
if (preg_match('/(\d{1,2}\/\d{1,2}\/\d{4})/', $testo, $matches)) {
|
|
$dati['data_fattura'] = $matches[1];
|
|
}
|
|
|
|
// Importo totale
|
|
if (preg_match('/totale[\s:]*€?\s*(\d+[,.]?\d*)/i', $testo, $matches)) {
|
|
$dati['importo_totale'] = str_replace(',', '.', $matches[1]);
|
|
}
|
|
|
|
// Partita IVA fornitore
|
|
if (preg_match('/p\.?\s*iva[\s:]*(\d{11})/i', $testo, $matches)) {
|
|
$dati['partita_iva'] = $matches[1];
|
|
}
|
|
|
|
return $dati;
|
|
}
|
|
}
|
|
```
|
|
|
|
### 🏷️ Sistema Stampa Etichette Automatizzato
|
|
|
|
#### Generazione Etichette Faldoni
|
|
```php
|
|
class EtichettaService {
|
|
|
|
/**
|
|
* Genera etichetta per faldone/cartella
|
|
*/
|
|
public function generaEtichettaFaldone($stabileId, $categoria, $anno, $progressivo) {
|
|
$stabile = Stabile::find($stabileId);
|
|
$codiceEtichetta = $this->generaCodiceEtichetta($stabile, $categoria, $anno, $progressivo);
|
|
|
|
$etichettaData = [
|
|
'codice' => $codiceEtichetta,
|
|
'stabile' => [
|
|
'codice' => $stabile->codice_stabile,
|
|
'denominazione' => $stabile->denominazione,
|
|
'indirizzo' => $stabile->indirizzo_completo
|
|
],
|
|
'categoria' => $categoria,
|
|
'anno' => $anno,
|
|
'progressivo' => $progressivo,
|
|
'data_creazione' => now()->format('d/m/Y'),
|
|
'qr_code' => $this->generaQRCodeEtichetta($codiceEtichetta)
|
|
];
|
|
|
|
return $this->stampaEtichetta($etichettaData);
|
|
}
|
|
|
|
/**
|
|
* Template etichetta per faldone (formato Dymo/Brother)
|
|
*/
|
|
private function stampaEtichetta($data) {
|
|
$html = view('labels.faldone', $data)->render();
|
|
|
|
// Conversione in formato stampa (PDF o diretta stampante)
|
|
$pdf = app('dompdf.wrapper');
|
|
$pdf->loadHTML($html);
|
|
$pdf->setPaper([0, 0, 283.46, 141.73], 'portrait'); // 100x50mm
|
|
|
|
return $pdf->stream("etichetta-{$data['codice']}.pdf");
|
|
}
|
|
|
|
/**
|
|
* Genera codice etichetta standardizzato
|
|
*/
|
|
private function generaCodiceEtichetta($stabile, $categoria, $anno, $progressivo) {
|
|
$prefissoStabile = strtoupper(substr($stabile->codice_stabile, 0, 3));
|
|
$prefissoCategoria = $this->getPrefissoCategoria($categoria);
|
|
|
|
return sprintf(
|
|
'%s-%s-%d-%03d',
|
|
$prefissoStabile,
|
|
$prefissoCategoria,
|
|
$anno,
|
|
$progressivo
|
|
);
|
|
}
|
|
|
|
private function getPrefissoCategoria($categoria) {
|
|
$prefissi = [
|
|
'amministrazione' => 'ADM',
|
|
'contabilita' => 'CNT',
|
|
'tecnico' => 'TEC',
|
|
'condomini' => 'CON',
|
|
'unita' => 'UNI',
|
|
'legale' => 'LEG'
|
|
];
|
|
|
|
return $prefissi[$categoria] ?? 'GEN';
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Template Etichetta Blade
|
|
```blade
|
|
{{-- resources/views/labels/faldone.blade.php --}}
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<style>
|
|
@page { margin: 0; }
|
|
body {
|
|
margin: 0;
|
|
padding: 8px;
|
|
font-family: Arial, sans-serif;
|
|
font-size: 10px;
|
|
line-height: 1.2;
|
|
width: 84mm;
|
|
height: 42mm;
|
|
border: 1px solid #000;
|
|
box-sizing: border-box;
|
|
}
|
|
.header {
|
|
text-align: center;
|
|
font-weight: bold;
|
|
font-size: 11px;
|
|
margin-bottom: 4px;
|
|
border-bottom: 1px solid #000;
|
|
padding-bottom: 2px;
|
|
}
|
|
.codice {
|
|
font-size: 14px;
|
|
font-weight: bold;
|
|
text-align: center;
|
|
margin: 4px 0;
|
|
}
|
|
.stabile {
|
|
font-weight: bold;
|
|
margin-bottom: 2px;
|
|
}
|
|
.dettagli {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-top: 4px;
|
|
font-size: 9px;
|
|
}
|
|
.qr-code {
|
|
float: right;
|
|
margin-left: 8px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="header">NETGESCON - ARCHIVIO DOCUMENTALE</div>
|
|
|
|
<div class="codice">{{ $codice }}</div>
|
|
|
|
<div class="content">
|
|
<div class="qr-code">
|
|
{!! QrCode::size(30)->generate($qr_code) !!}
|
|
</div>
|
|
|
|
<div class="stabile">
|
|
{{ $stabile['denominazione'] }}
|
|
</div>
|
|
|
|
<div>{{ $stabile['indirizzo'] }}</div>
|
|
|
|
<div style="margin-top: 4px;">
|
|
<strong>{{ strtoupper($categoria) }}</strong> - Anno {{ $anno }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="dettagli">
|
|
<span>Prog. {{ str_pad($progressivo, 3, '0', STR_PAD_LEFT) }}</span>
|
|
<span>{{ $data_creazione }}</span>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
### 🔄 Sistema Passaggio Consegne Automatizzato
|
|
|
|
#### Generazione Pacchetto Consegne
|
|
```php
|
|
class PassaggioConsegneService {
|
|
|
|
/**
|
|
* Genera pacchetto completo per passaggio amministratore
|
|
*/
|
|
public function generaPacchettoConsegne($stabileId, $amministratoreUscente, $amministratoreEntrante) {
|
|
$stabile = Stabile::find($stabileId);
|
|
$dataPassaggio = now();
|
|
|
|
// 1. Crea cartella di export
|
|
$cartellaPacchetto = $this->creaCcartellaPacchetto($stabile, $dataPassaggio);
|
|
|
|
// 2. Genera HTML browser locale
|
|
$this->generaBrowserLocale($stabile, $cartellaPacchetto);
|
|
|
|
// 3. Esporta database archivio
|
|
$this->esportaDatabaseArchivio($stabile, $cartellaPacchetto);
|
|
|
|
// 4. Copia documenti digitali
|
|
$this->copiaDocumentiDigitali($stabile, $cartellaPacchetto);
|
|
|
|
// 5. Genera inventario cartaceo
|
|
$this->generaInventarioCartaceo($stabile, $cartellaPacchetto);
|
|
|
|
// 6. Crea indice generale
|
|
$this->creaIndiceGenerale($stabile, $cartellaPacchetto);
|
|
|
|
// 7. Genera ZIP finale
|
|
return $this->comprmiPacchetto($cartellaPacchetto);
|
|
}
|
|
|
|
/**
|
|
* Genera sito HTML locale per consultazione offline
|
|
*/
|
|
private function generaBrowserLocale($stabile, $cartellaBase) {
|
|
$datiCompleti = $this->raccogliDatiCompleti($stabile);
|
|
|
|
// Struttura HTML statica
|
|
$struttura = [
|
|
'index.html' => $this->generaPaginaPrincipale($stabile, $datiCompleti),
|
|
'anagrafica.html' => $this->generaPaginaAnagrafica($datiCompleti['persone']),
|
|
'unita.html' => $this->generaPaginaUnita($datiCompleti['unita']),
|
|
'documenti.html' => $this->generaPaginaDocumenti($datiCompleti['documenti']),
|
|
'contabilita.html' => $this->generaPaginaContabilita($datiCompleti['contabilita']),
|
|
'fornitori.html' => $this->generaPaginaFornitori($datiCompleti['fornitori']),
|
|
'assets/style.css' => $this->generaCSS(),
|
|
'assets/script.js' => $this->generaJavaScript(),
|
|
'assets/data.json' => json_encode($datiCompleti, JSON_PRETTY_PRINT)
|
|
];
|
|
|
|
foreach ($struttura as $file => $contenuto) {
|
|
$percorso = $cartellaBase . '/browser_locale/' . $file;
|
|
$this->scriviFile($percorso, $contenuto);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Inventario completo archivio cartaceo
|
|
*/
|
|
private function generaInventarioCartaceo($stabile, $cartellaBase) {
|
|
$documenti = DocumentoArchivio::where('stabile_id', $stabile->id)
|
|
->where('stato_fisico', '!=', 'digitalizzato')
|
|
->orderBy('ubicazione_fisica')
|
|
->get();
|
|
|
|
$inventario = [
|
|
'intestazione' => [
|
|
'stabile' => $stabile->denominazione,
|
|
'data_inventario' => now()->format('d/m/Y H:i'),
|
|
'totale_documenti' => $documenti->count()
|
|
],
|
|
'sezioni' => []
|
|
];
|
|
|
|
// Raggruppa per ubicazione fisica
|
|
$documentiPerUbicazione = $documenti->groupBy('ubicazione_fisica');
|
|
|
|
foreach ($documentiPerUbicazione as $ubicazione => $docsUbicazione) {
|
|
$inventario['sezioni'][] = [
|
|
'ubicazione' => $ubicazione,
|
|
'totale_documenti' => $docsUbicazione->count(),
|
|
'documenti' => $docsUbicazione->map(function($doc) {
|
|
return [
|
|
'protocollo' => $doc->numero_protocollo,
|
|
'data' => $doc->data_protocollo->format('d/m/Y'),
|
|
'titolo' => $doc->titolo,
|
|
'categoria' => $doc->categoria_principale,
|
|
'etichetta' => $doc->codice_etichetta
|
|
];
|
|
})->toArray()
|
|
];
|
|
}
|
|
|
|
// Genera PDF inventario
|
|
$pdf = app('dompdf.wrapper');
|
|
$html = view('reports.inventario_cartaceo', $inventario)->render();
|
|
$pdf->loadHTML($html);
|
|
|
|
$pathPdf = $cartellaBase . '/inventario_cartaceo.pdf';
|
|
file_put_contents($pathPdf, $pdf->output());
|
|
|
|
return $pathPdf;
|
|
}
|
|
|
|
/**
|
|
* Indice generale del pacchetto
|
|
*/
|
|
private function creaIndiceGenerale($stabile, $cartellaBase) {
|
|
$indice = [
|
|
'stabile' => $stabile,
|
|
'data_generazione' => now(),
|
|
'contenuto_pacchetto' => [
|
|
'browser_locale' => 'Consultazione offline di tutti i dati',
|
|
'database' => 'Export database completo (SQL + Excel)',
|
|
'documenti_digitali' => 'Copia di tutti i documenti digitali',
|
|
'inventario_cartaceo' => 'Elenco completo documenti fisici',
|
|
'etichette' => 'Template per ristampa etichette'
|
|
],
|
|
'istruzioni' => [
|
|
'Per consultare i dati offline aprire: browser_locale/index.html',
|
|
'I documenti digitali sono nella cartella: documenti/',
|
|
'Il database può essere importato in MySQL',
|
|
'L\'inventario cartaceo elenca tutti i faldoni fisici'
|
|
]
|
|
];
|
|
|
|
$readme = view('passaggio_consegne.readme', $indice)->render();
|
|
file_put_contents($cartellaBase . '/README.html', $readme);
|
|
}
|
|
}
|
|
```
|
|
|
|
### 🔍 Sistema Ricerca Avanzata
|
|
|
|
#### Motore di Ricerca Full-Text
|
|
```php
|
|
class RicercaDocumentiService {
|
|
|
|
/**
|
|
* Ricerca avanzata con filtri multipli
|
|
*/
|
|
public function ricercaAvanzata($parametri) {
|
|
$query = DocumentoArchivio::query();
|
|
|
|
// Ricerca full-text
|
|
if (!empty($parametri['testo'])) {
|
|
$query->whereRaw(
|
|
'MATCH(titolo, descrizione, parole_chiave, testo_estratto_ocr) AGAINST(? IN NATURAL LANGUAGE MODE)',
|
|
[$parametri['testo']]
|
|
);
|
|
}
|
|
|
|
// Filtri categoria
|
|
if (!empty($parametri['categoria'])) {
|
|
$query->where('categoria_principale', $parametri['categoria']);
|
|
}
|
|
|
|
// Filtri data
|
|
if (!empty($parametri['data_da'])) {
|
|
$query->where('data_protocollo', '>=', $parametri['data_da']);
|
|
}
|
|
if (!empty($parametri['data_a'])) {
|
|
$query->where('data_protocollo', '<=', $parametri['data_a']);
|
|
}
|
|
|
|
// Filtri scadenze
|
|
if (!empty($parametri['solo_scadenti'])) {
|
|
$query->where('data_scadenza', '<=', now()->addDays(30));
|
|
}
|
|
|
|
// Filtro ubicazione fisica
|
|
if (!empty($parametri['ubicazione'])) {
|
|
$query->where('ubicazione_fisica', 'LIKE', '%' . $parametri['ubicazione'] . '%');
|
|
}
|
|
|
|
return $query->with(['stabile', 'persona', 'unitaImmobiliare'])
|
|
->orderBy('data_protocollo', 'desc')
|
|
->paginate(20);
|
|
}
|
|
|
|
/**
|
|
* Suggerimenti ricerca intelligenti
|
|
*/
|
|
public function suggerimentiRicerca($termineIncompleto, $stabileId = null) {
|
|
$suggerimenti = [];
|
|
|
|
// Suggerimenti da titoli documenti
|
|
$titoliSuggiriti = DocumentoArchivio::where('stabile_id', $stabileId)
|
|
->where('titolo', 'LIKE', '%' . $termineIncompleto . '%')
|
|
->limit(5)
|
|
->pluck('titolo')
|
|
->unique()
|
|
->values();
|
|
|
|
// Suggerimenti da parole chiave
|
|
$paroleSuggerite = DocumentoArchivio::where('stabile_id', $stabileId)
|
|
->where('parole_chiave', 'LIKE', '%' . $termineIncompleto . '%')
|
|
->limit(5)
|
|
->pluck('parole_chiave')
|
|
->map(function($parole) use ($termineIncompleto) {
|
|
return collect(explode(',', $parole))
|
|
->map('trim')
|
|
->filter(function($parola) use ($termineIncompleto) {
|
|
return stripos($parola, $termineIncompleto) !== false;
|
|
});
|
|
})
|
|
->flatten()
|
|
->unique()
|
|
->take(5);
|
|
|
|
return [
|
|
'titoli' => $titoliSuggiriti,
|
|
'parole_chiave' => $paroleSuggerite
|
|
];
|
|
}
|
|
}
|
|
```
|
|
|
|
## 4. GESTIONE PROTOCOLLI AVANZATA
|
|
|
|
### 4.1 Sistema di Protocollo Interno
|
|
- **Numerazione Automatica Documenti**
|
|
- Protocollo in entrata/uscita
|
|
- Numerazione progressiva annuale
|
|
- Classificazione per tipologia
|
|
- Gestione fascicoli tematici
|
|
|
|
### 4.2 Ricerca Avanzata Documenti
|
|
- **Search Engine Interno**
|
|
- Ricerca full-text nei contenuti
|
|
- Filtri per data, tipologia, mittente
|
|
- Tag personalizzabili
|
|
- Ricerca per contenuto OCR
|
|
|
|
### 4.3 OCR e Indicizzazione Automatica
|
|
```php
|
|
// Implementazione OCR per documenti
|
|
class DocumentOCRService {
|
|
public function processDocument($filePath) {
|
|
// OCR del documento
|
|
$text = $this->extractTextFromPDF($filePath);
|
|
|
|
// Estrazione automatica metadati
|
|
$metadata = $this->extractMetadata($text);
|
|
|
|
// Salvataggio in database per ricerca
|
|
DocumentIndex::create([
|
|
'documento_id' => $documento->id,
|
|
'contenuto_ocr' => $text,
|
|
'keywords' => $metadata['keywords'],
|
|
'data_estrazione' => now()
|
|
]);
|
|
}
|
|
|
|
private function extractMetadata($text) {
|
|
$keywords = [];
|
|
|
|
// Estrazione date
|
|
preg_match_all('/\d{2}\/\d{2}\/\d{4}/', $text, $dates);
|
|
|
|
// Estrazione numeri di protocollo
|
|
preg_match_all('/prot\.?\s*n\.?\s*(\d+)/i', $text, $protocols);
|
|
|
|
// Estrazione importi
|
|
preg_match_all('/€\s*(\d+[.,]\d{2})/', $text, $amounts);
|
|
|
|
return [
|
|
'keywords' => $keywords,
|
|
'dates' => $dates[0],
|
|
'protocols' => $protocols[1],
|
|
'amounts' => $amounts[1]
|
|
];
|
|
}
|
|
}
|
|
```
|
|
|
|
## 5. PASSAGGIO CONSEGNE DIGITALE
|
|
|
|
### 5.1 Sistema di Handover
|
|
- **Moduli di Consegna Digitali**
|
|
- Checklist personalizzabili
|
|
- Foto prima/dopo
|
|
- Firme digitali
|
|
- Timestamp automatici
|
|
|
|
### 5.2 Inventario Digitale
|
|
- **Tracking Beni Mobili**
|
|
- Inventario attrezzature
|
|
- Stato di conservazione
|
|
- Storia manutenzioni
|
|
- Responsabilità di custodia
|
|
|
|
### 5.3 Knowledge Base
|
|
```php
|
|
// Sistema knowledge base per handover
|
|
class HandoverKnowledgeBase {
|
|
public function createHandoverPackage($stabileId, $tipoConsegna) {
|
|
$package = [
|
|
'documenti_essenziali' => $this->getDocumentiEssenziali($stabileId),
|
|
'chiavi_e_accessi' => $this->getChiaviEAccessi($stabileId),
|
|
'contatti_emergenza' => $this->getContattiEmergenza($stabileId),
|
|
'procedure_operative' => $this->getProcedureOperative($tipoConsegna),
|
|
'checklist' => $this->getChecklistConsegna($tipoConsegna)
|
|
];
|
|
|
|
return $package;
|
|
}
|
|
|
|
public function generateHandoverReport($handoverId) {
|
|
$handover = Handover::with(['checklist', 'photos', 'signatures'])->find($handoverId);
|
|
|
|
return view('reports.handover', [
|
|
'handover' => $handover,
|
|
'completionRate' => $this->calculateCompletionRate($handover),
|
|
'criticalIssues' => $this->identifyCriticalIssues($handover)
|
|
]);
|
|
}
|
|
}
|
|
```
|
|
|
|
## 6. IMPORTAZIONE DATI DA ALTRI GESTIONALI
|
|
|
|
### 6.1 Connettori Multi-Sistema
|
|
- **Supporto Formati Multipli**
|
|
- Database Access (.mdb/.accdb)
|
|
- File Excel (.xls/.xlsx)
|
|
- Database MySQL/PostgreSQL
|
|
- File CSV delimitati
|
|
- XML strutturati
|
|
- JSON da API REST
|
|
|
|
### 6.2 Mapping Intelligente
|
|
```php
|
|
// Sistema di mapping automatico campi
|
|
class DataImportMapper {
|
|
private $commonMappings = [
|
|
'nome' => ['nome', 'name', 'cognome_nome', 'denominazione'],
|
|
'codice_fiscale' => ['cf', 'cod_fiscale', 'codice_fiscale', 'fiscal_code'],
|
|
'indirizzo' => ['via', 'indirizzo', 'address', 'strada'],
|
|
'telefono' => ['tel', 'telefono', 'phone', 'cellulare']
|
|
];
|
|
|
|
public function autoMapFields($sourceColumns, $targetTable) {
|
|
$mapping = [];
|
|
|
|
foreach($this->commonMappings as $targetField => $variants) {
|
|
foreach($variants as $variant) {
|
|
if(in_array($variant, $sourceColumns)) {
|
|
$mapping[$targetField] = $variant;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $mapping;
|
|
}
|
|
|
|
public function validateAndTransform($data, $mapping, $validationRules) {
|
|
$transformedData = [];
|
|
|
|
foreach($data as $row) {
|
|
$transformedRow = [];
|
|
|
|
foreach($mapping as $targetField => $sourceField) {
|
|
$value = $row[$sourceField] ?? null;
|
|
|
|
// Applicazione regole di trasformazione
|
|
$transformedRow[$targetField] = $this->transformValue($value, $targetField);
|
|
}
|
|
|
|
// Validazione Laravel
|
|
$validator = Validator::make($transformedRow, $validationRules);
|
|
|
|
if($validator->passes()) {
|
|
$transformedData[] = $transformedRow;
|
|
} else {
|
|
// Log errori per revisione manuale
|
|
$this->logValidationError($transformedRow, $validator->errors());
|
|
}
|
|
}
|
|
|
|
return $transformedData;
|
|
}
|
|
}
|
|
```
|
|
|
|
### 6.3 Wizard di Importazione
|
|
- **Interfaccia Step-by-Step**
|
|
- Upload e preview file
|
|
- Mapping automatico/manuale campi
|
|
- Validazione e preview dati
|
|
- Importazione con progress bar
|
|
- Report errori e successi
|
|
|
|
### 6.4 Gestione Conflitti
|
|
```php
|
|
// Gestione conflitti durante importazione
|
|
class ConflictResolver {
|
|
public function resolveConflicts($importData, $existingData) {
|
|
$conflicts = [];
|
|
$resolutions = [];
|
|
|
|
foreach($importData as $record) {
|
|
$existing = $this->findExistingRecord($record, $existingData);
|
|
|
|
if($existing) {
|
|
$conflicts[] = [
|
|
'type' => 'duplicate',
|
|
'import_record' => $record,
|
|
'existing_record' => $existing,
|
|
'suggested_action' => $this->suggestAction($record, $existing)
|
|
];
|
|
}
|
|
}
|
|
|
|
return $conflicts;
|
|
}
|
|
|
|
private function suggestAction($import, $existing) {
|
|
// Logica per suggerire azione basata su differenze
|
|
$differences = array_diff_assoc($import, $existing->toArray());
|
|
|
|
if(empty($differences)) {
|
|
return 'skip'; // Identici
|
|
} elseif(count($differences) < 3) {
|
|
return 'update'; // Poche differenze, aggiorna
|
|
} else {
|
|
return 'review'; // Molte differenze, revisione manuale
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## 🎯 ROADMAP IMPLEMENTAZIONE
|
|
|
|
### Fase 1: Archivio Base (2-3 settimane)
|
|
- ✅ **Database schema** documenti con OCR
|
|
- ✅ **Upload e categorizzazione** automatica
|
|
- ✅ **Sistema protocolli** numerazione automatica
|
|
- ✅ **Ricerca full-text** base
|
|
|
|
### Fase 2: Stampa e Fisico (1-2 settimane)
|
|
- ✅ **Generazione etichette** Dymo/Brother
|
|
- ✅ **Template etichette** personalizzabili
|
|
- ✅ **Tracciamento ubicazioni** fisiche
|
|
- ✅ **Inventario automatico**
|
|
|
|
### Fase 3: OCR e AI (2-3 settimane)
|
|
- ✅ **OCR integrazione** (Tesseract/Google Vision)
|
|
- ✅ **Riconoscimento automatico** tipologie documento
|
|
- ✅ **Estrazione dati** intelligente
|
|
- ✅ **Suggerimenti ricerca** AI
|
|
|
|
### Fase 4: Passaggio Consegne (1-2 settimane)
|
|
- ✅ **Browser HTML** locale
|
|
- ✅ **Export completo** dati
|
|
- ✅ **Pacchetti automatici**
|
|
- ✅ **Inventari digitali**
|
|
|
|
### Fase 5: Importazione (1-2 settimane)
|
|
- ✅ **Importatori multi-sorgente**
|
|
- ✅ **Mapping configurabile**
|
|
- ✅ **Validazione dati** automatica
|
|
- ✅ **Report importazione**
|
|
|
|
Questa gestione documentale completa l'ecosistema NetGesCon con strumenti professionali per l'amministratore, rendendo il passaggio da cartaceo a digitale fluido e mantenendo la tracciabilità completa di tutti i documenti.
|