++ fix statistiche campagne
- Dettaglio campagna: mostra tutti i territori conteggiati (stessa base della percentuale), non solo quelli assegnati dopo l'inizio. - Rientro: se più campagne corrispondono propone la più recente invece della prima creata; testo del banner chiarito. - Dettaglio campagna: nuova sezione "In corso" con i territori fuori durante la campagna e non ancora rientrati. - README: regola di conteggio documentata. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
13
README.md
13
README.md
@@ -842,8 +842,17 @@ sempre** sul flag: l'amministratore può aggiungere priorità, non toglierla
|
|||||||
quando la soglia è scattata.
|
quando la soglia è scattata.
|
||||||
|
|
||||||
### Percentuale di percorrenza delle campagne
|
### Percentuale di percorrenza delle campagne
|
||||||
- **Denominatore**: tutti i territori assegnati nel periodo della campagna
|
- **Denominatore**: tutti i territori attivi
|
||||||
- **Numeratore**: le assegnazioni contrassegnate come conteggiate
|
- **Numeratore**: i territori attivi distinti con almeno un'assegnazione
|
||||||
|
collegata alla campagna e contrassegnata come conteggiata al rientro (un
|
||||||
|
territorio percorso due volte conta una volta sola)
|
||||||
|
|
||||||
|
Al rientro la campagna viene proposta se il territorio era fuori almeno in
|
||||||
|
parte del periodo: assegnato durante la campagna, rientrato durante la
|
||||||
|
campagna, oppure fuori per tutta la sua durata. Se sia stato davvero percorso
|
||||||
|
lo decide l'operatore con la spunta. Se più campagne corrispondono viene
|
||||||
|
proposta la più recente. I territori fuori durante la campagna e non ancora
|
||||||
|
rientrati compaiono nel dettaglio come "in corso".
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,18 @@ class CampagnaShow extends Component
|
|||||||
->orderBy('returned_at')
|
->orderBy('returned_at')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
|
// Open assignments can still be counted for this campaign only while
|
||||||
|
// no later campaign has started: on return the most recent one is
|
||||||
|
// offered (see Assegnazione::campagnaApplicabile()).
|
||||||
|
$superata = Campagna::where('start_date', '>', $this->campagna->start_date)
|
||||||
|
->where('start_date', '<=', now()->toDateString())
|
||||||
|
->exists();
|
||||||
|
|
||||||
|
$inCorso = $superata ? null : $this->campagna->assegnazioniInCorso()
|
||||||
|
->with(['territorio', 'proclamatore'])
|
||||||
|
->orderBy('assigned_at')
|
||||||
|
->get();
|
||||||
|
|
||||||
// All assignments that were active during this campaign range
|
// All assignments that were active during this campaign range
|
||||||
$assegnateNelRange = Assegnazione::where('assigned_at', '<=', $this->campagna->end_date)
|
$assegnateNelRange = Assegnazione::where('assigned_at', '<=', $this->campagna->end_date)
|
||||||
->where(function ($q) {
|
->where(function ($q) {
|
||||||
@@ -38,6 +50,7 @@ class CampagnaShow extends Component
|
|||||||
|
|
||||||
return view('livewire.campagne.campagna-show', [
|
return view('livewire.campagne.campagna-show', [
|
||||||
'conteggiate' => $conteggiate,
|
'conteggiate' => $conteggiate,
|
||||||
|
'inCorso' => $inCorso,
|
||||||
'assegnateNelRange' => $assegnateNelRange,
|
'assegnateNelRange' => $assegnateNelRange,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -241,10 +241,18 @@ class Assegnazione extends Model
|
|||||||
*/
|
*/
|
||||||
public function campagnaApplicabile(?\Carbon\Carbon $returnDate = null): ?Campagna
|
public function campagnaApplicabile(?\Carbon\Carbon $returnDate = null): ?Campagna
|
||||||
{
|
{
|
||||||
$returnDate = $returnDate ?? now();
|
$returnDate = ($returnDate ?? now())->toDateString();
|
||||||
|
$assignedAt = $this->assigned_at->toDateString();
|
||||||
|
|
||||||
|
// Il territorio può essere conteggiato per una campagna se è rimasto
|
||||||
|
// in mano al proclamatore almeno in parte del periodo: assegnato
|
||||||
|
// durante la campagna, rientrato durante la campagna, oppure fuori per
|
||||||
|
// tutta la sua durata (assegnato prima e rientrato dopo). Se sia stato
|
||||||
|
// davvero percorso lo decide l'operatore con la spunta.
|
||||||
|
// Se più campagne corrispondono si propone la più recente.
|
||||||
return Campagna::where('start_date', '<=', $returnDate)
|
return Campagna::where('start_date', '<=', $returnDate)
|
||||||
->where('end_date', '>=', $this->assigned_at)
|
->where('end_date', '>=', $assignedAt)
|
||||||
|
->orderByDesc('start_date')
|
||||||
->first();
|
->first();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,16 @@ class Campagna extends Model
|
|||||||
->where('counted_in_campaign', true);
|
->where('counted_in_campaign', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assignments still open that were out during this campaign (assigned
|
||||||
|
* on or before its end): they can be counted for it when they come back.
|
||||||
|
*/
|
||||||
|
public function assegnazioniInCorso()
|
||||||
|
{
|
||||||
|
return Assegnazione::whereNull('returned_at')
|
||||||
|
->where('assigned_at', '<=', $this->end_date);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All assignments with assigned_at in this campaign's range.
|
* All assignments with assigned_at in this campaign's range.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -40,12 +40,12 @@
|
|||||||
@if($showCampaignPrompt && $campagna)
|
@if($showCampaignPrompt && $campagna)
|
||||||
<div class="bg-amber-50 border border-amber-200 rounded-lg p-4">
|
<div class="bg-amber-50 border border-amber-200 rounded-lg p-4">
|
||||||
<p class="text-sm font-medium text-amber-800 mb-2">
|
<p class="text-sm font-medium text-amber-800 mb-2">
|
||||||
⚠️ Campagna Attiva: <strong>{{ $campagna->descrizione }}</strong>
|
⚠️ Campagna: <strong>{{ $campagna->descrizione }}</strong>
|
||||||
</p>
|
</p>
|
||||||
<p class="text-xs text-amber-700 mb-3">
|
<p class="text-xs text-amber-700 mb-3">
|
||||||
Questo territorio è stato assegnato durante la campagna "{{ $campagna->descrizione }}"
|
Questo territorio era assegnato durante la campagna "{{ $campagna->descrizione }}"
|
||||||
({{ $campagna->start_date->format('d/m/Y') }} — {{ $campagna->end_date->format('d/m/Y') }}).
|
({{ $campagna->start_date->format('d/m/Y') }} — {{ $campagna->end_date->format('d/m/Y') }}).
|
||||||
Vuoi conteggiarlo nella percentuale di percorrenza?
|
È stato percorso per la campagna? Se sì, conteggialo nella percentuale di percorrenza.
|
||||||
</p>
|
</p>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<input wire:model="counted_in_campaign" type="checkbox" id="counted_in_campaign" class="rounded border-gray-300 text-amber-600 focus:ring-amber-500">
|
<input wire:model="counted_in_campaign" type="checkbox" id="counted_in_campaign" class="rounded border-gray-300 text-amber-600 focus:ring-amber-500">
|
||||||
|
|||||||
@@ -78,4 +78,40 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- Out during the campaign, not yet returned (hidden once a later campaign has started) --}}
|
||||||
|
@if($inCorso !== null)
|
||||||
|
<div class="mt-6 bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
|
||||||
|
<div class="px-4 py-3 bg-gray-50 border-b">
|
||||||
|
<h3 class="text-sm font-semibold text-gray-700">In corso ({{ $inCorso->count() }})</h3>
|
||||||
|
<p class="text-xs text-gray-500 mt-0.5">Fuori durante la campagna e non ancora rientrati: si potranno conteggiare al rientro.</p>
|
||||||
|
</div>
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="min-w-full divide-y divide-gray-200 text-sm table-cards">
|
||||||
|
<thead class="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500">Territorio</th>
|
||||||
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500">Proclamatore</th>
|
||||||
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500">Assegnato</th>
|
||||||
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500">Giorni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-gray-100">
|
||||||
|
@forelse($inCorso as $a)
|
||||||
|
<tr>
|
||||||
|
<td data-label="Territorio" class="px-4 py-2"><a href="{{ route('territori.show', $a->territorio_id) }}" class="text-indigo-600 hover:underline">N° {{ $a->territorio?->numero }}</a></td>
|
||||||
|
<td data-label="Proclamatore" class="px-4 py-2">{{ $a->proclamatore?->nome_completo ?? 'N/A' }}</td>
|
||||||
|
<td data-label="Assegnato" class="px-4 py-2">{{ $a->assigned_at->format('d/m/Y') }}</td>
|
||||||
|
<td data-label="Giorni" class="px-4 py-2">{{ $a->giorni }}</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="px-4 py-6 text-center text-gray-500">Nessun territorio in corso.</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user