++ fix permessi assegnazioni
This commit is contained in:
24
HANDOFF.md
24
HANDOFF.md
@@ -123,6 +123,30 @@ i pulsanti Modifica/Elimina in `campagna-index`/`campagna-show` sono sotto
|
|||||||
`@can('settings.manage')`. Nota: `Campagna` non ha soft delete — l'eliminazione
|
`@can('settings.manage')`. Nota: `Campagna` non ha soft delete — l'eliminazione
|
||||||
è definitiva.
|
è definitiva.
|
||||||
|
|
||||||
|
Per i **territori** vale un modello leggermente diverso, perché la
|
||||||
|
visualizzazione va aperta a tutti gli operativi: lista (`territori.index`) e
|
||||||
|
dettaglio (`territori.show`) sono ora su `territori.assign` (che hanno admin,
|
||||||
|
assistente e operatore); creazione/modifica/cestino restano su `territori.manage`
|
||||||
|
(solo admin). Attenzione a due punti:
|
||||||
|
|
||||||
|
- **Ordine delle rotte.** Le rotte letterali `territori/create` e
|
||||||
|
`territori/cestino` devono restare registrate PRIMA del wildcard
|
||||||
|
`territori/{territorio}`, altrimenti verrebbero catturate dalla rotta di
|
||||||
|
dettaglio.
|
||||||
|
|
||||||
|
- **`TerritorioIndex` perde la copertura del middleware persistente.** Il suo
|
||||||
|
componente è ora servito da una rotta rilassata (`territori.assign`), quindi i
|
||||||
|
suoi metodi di mutazione (`toggleActive`, `togglePriority`, `deleteTerritorio`)
|
||||||
|
NON sono più protetti dal middleware di route e hanno un
|
||||||
|
`$this->authorize('territori.manage')` interno — senza, un operativo potrebbe
|
||||||
|
invocarli via `/livewire/update` col pulsante nascosto. Per difesa in
|
||||||
|
profondità lo stesso check è stato aggiunto anche a `TerritorioCreate::save`,
|
||||||
|
`TerritorioEdit::save`/`removePdf`, `TerritorioCestino::restore`/`forceDelete`
|
||||||
|
(che restano comunque su rotte `territori.manage`). I pulsanti
|
||||||
|
Modifica/toggle/Elimina/Nuovo/Cestino in `territorio-index`/`territorio-show`
|
||||||
|
sono sotto `@can('territori.manage')`; il link "Dettaglio" resta visibile a
|
||||||
|
tutti. Il menu laterale mostra "Territori" a chi ha `territori.assign`.
|
||||||
|
|
||||||
### Export/Import XML: backup completo e round-trip
|
### Export/Import XML: backup completo e round-trip
|
||||||
|
|
||||||
`Livewire/Settings/XmlExchange` esporta e reimporta **tutti** i dati gestionali,
|
`Livewire/Settings/XmlExchange` esporta e reimporta **tutti** i dati gestionali,
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ class TerritorioCestino extends Component
|
|||||||
|
|
||||||
public function restore(int $id)
|
public function restore(int $id)
|
||||||
{
|
{
|
||||||
|
$this->authorize('territori.manage');
|
||||||
|
|
||||||
$territorio = Territorio::onlyTrashed()->findOrFail($id);
|
$territorio = Territorio::onlyTrashed()->findOrFail($id);
|
||||||
$territorio->restore();
|
$territorio->restore();
|
||||||
activity()->causedBy(auth()->user())
|
activity()->causedBy(auth()->user())
|
||||||
@@ -22,6 +24,8 @@ class TerritorioCestino extends Component
|
|||||||
|
|
||||||
public function forceDelete(int $id)
|
public function forceDelete(int $id)
|
||||||
{
|
{
|
||||||
|
$this->authorize('territori.manage');
|
||||||
|
|
||||||
$territorio = Territorio::onlyTrashed()->findOrFail($id);
|
$territorio = Territorio::onlyTrashed()->findOrFail($id);
|
||||||
|
|
||||||
if ($territorio->assegnazioni()->exists()) {
|
if ($territorio->assegnazioni()->exists()) {
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ class TerritorioCreate extends Component
|
|||||||
|
|
||||||
public function save()
|
public function save()
|
||||||
{
|
{
|
||||||
|
$this->authorize('territori.manage');
|
||||||
|
|
||||||
$this->validate();
|
$this->validate();
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ class TerritorioEdit extends Component
|
|||||||
|
|
||||||
public function save()
|
public function save()
|
||||||
{
|
{
|
||||||
|
$this->authorize('territori.manage');
|
||||||
|
|
||||||
$this->validate();
|
$this->validate();
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
@@ -82,6 +84,8 @@ class TerritorioEdit extends Component
|
|||||||
|
|
||||||
public function removePdf()
|
public function removePdf()
|
||||||
{
|
{
|
||||||
|
$this->authorize('territori.manage');
|
||||||
|
|
||||||
if ($this->territorio->pdf_path) {
|
if ($this->territorio->pdf_path) {
|
||||||
Storage::disk('public')->delete($this->territorio->pdf_path);
|
Storage::disk('public')->delete($this->territorio->pdf_path);
|
||||||
if ($this->territorio->thumbnail_path) {
|
if ($this->territorio->thumbnail_path) {
|
||||||
|
|||||||
@@ -96,6 +96,11 @@ class TerritorioIndex extends Component
|
|||||||
|
|
||||||
public function toggleActive(Territorio $territorio)
|
public function toggleActive(Territorio $territorio)
|
||||||
{
|
{
|
||||||
|
// La lista è ora visibile agli utenti operativi (territori.assign), ma
|
||||||
|
// modificare/eliminare i territori resta riservato all'amministratore.
|
||||||
|
// Il check DEVE stare nel metodo: la rotta index non lo garantisce più.
|
||||||
|
$this->authorize('territori.manage');
|
||||||
|
|
||||||
$territorio->update(['attivo' => !$territorio->attivo]);
|
$territorio->update(['attivo' => !$territorio->attivo]);
|
||||||
activity()->causedBy(auth()->user())
|
activity()->causedBy(auth()->user())
|
||||||
->performedOn($territorio)
|
->performedOn($territorio)
|
||||||
@@ -105,6 +110,8 @@ class TerritorioIndex extends Component
|
|||||||
|
|
||||||
public function togglePriority(Territorio $territorio)
|
public function togglePriority(Territorio $territorio)
|
||||||
{
|
{
|
||||||
|
$this->authorize('territori.manage');
|
||||||
|
|
||||||
$territorio->update(['prioritario' => !$territorio->prioritario]);
|
$territorio->update(['prioritario' => !$territorio->prioritario]);
|
||||||
activity()->causedBy(auth()->user())
|
activity()->causedBy(auth()->user())
|
||||||
->performedOn($territorio)
|
->performedOn($territorio)
|
||||||
@@ -114,6 +121,8 @@ class TerritorioIndex extends Component
|
|||||||
|
|
||||||
public function deleteTerritorio(Territorio $territorio)
|
public function deleteTerritorio(Territorio $territorio)
|
||||||
{
|
{
|
||||||
|
$this->authorize('territori.manage');
|
||||||
|
|
||||||
activity()->causedBy(auth()->user())
|
activity()->causedBy(auth()->user())
|
||||||
->performedOn($territorio)
|
->performedOn($territorio)
|
||||||
->log('soft_deleted');
|
->log('soft_deleted');
|
||||||
|
|||||||
@@ -70,7 +70,7 @@
|
|||||||
Home
|
Home
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@can('territori.manage')
|
@can('territori.assign')
|
||||||
<a href="{{ route('territori.index') }}"
|
<a href="{{ route('territori.index') }}"
|
||||||
class="flex items-center gap-3 px-3 py-2.5 text-sm font-medium rounded-lg transition-all duration-150 {{ request()->routeIs('territori.*') ? 'sidebar-active' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900' }}">
|
class="flex items-center gap-3 px-3 py-2.5 text-sm font-medium rounded-lg transition-all duration-150 {{ request()->routeIs('territori.*') ? 'sidebar-active' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900' }}">
|
||||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7"/></svg>
|
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7"/></svg>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<h1 class="text-2xl font-bold text-gray-900">Territori</h1>
|
<h1 class="text-2xl font-bold text-gray-900">Territori</h1>
|
||||||
<p class="text-sm text-gray-500 mt-1">Gestione dei territori della congregazione</p>
|
<p class="text-sm text-gray-500 mt-1">Gestione dei territori della congregazione</p>
|
||||||
</div>
|
</div>
|
||||||
|
@can('territori.manage')
|
||||||
<div class="mt-4 sm:mt-0 flex gap-2">
|
<div class="mt-4 sm:mt-0 flex gap-2">
|
||||||
<a href="{{ route('territori.cestino') }}" class="inline-flex items-center gap-1.5 px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition">
|
<a href="{{ route('territori.cestino') }}" class="inline-flex items-center gap-1.5 px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition">
|
||||||
<svg class="h-4 w-4" style="color:#9ca3af" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
|
<svg class="h-4 w-4" style="color:#9ca3af" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
|
||||||
@@ -14,6 +15,7 @@
|
|||||||
Nuovo territorio
|
Nuovo territorio
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@endcan
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Filters --}}
|
{{-- Filters --}}
|
||||||
@@ -159,6 +161,7 @@
|
|||||||
<a href="{{ route('territori.show', $territorio) }}" class="btn-action btn-action-indigo" title="Dettaglio">
|
<a href="{{ route('territori.show', $territorio) }}" class="btn-action btn-action-indigo" title="Dettaglio">
|
||||||
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
|
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
|
||||||
</a>
|
</a>
|
||||||
|
@can('territori.manage')
|
||||||
<a href="{{ route('territori.edit', $territorio) }}" class="btn-action btn-action-gray" title="Modifica">
|
<a href="{{ route('territori.edit', $territorio) }}" class="btn-action btn-action-gray" title="Modifica">
|
||||||
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
|
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
|
||||||
</a>
|
</a>
|
||||||
@@ -172,6 +175,7 @@
|
|||||||
<button wire:click="deleteTerritorio({{ $territorio->id }})" wire:confirm="Spostare il territorio {{ $territorio->numero }} nel cestino?" class="btn-action btn-action-red" title="Elimina">
|
<button wire:click="deleteTerritorio({{ $territorio->id }})" wire:confirm="Spostare il territorio {{ $territorio->numero }} nel cestino?" class="btn-action btn-action-red" title="Elimina">
|
||||||
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
|
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
|
||||||
</button>
|
</button>
|
||||||
|
@endcan
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -24,10 +24,12 @@
|
|||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
@endcan
|
@endcan
|
||||||
|
@can('territori.manage')
|
||||||
<a href="{{ route('territori.edit', $territorio) }}" class="btn-action btn-action-indigo" style="padding:8px 16px;font-size:14px;border-radius:8px">
|
<a href="{{ route('territori.edit', $territorio) }}" class="btn-action btn-action-indigo" style="padding:8px 16px;font-size:14px;border-radius:8px">
|
||||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
|
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
|
||||||
Modifica
|
Modifica
|
||||||
</a>
|
</a>
|
||||||
|
@endcan
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -66,12 +66,23 @@ Route::middleware('auth')->group(function () {
|
|||||||
// Home Dashboard
|
// Home Dashboard
|
||||||
Route::get('/', Home::class)->name('dashboard');
|
Route::get('/', Home::class)->name('dashboard');
|
||||||
|
|
||||||
// Territori
|
// Territori — visualizzazione (lista + dettaglio) per tutti gli utenti
|
||||||
Route::middleware('permission:territori.manage')->group(function () {
|
// operativi, cioè chi può assegnare i territori.
|
||||||
|
Route::middleware('permission:territori.assign')->group(function () {
|
||||||
Route::get('territori', TerritorioIndex::class)->name('territori.index');
|
Route::get('territori', TerritorioIndex::class)->name('territori.index');
|
||||||
|
});
|
||||||
|
// Gestione territori (crea/modifica/cestino): solo amministratore
|
||||||
|
// (territori.manage). Le rotte LETTERALI (create/cestino) devono essere
|
||||||
|
// registrate PRIMA del wildcard {territorio}, altrimenti "create"/"cestino"
|
||||||
|
// verrebbero catturate dalla rotta di dettaglio.
|
||||||
|
Route::middleware('permission:territori.manage')->group(function () {
|
||||||
Route::get('territori/create', TerritorioCreate::class)->name('territori.create');
|
Route::get('territori/create', TerritorioCreate::class)->name('territori.create');
|
||||||
Route::get('territori/cestino', TerritorioCestino::class)->name('territori.cestino');
|
Route::get('territori/cestino', TerritorioCestino::class)->name('territori.cestino');
|
||||||
|
});
|
||||||
|
Route::middleware('permission:territori.assign')->group(function () {
|
||||||
Route::get('territori/{territorio}', TerritorioShow::class)->name('territori.show');
|
Route::get('territori/{territorio}', TerritorioShow::class)->name('territori.show');
|
||||||
|
});
|
||||||
|
Route::middleware('permission:territori.manage')->group(function () {
|
||||||
Route::get('territori/{territorio}/edit', TerritorioEdit::class)->name('territori.edit');
|
Route::get('territori/{territorio}/edit', TerritorioEdit::class)->name('territori.edit');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user