++ fix prioritario display and add link sent toggle

This commit is contained in:
2026-04-13 15:40:35 +00:00
parent 0553d4ef74
commit 6a65087449
10 changed files with 459 additions and 108 deletions

View File

@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
use App\Models\Assegnazione;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\StreamedResponse;
@@ -14,6 +15,10 @@ class AssignmentPdfController extends Controller
{
$this->validateAccess($assignment, $code);
if ($expired = $this->linkScaduto($assignment)) {
return $expired;
}
$pdfUrl = route('assignments.pdf.file', [
'assignment' => $assignment->id,
'code' => $code,
@@ -25,10 +30,14 @@ class AssignmentPdfController extends Controller
]);
}
public function file(Request $request, Assegnazione $assignment, string $code): StreamedResponse
public function file(Request $request, Assegnazione $assignment, string $code): StreamedResponse|View
{
$this->validateAccess($assignment, $code);
if ($expired = $this->linkScaduto($assignment)) {
return $expired;
}
$pdfPath = $assignment->territorio?->pdf_path;
abort_unless($pdfPath && Storage::disk('public')->exists($pdfPath), 404);
@@ -48,4 +57,22 @@ class AssignmentPdfController extends Controller
abort_unless($assignment->is_aperta, 403);
abort_unless($assignment->territorio?->pdf_path, 404);
}
protected function linkScaduto(Assegnazione $assignment): ?View
{
if (auth()->check()) {
return null;
}
$ttlMonths = max(1, (int) \App\Models\Setting::getValue('assignment_link_ttl_hours', 1));
if ($assignment->assigned_at->copy()->addMonths($ttlMonths)->isPast()) {
return view('assignments.link-scaduto', [
'numero' => $assignment->territorio?->numero,
]);
}
return null;
}
}

View File

@@ -3,20 +3,32 @@
namespace App\Http\Controllers;
use App\Models\Assegnazione;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
class ShortPdfLinkController extends Controller
{
public function __invoke(string $code): RedirectResponse
public function __invoke(string $code): RedirectResponse|View
{
$assignment = Assegnazione::where('pdf_access_code', $code)->firstOrFail();
abort_unless($assignment->is_aperta, 403);
abort_unless($assignment->territorio?->pdf_path, 404);
// Unauthenticated users (proclamatori) are subject to link TTL
if (! auth()->check()) {
$ttlMonths = max(1, (int) \App\Models\Setting::getValue('assignment_link_ttl_hours', 1));
if ($assignment->assigned_at->copy()->addMonths($ttlMonths)->isPast()) {
return view('assignments.link-scaduto', [
'numero' => $assignment->territorio?->numero,
]);
}
}
return redirect()->route('assignments.pdf.viewer', [
'assignment' => $assignment->id,
'code' => $code,
]);
}
}

View File

@@ -83,6 +83,13 @@ class Assegna extends Component
return $this->redirect(route('territori.show', $territorio), navigate: true);
}
public function toggleLinkSent(int $assegnazioneId): void
{
$this->authorize('territori.assign');
$assegnazione = Assegnazione::findOrFail($assegnazioneId);
$assegnazione->forceFill(['link_sent' => ! $assegnazione->link_sent])->saveQuietly();
}
#[Computed]
public function selectedThumbnailUrl(): ?string
{
@@ -122,10 +129,13 @@ class Assegna extends Component
->get()
->sortBy(fn($a) => (int) $a->territorio?->numero);
$linkTtlMonths = max(1, (int) \App\Models\Setting::getValue('assignment_link_ttl_hours', 1));
return view('livewire.assegnazioni.assegna', [
'territoriDisponibili' => $territoriDisponibili,
'proclamatoriAttivi' => $proclamatoriAttivi,
'assegnazioniAperte' => $assegnazioniAperte,
'linkTtlMonths' => $linkTtlMonths,
]);
}
}

View File

@@ -18,9 +18,12 @@ class CampagnaShow extends Component
public function render()
{
// All assignments with returned_at in campaign range that were counted
$conteggiate = Assegnazione::where('campagna_id', $this->campagna->id)
// Assignments counted for this campaign:
// - assigned on or after campaign start
// - linked to this campaign (campaign_id), regardless of returned_at (retroactive returns allowed)
$conteggiate = Assegnazione::where('campaign_id', $this->campagna->id)
->where('counted_in_campaign', true)
->where('assigned_at', '>=', $this->campagna->start_date)
->with(['territorio', 'proclamatore'])
->orderBy('returned_at')
->get();

View File

@@ -19,6 +19,7 @@ class Assegnazione extends Model
'counted_in_campaign',
'campaign_id',
'pdf_access_code',
'link_sent',
'note',
'created_by',
'returned_by',
@@ -30,6 +31,7 @@ class Assegnazione extends Model
'assigned_at' => 'date',
'returned_at' => 'date',
'counted_in_campaign' => 'boolean',
'link_sent' => 'boolean',
];
}
@@ -117,6 +119,11 @@ class Assegnazione extends Model
return route('assignments.pdf.short', ['code' => $this->ensurePdfAccessCode()]);
}
public function markLinkSent(): void
{
$this->forceFill(['link_sent' => true])->saveQuietly();
}
// ─── Scopes ─────────────────────────────────────────────────
public function scopeAperte($query)

View File

@@ -72,19 +72,19 @@ class Campagna extends Model
/**
* Campaign coverage percentage.
* Numerator: assignments counted for campaign
* Denominator: ALL assignments with assigned_at in campaign range (returned or not)
* Denominator: total active territories
*/
public function getPercentualePercorrenzaAttribute(): float
{
$totaleNelRange = $this->assegnazioniNelRange()->count();
$totaleAttivi = Territorio::where('attivo', true)->count();
if ($totaleNelRange === 0) {
if ($totaleAttivi === 0) {
return 0.0;
}
$conteggiate = $this->assegnazioniConteggiate()->count();
return round(($conteggiate / $totaleNelRange) * 100, 1);
return round(($conteggiate / $totaleAttivi) * 100, 1);
}
public function scopeCompletate($query)