Editor piantine: bozza automatica e pubblicazione con anteprima
- Il disegno è bozza e si salva da solo; i dati del territorio si modificano dall'editor e valgono subito nel gestionale. - Un solo "Pubblica" (immagine + PDF), uguale in editor e gestione: anteprima preparata in coda su disco privato, conferma obbligatoria, rifiutata se nel frattempo è cambiato qualcosa di stampato. - Stati Bozza / Pubblicata / Modifiche da pubblicare, calcolati sull'impronta di confine e dati stampati e sul PDF in uso. - Gestione riscritta: stato, ricerca, filtri, storico paginato. - Editor ridisegnato a tre zone, utilizzabile anche su tablet e telefono. - Avvisi via evento (trait Avvisa + <x-avvisi>). - .gitattributes: script shell sempre LF, altrimenti il container locale su Windows non parte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,15 +11,29 @@ use Illuminate\Database\Eloquent\Model;
|
||||
* cartografia, il numero si decide dopo. Finche' resta scollegato vale il
|
||||
* campo `nome` come etichetta; una volta collegato comanda il numero del
|
||||
* territorio, che e' il riferimento con cui lo chiamano tutti.
|
||||
*
|
||||
* `geojson` e' la bozza: l'editor la salva da solo a ogni modifica, e non
|
||||
* tocca il PDF del territorio. Il PDF cambia solo con la pubblicazione, che
|
||||
* ricorda cosa e' stato pubblicato (`geojson_pubblicato`,
|
||||
* `impronta_pubblicata`, `pubblicata_at`).
|
||||
*/
|
||||
class TerritorioMappa extends Model
|
||||
{
|
||||
public const STATO_BOZZA = 'bozza';
|
||||
|
||||
public const STATO_PUBBLICATA = 'pubblicata';
|
||||
|
||||
public const STATO_MODIFICATA = 'modificata';
|
||||
|
||||
protected $table = 'territorio_mappe';
|
||||
|
||||
protected $fillable = [
|
||||
'territorio_id',
|
||||
'nome',
|
||||
'geojson',
|
||||
'geojson_pubblicato',
|
||||
'impronta_pubblicata',
|
||||
'pubblicata_at',
|
||||
'image_path',
|
||||
'rendered_at',
|
||||
];
|
||||
@@ -28,12 +42,19 @@ class TerritorioMappa extends Model
|
||||
{
|
||||
return [
|
||||
'rendered_at' => 'datetime',
|
||||
'pubblicata_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* withTrashed: un territorio nel cestino resta collegato. Senza, la
|
||||
* relazione tornava null, l'editor mostrava la piantina come scollegata e
|
||||
* il primo salvataggio cancellava il collegamento — che al ripristino del
|
||||
* territorio non c'era piu'.
|
||||
*/
|
||||
public function territorio()
|
||||
{
|
||||
return $this->belongsTo(Territorio::class, 'territorio_id');
|
||||
return $this->belongsTo(Territorio::class, 'territorio_id')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,15 +63,22 @@ class TerritorioMappa extends Model
|
||||
*/
|
||||
public function geometria(): ?array
|
||||
{
|
||||
$decoded = json_decode($this->geojson, true);
|
||||
$decoded = json_decode((string) $this->geojson, true);
|
||||
|
||||
return is_array($decoded) ? $decoded : null;
|
||||
}
|
||||
|
||||
/** Se è collegata a un territorio, il PDF si può generare. */
|
||||
/** Collegata a un territorio vivo: è la condizione per pubblicare. */
|
||||
public function collegata(): bool
|
||||
{
|
||||
return $this->territorio_id !== null && $this->territorio !== null;
|
||||
return $this->territorio_id !== null
|
||||
&& $this->territorio !== null
|
||||
&& ! $this->territorio->trashed();
|
||||
}
|
||||
|
||||
public function territorioNelCestino(): bool
|
||||
{
|
||||
return $this->territorio !== null && $this->territorio->trashed();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,7 +88,7 @@ class TerritorioMappa extends Model
|
||||
*/
|
||||
public function etichetta(): string
|
||||
{
|
||||
if ($this->collegata()) {
|
||||
if ($this->territorio !== null) {
|
||||
return 'Terr. ' . $this->territorio->numero;
|
||||
}
|
||||
|
||||
@@ -68,4 +96,87 @@ class TerritorioMappa extends Model
|
||||
|
||||
return $nome !== '' ? $nome : 'Piantina #' . $this->id;
|
||||
}
|
||||
|
||||
// ─── Bozza e pubblicazione ──────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Impronta di tutto cio' che finisce nel PDF: confine, territorio e i
|
||||
* suoi dati stampati sulla cartolina (numero, zona come «Località»,
|
||||
* confini). Se cambia uno qualunque di questi, il PDF in vigore non
|
||||
* corrisponde piu' e la piantina ha modifiche da pubblicare — anche quando
|
||||
* il disegno e' rimasto lo stesso.
|
||||
*/
|
||||
public function impronta(): string
|
||||
{
|
||||
$territorio = $this->territorio;
|
||||
|
||||
return hash('sha256', json_encode([
|
||||
$this->geometria(),
|
||||
$this->territorio_id,
|
||||
$territorio ? trim((string) $territorio->numero) : null,
|
||||
$territorio?->zona?->nome,
|
||||
$territorio ? trim((string) $territorio->confini) : null,
|
||||
]));
|
||||
}
|
||||
|
||||
public function stato(): string
|
||||
{
|
||||
if ($this->pubblicata_at === null) {
|
||||
return self::STATO_BOZZA;
|
||||
}
|
||||
|
||||
return $this->impronta() === $this->impronta_pubblicata && $this->pdfInUsoDallaPiantina()
|
||||
? self::STATO_PUBBLICATA
|
||||
: self::STATO_MODIFICATA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Il PDF in vigore del territorio è uno composto da una piantina. Se nel
|
||||
* frattempo è stato sostituito per altre vie (ripristino di una versione,
|
||||
* caricamento a mano, import), quello che arriva ai proclamatori non è
|
||||
* più questo disegno, anche se l'impronta torna.
|
||||
*/
|
||||
public function pdfInUsoDallaPiantina(): bool
|
||||
{
|
||||
return $this->territorio_id !== null
|
||||
&& str_starts_with((string) $this->territorio?->pdf_path, 'territori-pdf/piantina-' . $this->territorio_id . '-');
|
||||
}
|
||||
|
||||
/** Il testo dell'etichetta di stato, uguale in ogni pagina. */
|
||||
public function statoEtichetta(): string
|
||||
{
|
||||
return match ($this->stato()) {
|
||||
self::STATO_BOZZA => 'Bozza',
|
||||
self::STATO_PUBBLICATA => 'Pubblicata',
|
||||
self::STATO_MODIFICATA => 'Modifiche da pubblicare',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Perché non si può pubblicare, o null se si può. Il testo va a schermo
|
||||
* accanto al pulsante: dice cosa fare, non solo che manca qualcosa.
|
||||
*/
|
||||
public function motivoNonPubblicabile(): ?string
|
||||
{
|
||||
if ($this->geometria() === null) {
|
||||
return 'Disegna il confine della piantina.';
|
||||
}
|
||||
|
||||
if ($this->territorioNelCestino()) {
|
||||
return 'Il territorio collegato è nel cestino: ripristinalo o collega un altro territorio.';
|
||||
}
|
||||
|
||||
if (! $this->collegata()) {
|
||||
return 'Collega la piantina a un territorio: il PDF è quello del territorio.';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Il confine pubblicato è diverso da quello in bozza: si può scartare. */
|
||||
public function bozzaScartabile(): bool
|
||||
{
|
||||
return $this->geojson_pubblicato !== null
|
||||
&& json_decode($this->geojson_pubblicato, true) != $this->geometria();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user