++ Richieste dal modulo pubblico, territori cartacei, S-13 mensile per email
- /richiesta: modulo pubblico (nuovo territorio o riconsegna) con campo trappola, tempo minimo e limite orario; nome e note cifrati, avviso per email a chi ha richieste.gestisci (amministratore, assistente) - Home: riquadro Richieste con i proclamatori dal nome simile; porta su Assegna/Rientra già esistenti, che chiudono la richiesta; Scarta - Cartaceo sull'assegnazione (Assegna e Il mio gruppo), etichetta nell'elenco degli assegnati, pagina Cartacei, passa per l'XML - registro:invia-s13 agli amministratori l'ultima domenica del mese; richieste:pulizia dopo 90 giorni dalla gestione - Privacy, README e HANDOFF aggiornati; test in RichiesteTest Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
154
app/Services/RichiesteServizio.php
Normal file
154
app/Services/RichiesteServizio.php
Normal file
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Assegnazione;
|
||||
use App\Models\Proclamatore;
|
||||
use App\Models\Richiesta;
|
||||
use App\Models\Territorio;
|
||||
use App\Models\User;
|
||||
use App\Notifications\RichiestaRicevuta;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Le richieste dal modulo pubblico: registrarle, avvisare chi le gestisce e
|
||||
* aiutarlo a ricondurle a un proclamatore o a un'assegnazione. La conferma
|
||||
* passa dai moduli già esistenti (Assegna, Rientra), che chiudono la
|
||||
* richiesta da cui sono partiti.
|
||||
*/
|
||||
class RichiesteServizio
|
||||
{
|
||||
public function __construct(private ConfigurazionePosta $posta) {}
|
||||
|
||||
/** Registra la richiesta e avvisa per email amministratori e assistenti. */
|
||||
public function registra(string $tipo, string $nome, ?string $numero, ?string $note): Richiesta
|
||||
{
|
||||
$richiesta = Richiesta::create([
|
||||
'tipo' => $tipo === Richiesta::RICONSEGNA ? Richiesta::RICONSEGNA : Richiesta::NUOVO,
|
||||
'nome' => trim($nome),
|
||||
'numero_territorio' => $tipo === Richiesta::RICONSEGNA ? trim((string) $numero) : null,
|
||||
'note' => trim((string) $note) !== '' ? trim((string) $note) : null,
|
||||
]);
|
||||
|
||||
// Nel registro tipo e numero, mai il nome.
|
||||
activity()->performedOn($richiesta)
|
||||
->withProperties(array_filter(['tipo' => $richiesta->tipo, 'territorio' => $richiesta->numero_territorio]))
|
||||
->log('richiesta_ricevuta');
|
||||
|
||||
$this->avvisa($richiesta);
|
||||
|
||||
return $richiesta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Chi gestisce le richieste e ha un'email vera: amministratori e
|
||||
* assistenti (permesso richieste.gestisci) con l'indirizzo confermato.
|
||||
*/
|
||||
public function destinatari(): Collection
|
||||
{
|
||||
return User::permission('richieste.gestisci')->get()
|
||||
->filter(fn (User $u) => $u->email && $u->email_verified_at !== null)
|
||||
->values();
|
||||
}
|
||||
|
||||
/**
|
||||
* L'email parte subito e non in coda: contiene il nome scritto nella
|
||||
* richiesta, che nel database è cifrato e in coda starebbe in chiaro.
|
||||
* Senza un server di posta funzionante non si manda niente: la richiesta
|
||||
* resta comunque nella Home.
|
||||
*/
|
||||
private function avvisa(Richiesta $richiesta): void
|
||||
{
|
||||
if (! $this->posta->funzionante()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$destinatari = $this->destinatari();
|
||||
if ($destinatari->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Notification::send($destinatari, new RichiestaRicevuta($richiesta));
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* I proclamatori attivi con un nome riconducibile a quello scritto nella
|
||||
* richiesta, dal più simile: uguale, iniziale, una lettera sbagliata.
|
||||
* I nomi sono cifrati, quindi il confronto si fa qui, non in SQL.
|
||||
*
|
||||
* @return Collection<int, Proclamatore>
|
||||
*/
|
||||
public function nomiSimili(string $nome, ?Collection $proclamatori = null, int $quanti = 5): Collection
|
||||
{
|
||||
$cercate = self::parole($nome);
|
||||
if ($cercate === []) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return ($proclamatori ?? Proclamatore::attivi()->get())
|
||||
->map(function (Proclamatore $p) use ($cercate) {
|
||||
$sue = self::parole($p->cognome . ' ' . $p->nome);
|
||||
$punti = 0;
|
||||
foreach ($cercate as $parola) {
|
||||
$migliore = 0;
|
||||
foreach ($sue as $sua) {
|
||||
$migliore = max($migliore, self::somiglianza($parola, $sua));
|
||||
}
|
||||
$punti += $migliore;
|
||||
}
|
||||
|
||||
return [$p, $punti];
|
||||
})
|
||||
->filter(fn ($coppia) => $coppia[1] >= 2)
|
||||
->sortBy([[fn ($coppia) => $coppia[1], 'desc'], [fn ($coppia) => mb_strtolower($coppia[0]->nome_completo), 'asc']])
|
||||
->take($quanti)
|
||||
->map(fn ($coppia) => $coppia[0])
|
||||
->values();
|
||||
}
|
||||
|
||||
/** L'assegnazione aperta del territorio indicato in una riconsegna, se c'è. */
|
||||
public function assegnazioneAperta(?string $numero): ?Assegnazione
|
||||
{
|
||||
$numero = trim((string) $numero);
|
||||
if ($numero === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$territorio = Territorio::query()
|
||||
->whereRaw('LOWER(numero) = ?', [Str::lower($numero)])
|
||||
->with('assegnazioneCorrente.proclamatore')
|
||||
->first();
|
||||
|
||||
return $territorio?->assegnazioneCorrente;
|
||||
}
|
||||
|
||||
/** @return string[] parole minuscole, senza accenti né segni */
|
||||
private static function parole(string $testo): array
|
||||
{
|
||||
$pulito = preg_replace('/[^a-z0-9 ]+/', ' ', Str::lower(Str::ascii($testo)));
|
||||
|
||||
return array_values(array_filter(explode(' ', (string) $pulito), fn ($p) => strlen($p) >= 2));
|
||||
}
|
||||
|
||||
/** 3 uguale, 2 una inizia con l'altra, 1 una lettera di differenza. */
|
||||
private static function somiglianza(string $a, string $b): int
|
||||
{
|
||||
if ($a === $b) {
|
||||
return 3;
|
||||
}
|
||||
if (min(strlen($a), strlen($b)) >= 3 && (str_starts_with($a, $b) || str_starts_with($b, $a))) {
|
||||
return 2;
|
||||
}
|
||||
if (min(strlen($a), strlen($b)) >= 4 && levenshtein($a, $b) <= 1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user