fix: refine archive label printing and public qr
This commit is contained in:
parent
8bc7783ce3
commit
49a49cdb08
|
|
@ -40,7 +40,8 @@ ## Struttura documentazione
|
|||
## Stato operativo recente
|
||||
|
||||
- Strumenti > Documenti apre ora una dashboard `Protocollo comunicazioni` con ricerca, filtri e anteprima PDF.
|
||||
- Le etichette archivio fisico generano il QR in locale e supportano i formati DYMO `11354` e `99014`.
|
||||
- Le etichette archivio fisico generano il QR in locale, supportano i formati DYMO `11354` e `99014` e usano un URL pubblico configurabile tramite `APP_PUBLIC_URL`.
|
||||
- La vista `Archivio digitale` usa ora una tabella a tutta larghezza con riepilogo rapido della cartella corrente.
|
||||
- La topbar contestuale Filament usa layout a capo, senza scrollbar orizzontale.
|
||||
|
||||
## Contribuire
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class DocumentiArchivio extends Page implements HasTable
|
|||
|
||||
public string $protocolloView = 'cards';
|
||||
|
||||
#[Url(as: 'folder')]
|
||||
#[Url( as : 'folder')]
|
||||
public ?int $currentFolderId = null;
|
||||
|
||||
public array $physicalArchiveForm = [];
|
||||
|
|
@ -292,15 +292,15 @@ public function table(Table $table): Table
|
|||
->hiddenLabel()
|
||||
->icon('heroicon-o-archive-box')
|
||||
->tooltip('Collega archivio fisico')
|
||||
->fillForm(fn(Documento $record): array => [
|
||||
'supporto_fisico' => data_get($record->metadati_archivio ?? [], 'supporto_fisico'),
|
||||
'modello_etichetta' => data_get($record->metadati_archivio ?? [], 'modello_etichetta', '11354'),
|
||||
'busta_numero' => data_get($record->metadati_archivio ?? [], 'busta_numero'),
|
||||
'fascicolo_numero' => data_get($record->metadati_archivio ?? [], 'fascicolo_numero'),
|
||||
'contenitore_numero' => data_get($record->metadati_archivio ?? [], 'contenitore_numero'),
|
||||
->fillForm(fn(Documento $record): array=> [
|
||||
'supporto_fisico' => data_get($record->metadati_archivio ?? [], 'supporto_fisico'),
|
||||
'modello_etichetta' => data_get($record->metadati_archivio ?? [], 'modello_etichetta', '11354'),
|
||||
'busta_numero' => data_get($record->metadati_archivio ?? [], 'busta_numero'),
|
||||
'fascicolo_numero' => data_get($record->metadati_archivio ?? [], 'fascicolo_numero'),
|
||||
'contenitore_numero' => data_get($record->metadati_archivio ?? [], 'contenitore_numero'),
|
||||
'periodo_riferimento_da' => data_get($record->metadati_archivio ?? [], 'periodo_da'),
|
||||
'periodo_riferimento_a' => data_get($record->metadati_archivio ?? [], 'periodo_a'),
|
||||
'watermark_predefinito' => (bool) data_get($record->metadati_archivio ?? [], 'watermark_predefinito', false),
|
||||
'periodo_riferimento_a' => data_get($record->metadati_archivio ?? [], 'periodo_a'),
|
||||
'watermark_predefinito' => (bool) data_get($record->metadati_archivio ?? [], 'watermark_predefinito', false),
|
||||
])
|
||||
->form([
|
||||
Select::make('supporto_fisico')
|
||||
|
|
@ -327,16 +327,16 @@ public function table(Table $table): Table
|
|||
->default(false),
|
||||
])
|
||||
->action(function (Documento $record, array $data): void {
|
||||
$meta = is_array($record->metadati_archivio ?? null) ? $record->metadati_archivio : [];
|
||||
$meta['supporto_fisico'] = $this->normalizeNullableText($data['supporto_fisico'] ?? null);
|
||||
$meta['modello_etichetta'] = $this->normalizeNullableText($data['modello_etichetta'] ?? null);
|
||||
$meta['contenitore_numero'] = $this->normalizeNullableText($data['contenitore_numero'] ?? null);
|
||||
$meta['busta_numero'] = $this->normalizeNullableText($data['busta_numero'] ?? null);
|
||||
$meta['fascicolo_numero'] = $this->normalizeNullableText($data['fascicolo_numero'] ?? null);
|
||||
$meta['periodo_da'] = $data['periodo_riferimento_da'] ?? null;
|
||||
$meta['periodo_a'] = $data['periodo_riferimento_a'] ?? null;
|
||||
$meta = is_array($record->metadati_archivio ?? null) ? $record->metadati_archivio : [];
|
||||
$meta['supporto_fisico'] = $this->normalizeNullableText($data['supporto_fisico'] ?? null);
|
||||
$meta['modello_etichetta'] = $this->normalizeNullableText($data['modello_etichetta'] ?? null);
|
||||
$meta['contenitore_numero'] = $this->normalizeNullableText($data['contenitore_numero'] ?? null);
|
||||
$meta['busta_numero'] = $this->normalizeNullableText($data['busta_numero'] ?? null);
|
||||
$meta['fascicolo_numero'] = $this->normalizeNullableText($data['fascicolo_numero'] ?? null);
|
||||
$meta['periodo_da'] = $data['periodo_riferimento_da'] ?? null;
|
||||
$meta['periodo_a'] = $data['periodo_riferimento_a'] ?? null;
|
||||
$meta['watermark_predefinito'] = (bool) ($data['watermark_predefinito'] ?? false);
|
||||
$record->metadati_archivio = $meta;
|
||||
$record->metadati_archivio = $meta;
|
||||
$record->save();
|
||||
|
||||
Notification::make()
|
||||
|
|
@ -434,6 +434,30 @@ public function getDigitalArchiveCurrentFolderProperty(): ?DocumentoArchivioCart
|
|||
return DocumentoArchivioCartella::query()->find($this->currentFolderId);
|
||||
}
|
||||
|
||||
public function getDigitalArchiveCurrentFolderDocumentCountProperty(): int
|
||||
{
|
||||
$stabileId = (int) ($this->resolveActiveStabile()?->id ?? 0);
|
||||
if ($stabileId <= 0 || ! $this->currentFolderId) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int) Documento::query()
|
||||
->where('stabile_id', $stabileId)
|
||||
->where('tipo_documento', 'fattura')
|
||||
->where('cartella_archivio_id', $this->currentFolderId)
|
||||
->count();
|
||||
}
|
||||
|
||||
public function getDigitalArchiveCurrentFolderHintProperty(): string
|
||||
{
|
||||
$folder = $this->digitalArchiveCurrentFolder;
|
||||
if (! $folder instanceof DocumentoArchivioCartella) {
|
||||
return 'Seleziona la cartella annuale per vedere l’elenco dei PDF già ordinati per protocollo e data.';
|
||||
}
|
||||
|
||||
return trim((string) ($folder->percorso_logico ?: $folder->nome));
|
||||
}
|
||||
|
||||
public function getDigitalArchiveFoldersProperty(): array
|
||||
{
|
||||
$stabile = $this->resolveActiveStabile();
|
||||
|
|
@ -448,10 +472,10 @@ public function getDigitalArchiveFoldersProperty(): array
|
|||
|
||||
if (! $this->currentFolderId) {
|
||||
return [[
|
||||
'id' => (int) $root->id,
|
||||
'nome' => (string) $root->nome,
|
||||
'id' => (int) $root->id,
|
||||
'nome' => (string) $root->nome,
|
||||
'descrizione' => 'Cartella fatture PDF',
|
||||
'conteggio' => (int) Documento::query()
|
||||
'conteggio' => (int) Documento::query()
|
||||
->where('stabile_id', $stabile->id)
|
||||
->where('tipo_documento', 'fattura')
|
||||
->count(),
|
||||
|
|
@ -464,11 +488,11 @@ public function getDigitalArchiveFoldersProperty(): array
|
|||
->orderBy('sort_order')
|
||||
->orderBy('nome')
|
||||
->get()
|
||||
->map(fn(DocumentoArchivioCartella $folder): array => [
|
||||
'id' => (int) $folder->id,
|
||||
'nome' => (string) $folder->nome,
|
||||
->map(fn(DocumentoArchivioCartella $folder): array=> [
|
||||
'id' => (int) $folder->id,
|
||||
'nome' => (string) $folder->nome,
|
||||
'descrizione' => (string) ($folder->percorso_logico ?: $folder->nome),
|
||||
'conteggio' => (int) $folder->documenti()->count(),
|
||||
'conteggio' => (int) $folder->documenti()->count(),
|
||||
])
|
||||
->all();
|
||||
}
|
||||
|
|
@ -476,7 +500,7 @@ public function getDigitalArchiveFoldersProperty(): array
|
|||
public function getDigitalArchiveBreadcrumbsProperty(): array
|
||||
{
|
||||
$items = [[
|
||||
'label' => $this->digitalArchiveRootLabel,
|
||||
'label' => $this->digitalArchiveRootLabel,
|
||||
'folder_id' => null,
|
||||
]];
|
||||
|
||||
|
|
@ -488,7 +512,7 @@ public function getDigitalArchiveBreadcrumbsProperty(): array
|
|||
$chain = collect();
|
||||
while ($current instanceof DocumentoArchivioCartella) {
|
||||
$chain->prepend([
|
||||
'label' => (string) $current->nome,
|
||||
'label' => (string) $current->nome,
|
||||
'folder_id' => (int) $current->id,
|
||||
]);
|
||||
$current = $current->parent;
|
||||
|
|
@ -546,26 +570,26 @@ public function getArchivioFisicoTypesProperty(): array
|
|||
public function getArchivioClasseOptions(): array
|
||||
{
|
||||
return [
|
||||
'libro_verbali' => 'Libro verbali',
|
||||
'planimetria_stabile' => 'Planimetria stabile',
|
||||
'elaborato_planimetrico' => 'Elaborato planimetrico',
|
||||
'libro_verbali' => 'Libro verbali',
|
||||
'planimetria_stabile' => 'Planimetria stabile',
|
||||
'elaborato_planimetrico' => 'Elaborato planimetrico',
|
||||
'scheda_anagrafica_condominiale' => 'Scheda anagrafica condominiale',
|
||||
'fascicolo_fabbricato' => 'Fascicolo fabbricato',
|
||||
'polizza_fabbricato' => 'Polizza global fabbricato',
|
||||
'certificazioni_impianti' => 'Certificazioni e conformita impianti',
|
||||
'decreti_vincolo' => 'Decreti di vincolo',
|
||||
'tabelle_millesimali' => 'Tabelle millesimali',
|
||||
'regolamento_condominio' => 'Regolamento di condominio',
|
||||
'rendiconti_approvati' => 'Rendiconti approvati',
|
||||
'raccolta_contratti' => 'Raccolta contratti stipulati',
|
||||
'servitu_diritti_terzi' => 'Servitu e diritti di terzi',
|
||||
'giustificativi_spesa' => 'Giustificativi di spesa',
|
||||
'scritture_contabili' => 'Scritture contabili e sostituto imposta',
|
||||
'codice_fiscale' => 'Codice fiscale',
|
||||
'partita_iva_amministratore' => 'Partita IVA amministratore',
|
||||
'chiavi_stabile' => 'Chiavi stabile',
|
||||
'timbri_stabile' => 'Timbri stabile',
|
||||
'altro' => 'Altro contenuto archivio',
|
||||
'fascicolo_fabbricato' => 'Fascicolo fabbricato',
|
||||
'polizza_fabbricato' => 'Polizza global fabbricato',
|
||||
'certificazioni_impianti' => 'Certificazioni e conformita impianti',
|
||||
'decreti_vincolo' => 'Decreti di vincolo',
|
||||
'tabelle_millesimali' => 'Tabelle millesimali',
|
||||
'regolamento_condominio' => 'Regolamento di condominio',
|
||||
'rendiconti_approvati' => 'Rendiconti approvati',
|
||||
'raccolta_contratti' => 'Raccolta contratti stipulati',
|
||||
'servitu_diritti_terzi' => 'Servitu e diritti di terzi',
|
||||
'giustificativi_spesa' => 'Giustificativi di spesa',
|
||||
'scritture_contabili' => 'Scritture contabili e sostituto imposta',
|
||||
'codice_fiscale' => 'Codice fiscale',
|
||||
'partita_iva_amministratore' => 'Partita IVA amministratore',
|
||||
'chiavi_stabile' => 'Chiavi stabile',
|
||||
'timbri_stabile' => 'Timbri stabile',
|
||||
'altro' => 'Altro contenuto archivio',
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -620,7 +644,7 @@ public function getPhysicalArchiveParentOptionsProperty(): array
|
|||
->whereIn('tipo_item', ['busta_trasparente', 'cartella_tre_lembi', 'faldone_anelli', 'faldone_lacci', 'scatola'])
|
||||
->orderBy('codice_univoco')
|
||||
->get(['id', 'codice_univoco', 'titolo'])
|
||||
->mapWithKeys(fn(DocumentoArchivioFisicoItem $item): array => [
|
||||
->mapWithKeys(fn(DocumentoArchivioFisicoItem $item): array=> [
|
||||
(string) $item->id => trim($item->codice_univoco . ' · ' . $item->titolo),
|
||||
])
|
||||
->all();
|
||||
|
|
@ -682,17 +706,17 @@ public function getPhysicalArchiveRowsProperty(): array
|
|||
->get()
|
||||
->map(function (DocumentoArchivioFisicoItem $item): array {
|
||||
return [
|
||||
'id' => (int) $item->id,
|
||||
'codice_univoco' => (string) $item->codice_univoco,
|
||||
'tipo' => (string) $item->tipo_label,
|
||||
'titolo' => (string) $item->titolo,
|
||||
'voce' => trim(collect([$item->voce_numero ? ('Voce ' . $item->voce_numero) : null, $item->voce_titolo])->filter()->implode(' · ')),
|
||||
'id' => (int) $item->id,
|
||||
'codice_univoco' => (string) $item->codice_univoco,
|
||||
'tipo' => (string) $item->tipo_label,
|
||||
'titolo' => (string) $item->titolo,
|
||||
'voce' => trim(collect([$item->voce_numero ? ('Voce ' . $item->voce_numero) : null, $item->voce_titolo])->filter()->implode(' · ')),
|
||||
'data_archiviazione' => $item->data_archiviazione?->format('d-m-Y') ?: '—',
|
||||
'parent' => $item->parent ? trim($item->parent->codice_univoco . ' · ' . $item->parent->titolo) : 'Radice',
|
||||
'percorso' => (string) $item->percorso_fisico,
|
||||
'documento' => $item->documento ? trim((string) ($item->documento->nome_file ?: $item->documento->nome ?: ('Documento #' . $item->documento->id))) : null,
|
||||
'ubicazione' => collect([$item->magazzino, $item->scaffale, $item->ripiano, $item->ubicazione_dettaglio])->filter()->implode(' · '),
|
||||
'modello_etichetta' => (string) ($item->modello_etichetta ?: '11354'),
|
||||
'parent' => $item->parent ? trim($item->parent->codice_univoco . ' · ' . $item->parent->titolo) : 'Radice',
|
||||
'percorso' => (string) $item->percorso_fisico,
|
||||
'documento' => $item->documento ? trim((string) ($item->documento->nome_file ?: $item->documento->nome ?: ('Documento #' . $item->documento->id))): null,
|
||||
'ubicazione' => collect([$item->magazzino, $item->scaffale, $item->ripiano, $item->ubicazione_dettaglio])->filter()->implode(' · '),
|
||||
'modello_etichetta' => (string) ($item->modello_etichetta ?: '11354'),
|
||||
];
|
||||
})
|
||||
->all();
|
||||
|
|
@ -712,10 +736,10 @@ public function getPhysicalArchiveContainerTreeProperty(): array
|
|||
->get()
|
||||
->map(function (DocumentoArchivioFisicoItem $item): array {
|
||||
return [
|
||||
'id' => (int) $item->id,
|
||||
'id' => (int) $item->id,
|
||||
'codice_univoco' => (string) $item->codice_univoco,
|
||||
'titolo' => (string) $item->titolo,
|
||||
'tipo' => (string) $item->tipo_label,
|
||||
'titolo' => (string) $item->titolo,
|
||||
'tipo' => (string) $item->tipo_label,
|
||||
'children_count' => (int) $item->children()->count(),
|
||||
];
|
||||
})
|
||||
|
|
@ -736,7 +760,7 @@ public function savePhysicalArchiveEntry(): void
|
|||
return;
|
||||
}
|
||||
|
||||
$titolo = trim((string) ($this->physicalArchiveForm['titolo'] ?? ''));
|
||||
$titolo = trim((string) ($this->physicalArchiveForm['titolo'] ?? ''));
|
||||
$tipoItem = trim((string) ($this->physicalArchiveForm['tipo_item'] ?? 'documento'));
|
||||
if ($titolo === '' || ! array_key_exists($tipoItem, DocumentoArchivioFisicoItem::TIPI)) {
|
||||
Notification::make()->title('Titolo e tipologia sono obbligatori')->danger()->send();
|
||||
|
|
@ -744,7 +768,7 @@ public function savePhysicalArchiveEntry(): void
|
|||
}
|
||||
|
||||
$parentId = is_numeric($this->physicalArchiveForm['parent_id'] ?? null) ? (int) $this->physicalArchiveForm['parent_id'] : null;
|
||||
$parent = $parentId
|
||||
$parent = $parentId
|
||||
? DocumentoArchivioFisicoItem::query()->where('stabile_id', $stabileId)->find($parentId)
|
||||
: null;
|
||||
|
||||
|
|
@ -756,34 +780,34 @@ public function savePhysicalArchiveEntry(): void
|
|||
}
|
||||
|
||||
$item = DocumentoArchivioFisicoItem::query()->create([
|
||||
'stabile_id' => $stabileId,
|
||||
'parent_id' => $parent?->id,
|
||||
'documento_id' => is_numeric($this->physicalArchiveForm['documento_id'] ?? null) ? (int) $this->physicalArchiveForm['documento_id'] : null,
|
||||
'voce_numero' => $voceNumero,
|
||||
'voce_titolo' => $voceTitolo !== '' ? $voceTitolo : null,
|
||||
'tipo_item' => $tipoItem,
|
||||
'titolo' => $titolo,
|
||||
'note' => $this->normalizeNullableText($this->physicalArchiveForm['note'] ?? null),
|
||||
'data_archiviazione' => $this->physicalArchiveForm['data_archiviazione'] ?? now()->toDateString(),
|
||||
'supporto_fisico' => $this->normalizeNullableText($this->physicalArchiveForm['supporto_fisico'] ?? null),
|
||||
'modello_etichetta' => $this->normalizeNullableText($this->physicalArchiveForm['modello_etichetta'] ?? null) ?: '11354',
|
||||
'magazzino' => $this->normalizeNullableText($this->physicalArchiveForm['magazzino'] ?? null),
|
||||
'scaffale' => $this->normalizeNullableText($this->physicalArchiveForm['scaffale'] ?? null),
|
||||
'ripiano' => $this->normalizeNullableText($this->physicalArchiveForm['ripiano'] ?? null),
|
||||
'stabile_id' => $stabileId,
|
||||
'parent_id' => $parent?->id,
|
||||
'documento_id' => is_numeric($this->physicalArchiveForm['documento_id'] ?? null) ? (int) $this->physicalArchiveForm['documento_id'] : null,
|
||||
'voce_numero' => $voceNumero,
|
||||
'voce_titolo' => $voceTitolo !== '' ? $voceTitolo : null,
|
||||
'tipo_item' => $tipoItem,
|
||||
'titolo' => $titolo,
|
||||
'note' => $this->normalizeNullableText($this->physicalArchiveForm['note'] ?? null),
|
||||
'data_archiviazione' => $this->physicalArchiveForm['data_archiviazione'] ?? now()->toDateString(),
|
||||
'supporto_fisico' => $this->normalizeNullableText($this->physicalArchiveForm['supporto_fisico'] ?? null),
|
||||
'modello_etichetta' => $this->normalizeNullableText($this->physicalArchiveForm['modello_etichetta'] ?? null) ?: '11354',
|
||||
'magazzino' => $this->normalizeNullableText($this->physicalArchiveForm['magazzino'] ?? null),
|
||||
'scaffale' => $this->normalizeNullableText($this->physicalArchiveForm['scaffale'] ?? null),
|
||||
'ripiano' => $this->normalizeNullableText($this->physicalArchiveForm['ripiano'] ?? null),
|
||||
'ubicazione_dettaglio' => $this->normalizeNullableText($this->physicalArchiveForm['ubicazione_dettaglio'] ?? null),
|
||||
'created_by' => (int) $user->id,
|
||||
'updated_by' => (int) $user->id,
|
||||
'metadati' => [
|
||||
'created_by' => (int) $user->id,
|
||||
'updated_by' => (int) $user->id,
|
||||
'metadati' => [
|
||||
'origine_digitale' => is_numeric($this->physicalArchiveForm['documento_id'] ?? null),
|
||||
],
|
||||
]);
|
||||
|
||||
if ($item->documento instanceof Documento) {
|
||||
$meta = is_array($item->documento->metadati_archivio ?? null) ? $item->documento->metadati_archivio : [];
|
||||
$meta = is_array($item->documento->metadati_archivio ?? null) ? $item->documento->metadati_archivio : [];
|
||||
$meta['physical_archive_item_id'] = (int) $item->id;
|
||||
$meta['physical_archive_code'] = (string) $item->codice_univoco;
|
||||
$meta['contenitore_numero'] = $parent?->codice_univoco;
|
||||
$meta['supporto_fisico'] = $item->supporto_fisico;
|
||||
$meta['physical_archive_code'] = (string) $item->codice_univoco;
|
||||
$meta['contenitore_numero'] = $parent?->codice_univoco;
|
||||
$meta['supporto_fisico'] = $item->supporto_fisico;
|
||||
$item->documento->forceFill([
|
||||
'metadati_archivio' => $meta,
|
||||
])->save();
|
||||
|
|
@ -827,7 +851,7 @@ public function movePhysicalArchiveItem(): void
|
|||
}
|
||||
|
||||
$source->forceFill([
|
||||
'parent_id' => (int) $target->id,
|
||||
'parent_id' => (int) $target->id,
|
||||
'updated_by' => Auth::id(),
|
||||
])->save();
|
||||
|
||||
|
|
@ -902,8 +926,8 @@ public function getProtocolloComunicazioniStatsProperty(): array
|
|||
$query = $this->getProtocolloComunicazioniQuery();
|
||||
|
||||
return [
|
||||
'totale' => (clone $query)->count(),
|
||||
'con_pdf' => (clone $query)
|
||||
'totale' => (clone $query)->count(),
|
||||
'con_pdf' => (clone $query)
|
||||
->where(function (Builder $builder): void {
|
||||
$builder->whereNotNull('percorso_file')
|
||||
->orWhereNotNull('path_file');
|
||||
|
|
@ -912,7 +936,7 @@ public function getProtocolloComunicazioniStatsProperty(): array
|
|||
'questo_mese' => (clone $query)
|
||||
->whereDate('created_at', '>=', now()->startOfMonth()->toDateString())
|
||||
->count(),
|
||||
'categorie' => (clone $query)
|
||||
'categorie' => (clone $query)
|
||||
->reorder()
|
||||
->select('tipo_documento')
|
||||
->distinct()
|
||||
|
|
@ -935,13 +959,13 @@ public function getProtocolloComunicazioniCategoryOptionsProperty(): array
|
|||
$labels = $this->getCategorieOptions();
|
||||
|
||||
return collect($visibleCategories)
|
||||
->mapWithKeys(fn(string $value): array => [$value => $labels[$value] ?? Str::headline($value)])
|
||||
->mapWithKeys(fn(string $value): array=> [$value => $labels[$value] ?? Str::headline($value)])
|
||||
->all();
|
||||
}
|
||||
|
||||
public function getProtocolloComunicazioniRowsProperty(): array
|
||||
{
|
||||
$limit = $this->protocolloView === 'list' ? 80 : 18;
|
||||
$limit = $this->protocolloView === 'list' ? 80 : 18;
|
||||
$labels = $this->getCategorieOptions();
|
||||
|
||||
return $this->getProtocolloComunicazioniQuery()
|
||||
|
|
@ -950,23 +974,23 @@ public function getProtocolloComunicazioniRowsProperty(): array
|
|||
->get()
|
||||
->map(function (Documento $documento) use ($labels): array {
|
||||
$openUrl = route('filament.documenti.download', $documento) . '?inline=1';
|
||||
$tipo = trim((string) ($documento->tipo_documento ?? ''));
|
||||
$tipo = trim((string) ($documento->tipo_documento ?? ''));
|
||||
|
||||
return [
|
||||
'id' => (int) $documento->id,
|
||||
'protocollo' => trim((string) ($documento->numero_protocollo ?: 'DOC-' . $documento->id)),
|
||||
'titolo' => trim((string) ($documento->nome ?: $documento->nome_file ?: ('Documento #' . $documento->id))),
|
||||
'descrizione' => trim((string) ($documento->descrizione ?: Str::limit((string) ($documento->contenuto_ocr ?? ''), 140, '...'))),
|
||||
'categoria' => $labels[$tipo] ?? ($tipo !== '' ? Str::headline($tipo) : 'Protocollo generale'),
|
||||
'fornitore' => trim((string) ($documento->fornitore ?? '')),
|
||||
'data' => $documento->data_documento?->format('d/m/Y')
|
||||
'id' => (int) $documento->id,
|
||||
'protocollo' => trim((string) ($documento->numero_protocollo ?: 'DOC-' . $documento->id)),
|
||||
'titolo' => trim((string) ($documento->nome ?: $documento->nome_file ?: ('Documento #' . $documento->id))),
|
||||
'descrizione' => trim((string) ($documento->descrizione ?: Str::limit((string) ($documento->contenuto_ocr ?? ''), 140, '...'))),
|
||||
'categoria' => $labels[$tipo] ?? ($tipo !== '' ? Str::headline($tipo) : 'Protocollo generale'),
|
||||
'fornitore' => trim((string) ($documento->fornitore ?? '')),
|
||||
'data' => $documento->data_documento?->format('d/m/Y')
|
||||
?: ($documento->created_at?->format('d/m/Y') ?: '—'),
|
||||
'cartella' => trim((string) ($documento->cartellaArchivio?->nome ?? 'Protocollo generale')),
|
||||
'cartella' => trim((string) ($documento->cartellaArchivio?->nome ?? 'Protocollo generale')),
|
||||
'archive_reference' => trim((string) ($documento->archive_reference ?? '')),
|
||||
'has_pdf' => $this->hasPdf($documento),
|
||||
'preview_url' => $openUrl,
|
||||
'download_url' => route('filament.documenti.download', $documento),
|
||||
'print_url' => $openUrl,
|
||||
'has_pdf' => $this->hasPdf($documento),
|
||||
'preview_url' => $openUrl,
|
||||
'download_url' => route('filament.documenti.download', $documento),
|
||||
'print_url' => $openUrl,
|
||||
];
|
||||
})
|
||||
->all();
|
||||
|
|
@ -1160,20 +1184,20 @@ private function getStabiliOptions(): array
|
|||
private function initializePhysicalArchiveForms(): void
|
||||
{
|
||||
$this->physicalArchiveForm = [
|
||||
'data_archiviazione' => now()->toDateString(),
|
||||
'voce_numero' => null,
|
||||
'voce_titolo' => '',
|
||||
'titolo' => '',
|
||||
'note' => '',
|
||||
'tipo_item' => 'documento',
|
||||
'supporto_fisico' => 'Busta trasparente con anelli da archiviare',
|
||||
'parent_id' => null,
|
||||
'documento_id' => null,
|
||||
'magazzino' => '',
|
||||
'scaffale' => '',
|
||||
'ripiano' => '',
|
||||
'data_archiviazione' => now()->toDateString(),
|
||||
'voce_numero' => null,
|
||||
'voce_titolo' => '',
|
||||
'titolo' => '',
|
||||
'note' => '',
|
||||
'tipo_item' => 'documento',
|
||||
'supporto_fisico' => 'Busta trasparente con anelli da archiviare',
|
||||
'parent_id' => null,
|
||||
'documento_id' => null,
|
||||
'magazzino' => '',
|
||||
'scaffale' => '',
|
||||
'ripiano' => '',
|
||||
'ubicazione_dettaglio' => '',
|
||||
'modello_etichetta' => '11354',
|
||||
'modello_etichetta' => '11354',
|
||||
];
|
||||
|
||||
$this->physicalArchiveMoveForm = [
|
||||
|
|
@ -1279,7 +1303,7 @@ private function syncInvoiceFolders(): void
|
|||
->orderBy('id')
|
||||
->get(['id', 'data_documento', 'cartella_archivio_id'])
|
||||
->each(function (Documento $documento) use ($stabile, $root): void {
|
||||
$year = (int) (($documento->data_documento?->format('Y')) ?: now()->format('Y'));
|
||||
$year = (int) (($documento->data_documento?->format('Y')) ?: now()->format('Y'));
|
||||
$yearFolder = $this->ensureInvoiceYearFolder((int) $stabile->id, $root, $year);
|
||||
if (! $yearFolder instanceof DocumentoArchivioCartella) {
|
||||
return;
|
||||
|
|
@ -1313,16 +1337,16 @@ private function ensureInvoiceRootFolder(int $stabileId): ?DocumentoArchivioCart
|
|||
return DocumentoArchivioCartella::query()->firstOrCreate(
|
||||
[
|
||||
'stabile_id' => $stabileId,
|
||||
'parent_id' => null,
|
||||
'slug' => 'fatture-pdf',
|
||||
'parent_id' => null,
|
||||
'slug' => 'fatture-pdf',
|
||||
],
|
||||
[
|
||||
'nome' => 'fatture_PDF',
|
||||
'codice' => 'FATTURE',
|
||||
'percorso_logico' => 'fatture_PDF',
|
||||
'nome' => 'fatture_PDF',
|
||||
'codice' => 'FATTURE',
|
||||
'percorso_logico' => 'fatture_PDF',
|
||||
'visibility_scope' => 'interno',
|
||||
'created_by' => Auth::id(),
|
||||
'updated_by' => Auth::id(),
|
||||
'created_by' => Auth::id(),
|
||||
'updated_by' => Auth::id(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
@ -1336,16 +1360,16 @@ private function ensureInvoiceYearFolder(int $stabileId, DocumentoArchivioCartel
|
|||
return DocumentoArchivioCartella::query()->firstOrCreate(
|
||||
[
|
||||
'stabile_id' => $stabileId,
|
||||
'parent_id' => (int) $root->id,
|
||||
'slug' => (string) $year,
|
||||
'parent_id' => (int) $root->id,
|
||||
'slug' => (string) $year,
|
||||
],
|
||||
[
|
||||
'nome' => (string) $year,
|
||||
'codice' => 'FATT-' . $year,
|
||||
'percorso_logico' => trim((string) $root->nome) . '/' . $year,
|
||||
'nome' => (string) $year,
|
||||
'codice' => 'FATT-' . $year,
|
||||
'percorso_logico' => trim((string) $root->nome) . '/' . $year,
|
||||
'visibility_scope' => 'interno',
|
||||
'created_by' => Auth::id(),
|
||||
'updated_by' => Auth::id(),
|
||||
'created_by' => Auth::id(),
|
||||
'updated_by' => Auth::id(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
@ -1604,8 +1628,8 @@ private function createDocumentoFromForm(array $data, bool $isDemo): void
|
|||
$note = 'Faldone: ' . $faldone;
|
||||
}
|
||||
|
||||
$tipologia = $this->mapCategoriaToTipologia($categoria);
|
||||
$archivioClasse = trim((string) ($data['archivio_classe'] ?? ''));
|
||||
$tipologia = $this->mapCategoriaToTipologia($categoria);
|
||||
$archivioClasse = trim((string) ($data['archivio_classe'] ?? ''));
|
||||
$archiveReference = $this->buildArchiveReference($stabileId, $categoria, $year, $data);
|
||||
|
||||
$doc = Documento::query()->create([
|
||||
|
|
@ -1632,18 +1656,18 @@ private function createDocumentoFromForm(array $data, bool $isDemo): void
|
|||
'estensione' => 'pdf',
|
||||
'dimensione_file' => $size,
|
||||
'metadati_archivio' => [
|
||||
'classe' => $archivioClasse !== '' ? $archivioClasse : null,
|
||||
'classe_label' => $this->getArchivioClasseOptions()[$archivioClasse] ?? null,
|
||||
'supporto_fisico' => $this->normalizeNullableText($data['supporto_fisico'] ?? null),
|
||||
'modello_etichetta' => $this->normalizeNullableText($data['modello_etichetta'] ?? null),
|
||||
'busta_numero' => $this->normalizeNullableText($data['busta_numero'] ?? null),
|
||||
'fascicolo_numero' => $this->normalizeNullableText($data['fascicolo_numero'] ?? null),
|
||||
'classe' => $archivioClasse !== '' ? $archivioClasse : null,
|
||||
'classe_label' => $this->getArchivioClasseOptions()[$archivioClasse] ?? null,
|
||||
'supporto_fisico' => $this->normalizeNullableText($data['supporto_fisico'] ?? null),
|
||||
'modello_etichetta' => $this->normalizeNullableText($data['modello_etichetta'] ?? null),
|
||||
'busta_numero' => $this->normalizeNullableText($data['busta_numero'] ?? null),
|
||||
'fascicolo_numero' => $this->normalizeNullableText($data['fascicolo_numero'] ?? null),
|
||||
'contenitore_numero' => $this->normalizeNullableText($data['contenitore_numero'] ?? null),
|
||||
'periodo_da' => $data['periodo_riferimento_da'] ?? null,
|
||||
'periodo_a' => $data['periodo_riferimento_a'] ?? null,
|
||||
'codice_etichetta' => $archiveReference,
|
||||
'qr_ready' => true,
|
||||
'nfc_ready' => true,
|
||||
'periodo_da' => $data['periodo_riferimento_da'] ?? null,
|
||||
'periodo_a' => $data['periodo_riferimento_a'] ?? null,
|
||||
'codice_etichetta' => $archiveReference,
|
||||
'qr_ready' => true,
|
||||
'nfc_ready' => true,
|
||||
],
|
||||
'visibility_scope' => (string) ($data['visibility_scope'] ?? 'interno'),
|
||||
'visibility_roles' => $this->normalizeStringArray($data['visibility_roles'] ?? []),
|
||||
|
|
@ -1807,18 +1831,18 @@ private function generateProtocollo(string $categoria, int $year): string
|
|||
|
||||
private function buildArchiveReference(int $stabileId, string $categoria, int $year, array $data): string
|
||||
{
|
||||
$stabile = Stabile::query()->find($stabileId);
|
||||
$stabile = Stabile::query()->find($stabileId);
|
||||
$mnemonic = $this->mnemonicCodeHint;
|
||||
|
||||
if (! $mnemonic && $stabile instanceof Stabile) {
|
||||
$label = Str::upper((string) ($stabile->denominazione ?? 'STABILE'));
|
||||
$letters = preg_replace('/[^A-Z]/', '', Str::ascii($label)) ?: 'STB';
|
||||
$digits = preg_replace('/\D/', '', $label);
|
||||
$label = Str::upper((string) ($stabile->denominazione ?? 'STABILE'));
|
||||
$letters = preg_replace('/[^A-Z]/', '', Str::ascii($label)) ?: 'STB';
|
||||
$digits = preg_replace('/\D/', '', $label);
|
||||
$mnemonic = str_pad(substr($letters, 0, 3), 3, 'X') . str_pad(substr($digits !== '' ? $digits : (string) $stabile->id, -5), 5, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
$contenitore = $this->normalizeNullableText($data['contenitore_numero'] ?? $data['faldone'] ?? null) ?: '00';
|
||||
$fascicolo = $this->normalizeNullableText($data['fascicolo_numero'] ?? null) ?: '00/' . $year;
|
||||
$fascicolo = $this->normalizeNullableText($data['fascicolo_numero'] ?? null) ?: '00/' . $year;
|
||||
|
||||
return implode(' · ', array_filter([
|
||||
$mnemonic ?: 'STB00000',
|
||||
|
|
@ -1846,8 +1870,8 @@ private function buildDocumentStorageFilename(string $categoria, mixed $dataDocu
|
|||
? ($fornitore !== '' ? $fornitore : $titolo)
|
||||
: $titolo;
|
||||
|
||||
$safeLabel = trim((string) Str::of($sourceLabel)->ascii()->replaceMatches('/[^A-Za-z0-9]+/', ' ')->squish());
|
||||
$safeLabel = str_replace(' ', '_', Str::limit($safeLabel !== '' ? $safeLabel : 'documento', 60, ''));
|
||||
$safeLabel = trim((string) Str::of($sourceLabel)->ascii()->replaceMatches('/[^A-Za-z0-9]+/', ' ')->squish());
|
||||
$safeLabel = str_replace(' ', '_', Str::limit($safeLabel !== '' ? $safeLabel : 'documento', 60, ''));
|
||||
$safeProtocollo = str_replace(['/', ' '], '-', trim($protocollo));
|
||||
|
||||
if ($categoria === 'fattura') {
|
||||
|
|
|
|||
|
|
@ -164,9 +164,19 @@ public function etichettaArchivioFisico(Request $request, DocumentoArchivioFisic
|
|||
abort(403);
|
||||
}
|
||||
|
||||
$format = $request->query('format', '11354');
|
||||
$format = in_array($format, ['11354', '99014'], true) ? $format : '11354';
|
||||
$defaults = $this->getDymoDefaultsForUser($format);
|
||||
$format = $request->query('format', '11354');
|
||||
$format = in_array($format, ['11354', '99014'], true) ? $format : '11354';
|
||||
$labelPrinterMode = $request->boolean('autoprint')
|
||||
|| $request->has('dymo_fix')
|
||||
|| $request->has('dymo_rot')
|
||||
|| $request->has('dymo_dx')
|
||||
|| $request->has('dymo_dy');
|
||||
$defaults = $labelPrinterMode ? $this->getDymoDefaultsForUser($format) : [
|
||||
'fix' => false,
|
||||
'rot' => 0,
|
||||
'dx' => 0.0,
|
||||
'dy' => 0.0,
|
||||
];
|
||||
|
||||
$dymoFix = $request->query('dymo_fix');
|
||||
if ($dymoFix === null) {
|
||||
|
|
@ -181,24 +191,38 @@ public function etichettaArchivioFisico(Request $request, DocumentoArchivioFisic
|
|||
$dymoDx = $request->has('dymo_dx') ? (float) $request->query('dymo_dx') : (float) ($defaults['dx'] ?? 0);
|
||||
$dymoDy = $request->has('dymo_dy') ? (float) $request->query('dymo_dy') : (float) ($defaults['dy'] ?? 0);
|
||||
|
||||
if ($format === '11354' && $dymoFix) {
|
||||
if (! in_array($dymoRot, [-90, 90], true)) {
|
||||
$dymoRot = -90;
|
||||
}
|
||||
} else {
|
||||
if (! in_array($dymoRot, [-180, -90, 0, 90, 180], true)) {
|
||||
$dymoRot = 0;
|
||||
}
|
||||
}
|
||||
|
||||
$dymoDx = max(-15.0, min(15.0, $dymoDx));
|
||||
$dymoDy = max(-15.0, min(15.0, $dymoDy));
|
||||
|
||||
return view('filament.print.etichetta-archivio-fisico', [
|
||||
'item' => $item->load(['parent', 'children', 'stabile', 'documento']),
|
||||
'format' => $format,
|
||||
'dymoFix' => $dymoFix,
|
||||
'dymoRot' => $dymoRot,
|
||||
'dymoDx' => $dymoDx,
|
||||
'dymoDy' => $dymoDy,
|
||||
'publicUrl' => $item->qr_code_data,
|
||||
'item' => $item->load(['parent', 'children', 'stabile', 'documento']),
|
||||
'format' => $format,
|
||||
'labelPrinterMode' => $labelPrinterMode,
|
||||
'dymoFix' => $dymoFix,
|
||||
'dymoRot' => $dymoRot,
|
||||
'dymoDx' => $dymoDx,
|
||||
'dymoDy' => $dymoDy,
|
||||
'publicUrl' => $item->buildPublicQrUrl(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function showPublicArchivioFisicoItem(Request $request, DocumentoArchivioFisicoItem $item)
|
||||
{
|
||||
if (! $request->hasValidSignature()) {
|
||||
if (! $request->hasValidSignature(false)) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$chain = collect();
|
||||
$chain = collect();
|
||||
$current = $item;
|
||||
while ($current instanceof DocumentoArchivioFisicoItem) {
|
||||
$chain->prepend($current);
|
||||
|
|
@ -206,7 +230,7 @@ public function showPublicArchivioFisicoItem(Request $request, DocumentoArchivio
|
|||
}
|
||||
|
||||
return view('filament.pages.strumenti.archivio-fisico-show', [
|
||||
'item' => $item->load(['children.children', 'stabile', 'documento', 'parent']),
|
||||
'item' => $item->load(['children.children', 'stabile', 'documento', 'parent']),
|
||||
'chain' => $chain,
|
||||
]);
|
||||
}
|
||||
|
|
@ -301,7 +325,7 @@ private function buildWatermarkedPdf(string $inputPath, Documento $documento): ?
|
|||
@unlink($outputPath);
|
||||
|
||||
$postScript = '<< /BeginPage { gsave 0.90 setgray /Helvetica-Bold findfont 14 scalefont setfont 140 90 translate 38 rotate 0 0 moveto ('
|
||||
. $this->escapePostScriptString($watermarkText)
|
||||
. $this->escapePostScriptString($watermarkText)
|
||||
. ') show grestore } >> setpagedevice';
|
||||
|
||||
$process = new Process([
|
||||
|
|
@ -332,7 +356,7 @@ private function buildWatermarkedPdf(string $inputPath, Documento $documento): ?
|
|||
private function buildWatermarkText(Documento $documento): string
|
||||
{
|
||||
$archiveReference = trim((string) ($documento->archive_reference ?? ''));
|
||||
$meta = is_array($documento->metadati_archivio ?? null) ? $documento->metadati_archivio : [];
|
||||
$meta = is_array($documento->metadati_archivio ?? null) ? $documento->metadati_archivio : [];
|
||||
|
||||
$physicalParts = array_filter([
|
||||
trim((string) ($meta['supporto_fisico'] ?? '')),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
|
@ -42,19 +41,19 @@ class DocumentoArchivioFisicoItem extends Model
|
|||
|
||||
protected $casts = [
|
||||
'data_archiviazione' => 'date',
|
||||
'sort_order' => 'integer',
|
||||
'metadati' => 'array',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
'sort_order' => 'integer',
|
||||
'metadati' => 'array',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
];
|
||||
|
||||
public const TIPI = [
|
||||
'documento' => 'Documento cartaceo',
|
||||
'busta_trasparente' => 'Busta trasparente con anelli da archiviare',
|
||||
'documento' => 'Documento cartaceo',
|
||||
'busta_trasparente' => 'Busta trasparente con anelli da archiviare',
|
||||
'cartella_tre_lembi' => 'Cartella a tre lembi',
|
||||
'faldone_anelli' => 'Faldone con anelli',
|
||||
'faldone_lacci' => 'Faldone con lacci',
|
||||
'scatola' => 'Scatola',
|
||||
'faldone_anelli' => 'Faldone con anelli',
|
||||
'faldone_lacci' => 'Faldone con lacci',
|
||||
'scatola' => 'Scatola',
|
||||
];
|
||||
|
||||
public function stabile(): BelongsTo
|
||||
|
|
@ -94,7 +93,7 @@ public function getTipoLabelAttribute(): string
|
|||
|
||||
public function getPercorsoFisicoAttribute(): string
|
||||
{
|
||||
$parts = [];
|
||||
$parts = [];
|
||||
$current = $this;
|
||||
|
||||
while ($current instanceof self) {
|
||||
|
|
@ -107,15 +106,15 @@ public function getPercorsoFisicoAttribute(): string
|
|||
|
||||
public function getQrCodeImageAttribute(): string
|
||||
{
|
||||
$data = trim((string) $this->qr_code_data);
|
||||
$data = $this->buildPublicQrUrl();
|
||||
|
||||
if ($data !== '' && class_exists(\chillerlan\QRCode\QROptions::class) && class_exists(\chillerlan\QRCode\QRCode::class)) {
|
||||
$options = new \chillerlan\QRCode\QROptions([
|
||||
'outputType' => \chillerlan\QRCode\QRCode::OUTPUT_MARKUP_SVG,
|
||||
'eccLevel' => \chillerlan\QRCode\QRCode::ECC_M,
|
||||
'addQuietzone' => true,
|
||||
'outputType' => \chillerlan\QRCode\QRCode::OUTPUT_MARKUP_SVG,
|
||||
'eccLevel' => \chillerlan\QRCode\QRCode::ECC_M,
|
||||
'addQuietzone' => true,
|
||||
'quietzoneSize' => 1,
|
||||
'connectPaths' => true,
|
||||
'connectPaths' => true,
|
||||
]);
|
||||
|
||||
$svg = (new \chillerlan\QRCode\QRCode($options))->render($data);
|
||||
|
|
@ -142,11 +141,11 @@ public function getQrCodeImageAttribute(): string
|
|||
|
||||
public static function generateCodice(int $stabileId): string
|
||||
{
|
||||
$prefix = 'AF-' . $stabileId . '-';
|
||||
$prefix = 'AF-' . $stabileId . '-';
|
||||
$sequence = 1;
|
||||
|
||||
do {
|
||||
$code = $prefix . str_pad((string) $sequence, 4, '0', STR_PAD_LEFT);
|
||||
$code = $prefix . str_pad((string) $sequence, 4, '0', STR_PAD_LEFT);
|
||||
$exists = self::query()->where('codice_univoco', $code)->exists();
|
||||
$sequence++;
|
||||
} while ($exists);
|
||||
|
|
@ -156,15 +155,26 @@ public static function generateCodice(int $stabileId): string
|
|||
|
||||
public function refreshQrCodeData(): void
|
||||
{
|
||||
$url = URL::temporarySignedRoute(
|
||||
$this->forceFill([
|
||||
'qr_code_data' => $this->buildPublicQrUrl(),
|
||||
])->saveQuietly();
|
||||
}
|
||||
|
||||
public function buildPublicQrUrl(): string
|
||||
{
|
||||
$relativeSignedPath = URL::temporarySignedRoute(
|
||||
'public.archivio-fisico.show',
|
||||
now()->addYears(5),
|
||||
['item' => $this->id]
|
||||
['item' => $this->id],
|
||||
false,
|
||||
);
|
||||
|
||||
$this->forceFill([
|
||||
'qr_code_data' => $url,
|
||||
])->saveQuietly();
|
||||
$baseUrl = rtrim((string) config('app.public_url', config('app.url')), '/');
|
||||
if ($baseUrl === '') {
|
||||
return $relativeSignedPath;
|
||||
}
|
||||
|
||||
return $baseUrl . '/' . ltrim($relativeSignedPath, '/');
|
||||
}
|
||||
|
||||
protected static function booted(): void
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ public function panel(Panel $panel): Panel
|
|||
$user = Auth::user();
|
||||
|
||||
return $user instanceof User
|
||||
&& $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore']);
|
||||
&& $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore']);
|
||||
}),
|
||||
NavigationItem::make('Cellulare / WebApp')
|
||||
->group('NetGescon')
|
||||
|
|
@ -70,7 +70,7 @@ public function panel(Panel $panel): Panel
|
|||
$user = Auth::user();
|
||||
|
||||
return $user instanceof User
|
||||
&& $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore']);
|
||||
&& $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore']);
|
||||
}),
|
||||
NavigationItem::make('Ticket Mobile')
|
||||
->group('NetGescon')
|
||||
|
|
@ -80,7 +80,7 @@ public function panel(Panel $panel): Panel
|
|||
$user = Auth::user();
|
||||
|
||||
return $user instanceof User
|
||||
&& $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore']);
|
||||
&& $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore']);
|
||||
}),
|
||||
NavigationItem::make('Scheda Stabile')
|
||||
->group('NetGescon')
|
||||
|
|
@ -90,7 +90,7 @@ public function panel(Panel $panel): Panel
|
|||
$user = Auth::user();
|
||||
|
||||
return $user instanceof User
|
||||
&& $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore']);
|
||||
&& $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore']);
|
||||
}),
|
||||
NavigationItem::make('Catalogo Fornitore')
|
||||
->group('Fornitore')
|
||||
|
|
@ -102,8 +102,8 @@ public function panel(Panel $panel): Panel
|
|||
return $user instanceof User && $user->hasRole('fornitore');
|
||||
}),
|
||||
])
|
||||
->discoverResources(in: app_path('Filament/Resources'), for : 'App\\Filament\\Resources')
|
||||
->discoverPages(in: app_path('Filament/Pages'), for : 'App\\Filament\\Pages')
|
||||
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
|
||||
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
|
||||
->pages([
|
||||
Dashboard::class,
|
||||
SupportoDashboard::class,
|
||||
|
|
@ -111,7 +111,7 @@ public function panel(Panel $panel): Panel
|
|||
PrimaNotaModifica::class,
|
||||
ContoMastrino::class,
|
||||
])
|
||||
->discoverWidgets(in: app_path('Filament/Widgets'), for : 'App\\Filament\\Widgets')
|
||||
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
|
||||
->widgets([
|
||||
AccountWidget::class,
|
||||
TicketMobileOverview::class,
|
||||
|
|
@ -171,4 +171,4 @@ protected function resolveSupplierCatalogUrl(): string
|
|||
|
||||
return TicketOperativi::getUrl(panel: 'admin-filament');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@
|
|||
|
||||
'url' => env('APP_URL', 'http://localhost'),
|
||||
|
||||
'public_url' => env('APP_PUBLIC_URL', env('APP_URL', 'http://localhost')),
|
||||
|
||||
'asset_url' => env('ASSET_URL'),
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -53,6 +53,12 @@ ## Archivio fisico ed etichette
|
|||
- stampa etichette nei formati `DYMO 11354` e `DYMO 99014`;
|
||||
- pagina pubblica firmata per dettaglio contenitore o documento tramite QR.
|
||||
|
||||
Taratura operativa corrente:
|
||||
|
||||
- `11354`: layout orizzontale principale con correzione verticale leggera verso l'alto;
|
||||
- `11354 verticale`: variante dedicata ai driver che ruotano l'etichetta, ora con rotazione corretta lato stampa;
|
||||
- `99014`: QR spostato in basso a destra per liberare spazio scrivibile nella parte alta.
|
||||
|
||||
Per i documenti digitali resta disponibile anche l'assegnazione dei metadati fisici principali:
|
||||
|
||||
- supporto fisico;
|
||||
|
|
@ -75,6 +81,26 @@ ## QR locale sulle etichette
|
|||
|
||||
Questo elimina la dipendenza da URL esterni per il QR e rende piu stabile il preview di stampa.
|
||||
|
||||
## QR pubblico e dominio esterno
|
||||
|
||||
Il QR dell'archivio fisico non deve piu dipendere dall'IP interno del server.
|
||||
|
||||
Implementazione attuale:
|
||||
|
||||
- la firma della route pubblica viene generata in forma relativa;
|
||||
- il link finale viene costruito usando `APP_PUBLIC_URL`, con fallback su `APP_URL`;
|
||||
- la validazione avviene lato controller con firma relativa, cosi il QR resta valido anche dietro dominio pubblico o reverse proxy.
|
||||
|
||||
Configurazione richiesta in ambiente:
|
||||
|
||||
1. impostare `APP_PUBLIC_URL` con il dominio realmente raggiungibile dall'esterno;
|
||||
2. lasciare `APP_URL` per il contesto applicativo interno se serve;
|
||||
3. rigenerare o aggiornare i QR gia salvati solo se si vuole riallineare anche il dato persistito nel database.
|
||||
|
||||
Esempio:
|
||||
|
||||
- `APP_PUBLIC_URL=https://netgescon.example.it`
|
||||
|
||||
## Stampa PDF e watermark archivio
|
||||
|
||||
Per i documenti PDF sono ora previste due aperture distinte dalla tab archivio:
|
||||
|
|
@ -208,6 +234,24 @@ ## Scanner Epson da pagina web
|
|||
2. upload/ingestione rapida in NetGescon;
|
||||
3. OCR e archiviazione automatica.
|
||||
|
||||
Per il modello Epson WF-C5710 la direzione pratica da testare non e il browser puro, ma una di queste due:
|
||||
|
||||
- scansione verso cartella di rete osservata da NetGescon;
|
||||
- piccolo bridge locale o di rete che espone una chiamata HTTP interna e usa il canale scanner disponibile sul dispositivo.
|
||||
|
||||
Questo evita il vincolo dei browser su TWAIN/WIA e mantiene il flusso pilotabile dalla pagina archivio.
|
||||
|
||||
## Archivio digitale
|
||||
|
||||
La vista `Strumenti > Documenti > Archivio digitale` e stata alleggerita per lavorare meglio dentro la cartella corrente.
|
||||
|
||||
Stato attuale:
|
||||
|
||||
- breadcrumb e cartelle restano in alto;
|
||||
- la tabella documenti ora usa tutta la larghezza utile;
|
||||
- le informazioni laterali sono state compattate in tre card sintetiche sopra la tabella;
|
||||
- la pagina mostra nome cartella attiva, numero documenti del livello e stato operativo digitale/fisico.
|
||||
|
||||
## Dymo LAN 11354
|
||||
|
||||
Domani si puo provare una stampa etichetta su rete con formato `11354` usando:
|
||||
|
|
|
|||
|
|
@ -283,24 +283,26 @@ class="rounded-2xl border border-slate-200 bg-white p-4 text-left shadow-sm tran
|
|||
@endforelse
|
||||
</div>
|
||||
|
||||
<div class="grid gap-3 xl:grid-cols-[minmax(0,1fr)_320px]">
|
||||
<div>
|
||||
{{ $this->table }}
|
||||
<div class="grid gap-3 lg:grid-cols-3">
|
||||
<div class="rounded-xl border bg-white p-4">
|
||||
<div class="text-xs uppercase tracking-wide text-slate-500">Cartella attiva</div>
|
||||
<div class="mt-2 text-sm font-semibold text-slate-900">{{ $this->digitalArchiveCurrentFolder?->nome ?? 'Nessuna cartella selezionata' }}</div>
|
||||
<div class="mt-1 text-xs text-slate-500">{{ $this->digitalArchiveCurrentFolderHint }}</div>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<div class="rounded-xl border bg-white p-4">
|
||||
<div class="text-sm font-semibold text-slate-900">Flusso digitale e fisico</div>
|
||||
<div class="mt-2 text-sm text-slate-600">Usa l'icona archivio per assegnare contenitore, busta, fascicolo ed etichetta del documento cartaceo. La posizione verra poi richiamata anche nel watermark PDF.</div>
|
||||
</div>
|
||||
<div class="rounded-xl border bg-white p-4">
|
||||
<div class="text-sm font-semibold text-slate-900">Stampa</div>
|
||||
<div class="mt-2 text-sm text-slate-600">Icona stampante: PDF senza timbro. Icona duplicato: PDF con watermark archivio digitale e fisico, se la codifica e presente.</div>
|
||||
</div>
|
||||
<div class="rounded-xl border bg-white p-4">
|
||||
<div class="text-sm font-semibold text-slate-900">Scanner</div>
|
||||
<div class="mt-2 text-sm text-slate-600">La struttura attuale e pronta per il prossimo aggancio scanner: acquisizione PDF, naming ordinato, assegnazione immediata a cartella digitale e posizione fisica.</div>
|
||||
</div>
|
||||
<div class="rounded-xl border bg-white p-4">
|
||||
<div class="text-xs uppercase tracking-wide text-slate-500">Documenti nel livello</div>
|
||||
<div class="mt-2 text-2xl font-semibold text-slate-900">{{ $this->digitalArchiveCurrentFolderDocumentCount }}</div>
|
||||
<div class="mt-1 text-xs text-slate-500">Solo fatture PDF collegate alla cartella corrente.</div>
|
||||
</div>
|
||||
<div class="rounded-xl border bg-white p-4">
|
||||
<div class="text-xs uppercase tracking-wide text-slate-500">Operatività</div>
|
||||
<div class="mt-2 text-sm font-semibold text-slate-900">Archivio digitale e fisico allineati</div>
|
||||
<div class="mt-1 text-xs text-slate-500">Da qui puoi aprire, stampare, watermarkare e assegnare subito la collocazione cartacea.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ $this->table }}
|
||||
</div>
|
||||
</div>
|
||||
</x-filament::section>
|
||||
|
|
@ -509,8 +511,9 @@ class="rounded-2xl border border-slate-200 bg-white p-4 text-left shadow-sm tran
|
|||
@endif
|
||||
</div>
|
||||
<div class="flex shrink-0 flex-wrap items-center gap-2">
|
||||
<a href="{{ route('filament.archivio-fisico.etichetta', ['item' => $row['id']]) }}?format=11354&autoprint=1" target="_blank" class="inline-flex items-center rounded-lg border border-slate-300 bg-white px-3 py-2 text-xs font-medium text-slate-700 shadow-sm hover:bg-slate-50">11354</a>
|
||||
<a href="{{ route('filament.archivio-fisico.etichetta', ['item' => $row['id']]) }}?format=99014&autoprint=1" target="_blank" class="inline-flex items-center rounded-lg border border-slate-300 bg-white px-3 py-2 text-xs font-medium text-slate-700 shadow-sm hover:bg-slate-50">99014</a>
|
||||
<a href="{{ route('filament.archivio-fisico.etichetta', ['item' => $row['id']]) }}?format=11354&autoprint=1&dymo_fix=0&dymo_rot=0&dymo_dx=0&dymo_dy=-1" target="_blank" class="inline-flex items-center rounded-lg border border-slate-300 bg-white px-3 py-2 text-xs font-medium text-slate-700 shadow-sm hover:bg-slate-50">11354</a>
|
||||
<a href="{{ route('filament.archivio-fisico.etichetta', ['item' => $row['id']]) }}?format=11354&autoprint=1&dymo_fix=1&dymo_rot=90&dymo_dx=0&dymo_dy=0" target="_blank" class="inline-flex items-center rounded-lg border border-slate-300 bg-white px-3 py-2 text-xs font-medium text-slate-700 shadow-sm hover:bg-slate-50">11354 verticale</a>
|
||||
<a href="{{ route('filament.archivio-fisico.etichetta', ['item' => $row['id']]) }}?format=99014&autoprint=1&dymo_fix=0&dymo_rot=0&dymo_dx=3.5&dymo_dy=3.5" target="_blank" class="inline-flex items-center rounded-lg border border-slate-300 bg-white px-3 py-2 text-xs font-medium text-slate-700 shadow-sm hover:bg-slate-50">99014</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,17 +10,60 @@
|
|||
@php($dymoDx = isset($dymoDx) ? (float) $dymoDx : 0.0)
|
||||
@php($dymoDy = isset($dymoDy) ? (float) $dymoDy : 0.0)
|
||||
@php($autoPrint = request()->has('autoprint') && filter_var(request()->query('autoprint'), FILTER_VALIDATE_BOOL))
|
||||
@php($labelPrinterMode = isset($labelPrinterMode) ? (bool) $labelPrinterMode : false)
|
||||
@php($pageWidth = $fmt === '99014' ? '54mm' : '57mm')
|
||||
@php($pageHeight = $fmt === '99014' ? '101mm' : '32mm')
|
||||
<style>
|
||||
@page { margin: 0; size: {{ $fmt === '99014' ? '54mm 101mm' : '57mm 32mm' }}; }
|
||||
@page {
|
||||
margin: {{ $labelPrinterMode ? '0' : '10mm' }};
|
||||
size: {{ $labelPrinterMode ? ($fmt === '99014' ? '54mm 101mm' : '57mm 32mm') : 'A4 portrait' }};
|
||||
}
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: {{ $fmt === '99014' ? '54mm' : '57mm' }};
|
||||
height: {{ $fmt === '99014' ? '101mm' : '32mm' }};
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
background: {{ $labelPrinterMode ? 'white' : '#eef1f5' }};
|
||||
}
|
||||
.sheet {
|
||||
box-sizing: border-box;
|
||||
width: {{ $labelPrinterMode ? $pageWidth : '210mm' }};
|
||||
min-height: {{ $labelPrinterMode ? $pageHeight : '297mm' }};
|
||||
padding: {{ $labelPrinterMode ? '0' : '12mm' }};
|
||||
display: flex;
|
||||
align-items: {{ $labelPrinterMode ? 'stretch' : 'flex-start' }};
|
||||
justify-content: {{ $labelPrinterMode ? 'stretch' : 'flex-start' }};
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
overflow: hidden;
|
||||
-webkit-print-color-adjust: exact;
|
||||
print-color-adjust: exact;
|
||||
break-inside: avoid;
|
||||
break-after: avoid-page;
|
||||
page-break-inside: avoid;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
.label {
|
||||
box-sizing: border-box;
|
||||
width: {{ $pageWidth }};
|
||||
height: {{ $pageHeight }};
|
||||
padding: 0;
|
||||
flex: 0 0 auto;
|
||||
position: relative;
|
||||
}
|
||||
.calib {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
.label-content {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: {{ $fmt === '99014' ? '3.5mm' : '2mm' }};
|
||||
}
|
||||
.label { box-sizing: border-box; width: 100%; height: 100%; padding: {{ $fmt === '99014' ? '3.5mm' : '2mm' }}; }
|
||||
.panel {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
|
@ -32,7 +75,16 @@
|
|||
padding: {{ $fmt === '99014' ? '2.4mm' : '1.5mm' }};
|
||||
align-items: stretch;
|
||||
}
|
||||
.panel.panel-99014 {
|
||||
display: block;
|
||||
position: relative;
|
||||
padding: 2.8mm;
|
||||
}
|
||||
.content { min-width: 0; }
|
||||
.panel.panel-99014 .content {
|
||||
padding-right: 0;
|
||||
padding-bottom: 21mm;
|
||||
}
|
||||
.code { font-weight: 800; font-size: {{ $fmt === '99014' ? '14px' : '10px' }}; letter-spacing: 0.4px; }
|
||||
.title { font-weight: 700; font-size: {{ $fmt === '99014' ? '13px' : '9px' }}; line-height: 1.15; margin-top: 1mm; }
|
||||
.meta { font-size: {{ $fmt === '99014' ? '10px' : '7px' }}; line-height: 1.2; color: #222; margin-top: 1.1mm; }
|
||||
|
|
@ -46,6 +98,14 @@
|
|||
padding: 0.4mm;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.panel.panel-99014 .qr {
|
||||
position: absolute;
|
||||
right: 2.8mm;
|
||||
bottom: 2.8mm;
|
||||
width: 18mm;
|
||||
height: 18mm;
|
||||
padding: 0;
|
||||
}
|
||||
.qr svg,
|
||||
.qr img {
|
||||
display: block;
|
||||
|
|
@ -58,13 +118,61 @@
|
|||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
.path { font-size: {{ $fmt === '99014' ? '9px' : '6.6px' }}; line-height: 1.2; color: #444; margin-top: 1mm; word-break: break-word; }
|
||||
@media screen {
|
||||
body {
|
||||
padding: 12mm 0;
|
||||
}
|
||||
.sheet {
|
||||
box-shadow: 0 10px 28px rgba(15, 23, 42, 0.18);
|
||||
}
|
||||
}
|
||||
@media print {
|
||||
html, body { width: 100%; height: 100%; }
|
||||
.label { transform: translate({{ $dymoDx }}mm, {{ $dymoDy }}mm) rotate({{ $dymoRot }}deg); transform-origin: top left; }
|
||||
@if($fmt === '11354' && $dymoFix)
|
||||
@page { size: 32mm 57mm; }
|
||||
html, body { width: 32mm; height: 57mm; }
|
||||
.label { width: 57mm; height: 32mm; transform: translate({{ $dymoDx }}mm, calc(57mm + {{ $dymoDy }}mm)) rotate(-90deg); transform-origin: top left; }
|
||||
html, body {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
background: white;
|
||||
}
|
||||
.sheet {
|
||||
box-shadow: none;
|
||||
margin: 0;
|
||||
padding: {{ $labelPrinterMode ? '0' : '12mm' }};
|
||||
min-height: auto;
|
||||
}
|
||||
@if($labelPrinterMode)
|
||||
html, body {
|
||||
overflow: hidden;
|
||||
}
|
||||
.sheet {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.calib {
|
||||
transform: translate({{ $dymoDx }}mm, {{ $dymoDy }}mm) rotate({{ $dymoRot }}deg);
|
||||
}
|
||||
@if($fmt === '11354' && $dymoFix)
|
||||
@page { size: 32mm 57mm; }
|
||||
html, body {
|
||||
width: 32mm;
|
||||
height: 57mm;
|
||||
}
|
||||
.sheet {
|
||||
width: 32mm;
|
||||
height: 57mm;
|
||||
padding: 0;
|
||||
}
|
||||
.calib {
|
||||
width: 57mm;
|
||||
height: 32mm;
|
||||
@if($dymoRot === 90)
|
||||
transform: translate({{ $dymoDx }}mm, {{ $dymoDy }}mm) translateX(32mm) rotate(90deg);
|
||||
@else
|
||||
transform: translate({{ $dymoDx }}mm, {{ $dymoDy }}mm) translateY(57mm) rotate(-90deg);
|
||||
@endif
|
||||
}
|
||||
@endif
|
||||
@else
|
||||
.calib { transform: none !important; }
|
||||
@endif
|
||||
}
|
||||
</style>
|
||||
|
|
@ -76,27 +184,39 @@
|
|||
@endif
|
||||
</head>
|
||||
<body>
|
||||
<div class="label">
|
||||
<div class="panel">
|
||||
<div class="content">
|
||||
<div class="code">{{ $item->codice_univoco }}</div>
|
||||
<div class="title">{{ $item->titolo }}</div>
|
||||
<div class="meta">
|
||||
<div><strong>Tipo:</strong> {{ $item->tipo_label }}</div>
|
||||
@if($item->voce_numero || $item->voce_titolo)
|
||||
<div><strong>Voce:</strong> {{ $item->voce_numero ? 'Voce ' . $item->voce_numero : '' }} {{ $item->voce_titolo }}</div>
|
||||
@endif
|
||||
@if($item->parent)
|
||||
<div><strong>Dentro:</strong> {{ $item->parent->codice_univoco }}</div>
|
||||
@endif
|
||||
@if($item->documento)
|
||||
<div><strong>Digitale:</strong> {{ $item->documento->nome_file ?: $item->documento->nome }}</div>
|
||||
@endif
|
||||
<div class="sheet">
|
||||
<div class="label">
|
||||
<div class="calib">
|
||||
<div class="label-content">
|
||||
<div @class(['panel', 'panel-99014' => $fmt === '99014'])>
|
||||
<div class="content">
|
||||
<div class="code">{{ $item->codice_univoco }}</div>
|
||||
<div class="title">{{ $item->titolo }}</div>
|
||||
<div class="meta">
|
||||
@if($item->stabile)
|
||||
<div><strong>Stabile:</strong> {{ $item->stabile->denominazione }}</div>
|
||||
@endif
|
||||
<div><strong>Tipo:</strong> {{ $item->tipo_label }}</div>
|
||||
@if($item->voce_numero || $item->voce_titolo)
|
||||
<div><strong>Voce:</strong> {{ $item->voce_numero ? 'Voce ' . $item->voce_numero : '' }} {{ $item->voce_titolo }}</div>
|
||||
@endif
|
||||
@if($item->data_archiviazione)
|
||||
<div><strong>Data:</strong> {{ $item->data_archiviazione->format('d-m-Y') }}</div>
|
||||
@endif
|
||||
@if($item->parent)
|
||||
<div><strong>Dentro:</strong> {{ $item->parent->codice_univoco }}</div>
|
||||
@endif
|
||||
@if($item->documento)
|
||||
<div><strong>Digitale:</strong> {{ $item->documento->nome_file ?: $item->documento->nome }}</div>
|
||||
@endif
|
||||
</div>
|
||||
<div class="path">{{ $item->percorso_fisico }}</div>
|
||||
</div>
|
||||
<div class="qr">
|
||||
{!! $item->qr_code_image !!}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="path">{{ $item->percorso_fisico }}</div>
|
||||
</div>
|
||||
<div class="qr">
|
||||
{!! $item->qr_code_image !!}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -727,7 +727,7 @@
|
|||
// --- AUTHENTICATION ROUTES ---
|
||||
require __DIR__ . '/auth.php';
|
||||
|
||||
Route::middleware('signed')->get('/archivio-fisico/{item}', [\App\Http\Controllers\Filament\DocumentiPrintController::class, 'showPublicArchivioFisicoItem'])
|
||||
Route::get('/archivio-fisico/{item}', [\App\Http\Controllers\Filament\DocumentiPrintController::class, 'showPublicArchivioFisicoItem'])
|
||||
->name('public.archivio-fisico.show');
|
||||
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user