netgescon-day0/app/Models/DocumentoArchivioFisicoItem.php

224 lines
6.8 KiB
PHP

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Str;
class DocumentoArchivioFisicoItem extends Model
{
use HasFactory;
protected $table = 'documento_archivio_fisico_items';
protected $fillable = [
'stabile_id',
'parent_id',
'documento_id',
'codice_univoco',
'qr_code_data',
'nfc_reference',
'voce_numero',
'voce_titolo',
'tipo_item',
'titolo',
'note',
'data_archiviazione',
'supporto_fisico',
'modello_etichetta',
'magazzino',
'scaffale',
'ripiano',
'ubicazione_dettaglio',
'stato',
'sort_order',
'metadati',
'created_by',
'updated_by',
];
protected $casts = [
'data_archiviazione' => 'date',
'sort_order' => 'integer',
'metadati' => 'array',
'created_at' => 'datetime',
'updated_at' => 'datetime',
];
public const TIPI = [
'documento' => 'Documento cartaceo',
'foglio' => 'Foglio / documento singolo',
'cartellina' => 'Cartellina',
'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',
'scaffale' => 'Scaffale',
'posto' => 'Posto / ubicazione finale',
];
public function stabile(): BelongsTo
{
return $this->belongsTo(Stabile::class, 'stabile_id');
}
public function parent(): BelongsTo
{
return $this->belongsTo(self::class, 'parent_id');
}
public function children(): HasMany
{
return $this->hasMany(self::class, 'parent_id')->orderBy('sort_order')->orderBy('titolo');
}
public function documento(): BelongsTo
{
return $this->belongsTo(Documento::class, 'documento_id');
}
public function creator(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by');
}
public function updater(): BelongsTo
{
return $this->belongsTo(User::class, 'updated_by');
}
public function getTipoLabelAttribute(): string
{
return self::TIPI[$this->tipo_item] ?? $this->tipo_item;
}
public function getPercorsoFisicoAttribute(): string
{
$parts = [];
$current = $this;
while ($current instanceof self) {
$parts[] = trim($current->codice_univoco . ' ' . $current->titolo);
$current = $current->parent;
}
return implode(' / ', array_reverse(array_filter($parts)));
}
public function getQrCodeImageAttribute(): string
{
$data = $this->buildQrPayload();
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,
'quietzoneSize' => 1,
'connectPaths' => true,
]);
$svg = (new \chillerlan\QRCode\QRCode($options))->render($data);
if (str_starts_with($svg, 'data:image/svg+xml;base64,')) {
$decoded = base64_decode(substr($svg, strlen('data:image/svg+xml;base64,')), true);
if (is_string($decoded) && $decoded !== '') {
$svg = $decoded;
}
}
return str_contains($svg, '<svg')
? str_replace('<svg ', '<svg aria-label="QR archivio fisico" role="img" ', $svg)
: $svg;
}
if ($data === '') {
return '<span aria-hidden="true"></span>';
}
return '<img alt="QR archivio fisico" src="https://api.qrserver.com/v1/create-qr-code/?size=220x220&data=' . rawurlencode($data) . '">';
}
public static function generateCodice(int $stabileId): string
{
$prefix = 'AF-' . $stabileId . '-';
$sequence = 1;
do {
$code = $prefix . str_pad((string) $sequence, 4, '0', STR_PAD_LEFT);
$exists = self::query()->where('codice_univoco', $code)->exists();
$sequence++;
} while ($exists);
return $code;
}
public function refreshQrCodeData(): void
{
$this->forceFill([
'qr_code_data' => $this->buildQrPayload(),
])->saveQuietly();
}
public function buildQrPayload(): string
{
$parentCode = trim((string) ($this->parent?->codice_univoco ?? 'RADICE'));
$documentCode = $this->documento instanceof Documento
? trim($this->documento->resolveArchiveCode())
: '';
$location = collect([
trim((string) ($this->magazzino ?? '')),
trim((string) ($this->scaffale ?? '')),
trim((string) ($this->ripiano ?? '')),
trim((string) ($this->ubicazione_dettaglio ?? '')),
])->filter()->implode('/');
return implode('|', array_filter([
'NGDOC',
'AF:' . trim((string) $this->codice_univoco),
'TIPO:' . Str::upper((string) $this->tipo_item),
'STB:' . (int) $this->stabile_id,
'PARENT:' . ($parentCode !== '' ? $parentCode : 'RADICE'),
$documentCode !== '' ? 'DOC:' . $documentCode : null,
$location !== '' ? 'LOC:' . $location : null,
]));
}
public function buildPublicQrUrl(): string
{
$relativeSignedPath = URL::temporarySignedRoute(
'public.archivio-fisico.show',
now()->addYears(5),
['item' => $this->id],
false,
);
$baseUrl = rtrim((string) config('app.public_url', config('app.url')), '/');
if ($baseUrl === '') {
return $relativeSignedPath;
}
return $baseUrl . '/' . ltrim($relativeSignedPath, '/');
}
protected static function booted(): void
{
static::creating(function (self $item): void {
if (trim((string) $item->codice_univoco) === '') {
$item->codice_univoco = self::generateCodice((int) $item->stabile_id);
}
});
static::created(function (self $item): void {
if (trim((string) $item->qr_code_data) === '') {
$item->refreshQrCodeData();
}
});
}
}