Browse Source

Pagamenti e prima nota: incasso, storno contabile e metodi.

Lo staff resta fuori dal saldo; lo storno crea il movimento di uscita.

Co-authored-by: Cursor <cursoragent@cursor.com>
marcofalabretti 2 weeks ago
parent
commit
91bb1961bf

+ 7
- 4
app/DataTables/MetodoPagamentoDataTable.php View File

@@ -17,6 +17,8 @@ use App\Models\Attivita;
17 17
 
18 18
 class MetodoPagamentoDataTable extends DataTable
19 19
 {
20
+    public ?int $attivita_id = null;
21
+
20 22
     public function __construct()
21 23
     {
22 24
         $this->dataTableVariable = 'dataTable_metodo_pagamento';
@@ -55,11 +57,12 @@ class MetodoPagamentoDataTable extends DataTable
55 57
      */
56 58
     public function query(MetodoPagamento $model): QueryBuilder
57 59
     {
58
-        if($this->attivita_id){
59
-            return $model->newQuery()->where('attivita_id', $this->attivita_id)->orderBy('nome', 'asc');
60
-        }else{
60
+        $attivitaId = (int) $this->attivita_id;
61
+        if ($attivitaId <= 0) {
61 62
             return $model->newQuery()->whereRaw('1 = 0');
62 63
         }
64
+
65
+        return $model->newQuery()->where('attivita_id', $attivitaId)->orderBy('nome', 'asc');
63 66
     }
64 67
 
65 68
     /**
@@ -88,7 +91,7 @@ class MetodoPagamentoDataTable extends DataTable
88 91
                     ->editor(
89 92
                         Editor::make()
90 93
                             ->fields([
91
-                                Fields\Hidden::make('attivita_id')->label('Attività')->default($this->attivita_id),
94
+                                Fields\Hidden::make('attivita_id')->label('Attività')->default((int) $this->attivita_id),
92 95
                                 Fields\Boolean::make('is_attivo')->label('Attivo')->default(true),
93 96
                                 Fields\Select2::make('tipo')->label('Tipo')->options(MetodoPagamento::getTipiPagamento()->sortBy('label')->pluck('value' ,'label')),
94 97
                                 Fields\Text::make('nome')->label('Nome'),

+ 13
- 9
app/DataTables/MetodoPagamentoDataTableEditor.php View File

@@ -3,13 +3,10 @@
3 3
 namespace App\DataTables;
4 4
 
5 5
 use App\Models\MetodoPagamento;
6
+use App\Services\Attivita\AttivitaService;
6 7
 use Illuminate\Database\Eloquent\Model;
7
-use Illuminate\Validation\Rule;
8 8
 use Yajra\DataTables\DataTablesEditor;
9
-use Illuminate\Http\Request;
10
-use Illuminate\Validation\ValidationException;
11
-use Storage;
12
-use Illuminate\Support\Str;
9
+use Illuminate\Support\Facades\Session;
13 10
 
14 11
 class  MetodoPagamentoDataTableEditor extends DataTablesEditor
15 12
 {
@@ -58,7 +55,6 @@ class  MetodoPagamentoDataTableEditor extends DataTablesEditor
58 55
     return [
59 56
       'nome'  => 'required',
60 57
       'tipo' => 'required|in:'.MetodoPagamento::getTipiPagamento()->pluck('value')->implode(','),
61
-      'attivita_id' => 'required|exists:attivita,id',
62 58
       // 'is_attivo' => 'boolean',
63 59
       // 'is_pubblico' => 'boolean',
64 60
       'key' => 'nullable',
@@ -85,7 +81,8 @@ class  MetodoPagamentoDataTableEditor extends DataTablesEditor
85 81
 
86 82
   public function creating(Model $model, array $data): array
87 83
   {
88
-    // $model->roles()->sync([$data['ruolo_id']]);
84
+    $data['attivita_id'] = Session::get('attivita_attuale');
85
+    AttivitaService::garantisciAttivita($data['attivita_id']);
89 86
     $data['is_attivo'] = isset($data['is_attivo']) ? true : false;
90 87
     $data['is_pubblico'] = isset($data['is_pubblico']) ? true : false;
91 88
     return $data;
@@ -93,13 +90,20 @@ class  MetodoPagamentoDataTableEditor extends DataTablesEditor
93 90
 
94 91
   public function updating(Model $model, array $data): array
95 92
   {
96
-    // dd($data['ruolo']);
97
-    // $model->roles()->sync([$data['ruolo_id']]);
93
+    AttivitaService::garantisciAttivita($model->attivita_id);
94
+    unset($data['attivita_id']);
98 95
     $data['is_attivo'] = isset($data['is_attivo']) ? true : false;
99 96
     $data['is_pubblico'] = isset($data['is_pubblico']) ? true : false;
100 97
     return $data;
101 98
   }
102 99
 
100
+  public function deleting(Model $model, array $data): array
101
+  {
102
+    AttivitaService::garantisciAttivita($model->attivita_id);
103
+
104
+    return $data;
105
+  }
106
+
103 107
   public function messages(): array
104 108
   {
105 109
     return $this->messages;

+ 11
- 12
app/DataTables/PagamentoDataTable.php View File

@@ -3,10 +3,7 @@
3 3
 namespace App\DataTables;
4 4
 
5 5
 use App\Models\Pagamento;
6
-use App\Models\Ordine;
7
-use App\Models\Prenotazione;
8 6
 use App\Models\MetodoPagamento;
9
-use App\Models\Attivita;
10 7
 use Illuminate\Database\Eloquent\Builder as QueryBuilder;
11 8
 use Yajra\DataTables\EloquentDataTable;
12 9
 use Yajra\DataTables\Html\Builder as HtmlBuilder;
@@ -64,11 +61,12 @@ class PagamentoDataTable extends DataTable
64 61
      */
65 62
     public function query(Pagamento $model): QueryBuilder
66 63
     {
67
-        if($this->attivita_id){
68
-            return $model->newQuery()->where('attivita_id', $this->attivita_id)->orderBy('created_at', 'desc');
69
-        }else{
64
+        $attivitaId = (int) $this->attivita_id;
65
+        if ($attivitaId <= 0) {
70 66
             return $model->newQuery()->whereRaw('1 = 0')->orderBy('created_at', 'desc');
71 67
         }
68
+
69
+        return $model->newQuery()->where('attivita_id', $attivitaId)->orderBy('created_at', 'desc');
72 70
     }
73 71
 
74 72
     /**
@@ -98,15 +96,15 @@ class PagamentoDataTable extends DataTable
98 96
                     ->editor(
99 97
                         Editor::make()
100 98
                             ->fields([
101
-                                Fields\Text::make('attivita_id')->label('Attività')->options(Attivita::all()->pluck('nome', 'id')),
102
-                                Fields\Select2::make('ordine_id')->label('Ordine')->options(Ordine::all()->pluck('id', 'nome')),
103
-                                Fields\Select2::make('prenotazione_id')->label('Prenotazione')->options(Prenotazione::all()->pluck('id', 'nome')),
99
+                                Fields\Hidden::make('attivita_id')->default((int) $this->attivita_id),
100
+                                Fields\Hidden::make('ordine_id'),
101
+                                Fields\Hidden::make('prenotazione_id'),
104 102
                                 Fields\Text::make('importo')->label('Importo'),
105 103
                                 Fields\Text::make('causale')->label('Causale'),
106 104
                                 Fields\Select2::make('stato')->label('Stato')->options(Pagamento::getStati()->pluck('value', 'label')),
107
-                                Fields\Select2::make('metodo_pagamento_id')->label('Metodo pagamento')->options(MetodoPagamento::all()->pluck('id', 'nome')),
105
+                                Fields\Select2::make('metodo_pagamento_id')->label('Metodo pagamento')->options(MetodoPagamento::where('attivita_id', (int) $this->attivita_id)->where('is_attivo', true)->pluck('id' , 'nome')),
108 106
                                 Fields\Text::make('note')->label('Note'),
109
-                                Fields\Text::make('info')->label('Info'),
107
+                                // Fields\Text::make('info')->label('Info'),
110 108
                             ])
111 109
                     )
112 110
                     ->initComplete("function(settings, json){
@@ -127,7 +125,8 @@ class PagamentoDataTable extends DataTable
127 125
             Column::make('prenotazione_id')->title('Prenotazione')->data('prenotazione_id_display'),
128 126
             Column::make('importo')->title('Importo')->data('importo_display'),
129 127
             Column::make('causale')->title('Causale'),
130
-            Column::make('metodo_pagamento_id')->title('Metodo pagamento')->data('metodo_pagamento_id_display'),
128
+            Column::make('metodo_pagamento_id')->visible(false)->width('10%'),
129
+            Column::make('metodo_pagamento_id_display')->title('Metodo pagamento')->data('metodo_pagamento_id_display'),
131 130
             Column::make('note')->title('Note'),
132 131
             Column::computed('action')
133 132
                   ->title('')

+ 141
- 0
app/DataTables/PagamentoDataTableEditor.php View File

@@ -0,0 +1,141 @@
1
+<?php
2
+
3
+namespace App\DataTables;
4
+
5
+use App\Models\Ordine;
6
+use App\Models\Pagamento;
7
+use App\Models\Prenotazione;
8
+use App\Models\MetodoPagamento;
9
+use App\Services\Attivita\AttivitaService;
10
+use Illuminate\Database\Eloquent\Model;
11
+use Yajra\DataTables\DataTablesEditor;
12
+use Illuminate\Support\Facades\Session;
13
+
14
+class PagamentoDataTableEditor extends DataTablesEditor
15
+{
16
+  protected $model = Pagamento::class;
17
+
18
+  protected $messages = [
19
+    'attivita_id.required' => 'L\'attività è richiesta',
20
+    'ordine_id.required' => 'L\'ordine è richiesto',
21
+    'prenotazione_id.required' => 'La prenotazione è richiesta',
22
+    'importo.required' => 'L\'importo è richiesto',
23
+    'causale.required' => 'La causale è richiesta',
24
+    'stato.required' => 'Lo stato è richiesto',
25
+    'metodo_pagamento_id.required' => 'Il metodo di pagamento è richiesto',
26
+    'note.required' => 'La note è richiesta',
27
+    'info.required' => 'L\'info è richiesta',
28
+  ];
29
+
30
+  /**
31
+  * Get create action validation rules.
32
+  *
33
+  * @return array
34
+  */
35
+  public function createRules(): array
36
+  {
37
+    return [
38
+      'ordine_id' => 'required|exists:ordine,id',
39
+      'prenotazione_id' => 'nullable|exists:prenotazione,id',
40
+      'importo' => 'required|numeric',
41
+      'causale' => 'required|string',
42
+      'stato' => 'required|string',
43
+      'metodo_pagamento_id' => 'required|exists:metodo_pagamento,id',
44
+      'note' => 'nullable',
45
+      'info' => 'nullable',
46
+    ];
47
+  }
48
+
49
+  public function createMessages(): array{
50
+    return $this->messages;
51
+  }
52
+
53
+  /**
54
+  * Get edit action validation rules.
55
+  *
56
+  * @param Model $model
57
+  * @return array
58
+  */
59
+  public function editRules(Model $model): array
60
+  {
61
+    return [
62
+      'ordine_id' => 'required|exists:ordine,id',
63
+      'prenotazione_id' => 'nullable|exists:prenotazione,id',
64
+      'importo' => 'required|numeric',
65
+      'causale' => 'required|string',
66
+      'stato' => 'required|string',
67
+      'metodo_pagamento_id' => 'required|exists:metodo_pagamento,id',
68
+      'note' => 'nullable',
69
+      'info' => 'nullable',
70
+    ];
71
+  }
72
+
73
+  public function editMessages(): array{
74
+    return $this->messages;
75
+  }
76
+
77
+  /**
78
+  * Get remove action validation rules.
79
+  *
80
+  * @param Model $model
81
+  * @return array
82
+  */
83
+  public function removeRules(Model $model): array
84
+  {
85
+    return [];
86
+  }
87
+
88
+  public function creating(Model $model, array $data): array
89
+  {
90
+    $data['attivita_id'] = Session::get('attivita_attuale');
91
+    AttivitaService::garantisciAttivita($data['attivita_id']);
92
+    $this->garantisciRelazioniDiQuestaAttivita($data, $data['attivita_id']);
93
+
94
+    return $data;
95
+  }
96
+
97
+  public function updating(Model $model, array $data): array
98
+  {
99
+    AttivitaService::garantisciAttivita($model->attivita_id);
100
+    unset($data['attivita_id']);
101
+    $this->garantisciRelazioniDiQuestaAttivita($data, $model->attivita_id, $model);
102
+
103
+    return $data;
104
+  }
105
+
106
+  public function deleting(Model $model, array $data): array
107
+  {
108
+    AttivitaService::garantisciAttivita($model->attivita_id);
109
+
110
+    return $data;
111
+  }
112
+
113
+  public function messages(): array
114
+  {
115
+    return $this->messages;
116
+  }
117
+
118
+  private function garantisciRelazioniDiQuestaAttivita(array $data, $attivitaId, ?Model $model = null): void
119
+  {
120
+    $ordineId = $data['ordine_id'] ?? $model?->ordine_id;
121
+    $ordine = Ordine::find($ordineId);
122
+    if (! $ordine || (int) $ordine->attivita_id !== (int) $attivitaId) {
123
+      abort(404);
124
+    }
125
+
126
+    $prenotazioneId = $data['prenotazione_id'] ?? $model?->prenotazione_id;
127
+    if ($prenotazioneId) {
128
+      $prenotazione = Prenotazione::with('evento')->find($prenotazioneId);
129
+      if (! $prenotazione || (int) $prenotazione->evento?->attivita_id !== (int) $attivitaId) {
130
+        abort(404);
131
+      }
132
+    }
133
+
134
+    $metodoId = $data['metodo_pagamento_id'] ?? $model?->metodo_pagamento_id;
135
+    $metodo = MetodoPagamento::find($metodoId);
136
+    if (! $metodo || (int) $metodo->attivita_id !== (int) $attivitaId) {
137
+      abort(404);
138
+    }
139
+  }
140
+
141
+}

+ 9
- 6
app/DataTables/PrimaNotaDataTable.php View File

@@ -118,12 +118,14 @@ class PrimaNotaDataTable extends DataTable
118 118
      */
119 119
     public function query(PrimaNota $model): QueryBuilder
120 120
     {
121
-        if (!$this->attivita_id) {
121
+        $attivitaId = (int) $this->attivita_id;
122
+        if ($attivitaId <= 0) {
122 123
             return $model->newQuery()->whereRaw('1 = 0');
123 124
         }
124 125
 
125 126
         return $model->newQuery()
126
-            ->where('attivita_id', $this->attivita_id)
127
+            ->with('pagamento')
128
+            ->where('attivita_id', $attivitaId)
127 129
             ->conFiltriIndice()
128 130
             ->orderBy('created_at', 'desc');
129 131
     }
@@ -195,21 +197,22 @@ class PrimaNotaDataTable extends DataTable
195 197
                     ->editor(
196 198
                         Editor::make()
197 199
                             ->fields([
198
-                                Fields\Hidden::make('attivita_id')->default($this->attivita_id),
200
+                                Fields\Hidden::make('attivita_id')->default((int) $this->attivita_id),
199 201
                                 Fields\Select2::make('tipo_movimento')->label('Tipo movimento')->options(PrimaNota::getTipiMovimento()->pluck('value', 'label')),
200
-                                Fields\Select2::make('categoria_contabile_id')->label('Categoria contabile')->options(\App\Models\Categoriacontabile::where('is_attiva', true)->pluck('id', 'nome')),
202
+                                Fields\Select2::make('categoria_contabile_id')->label('Categoria contabile')->options(\App\Models\Categoriacontabile::where('attivita_id', (int) $this->attivita_id)->where('is_attiva', true)->pluck('id', 'nome')),
203
+                                Fields\Select2::make('pagamento_id')->label('Pagamento')->options(Pagamento::where('attivita_id', (int) $this->attivita_id)->pluck('id', 'causale')),
201 204
                                 Fields\Text::make('importo')->label('Importo'),
202 205
                                 Fields\Text::make('causale')->label('Causale'),
203 206
                                 Fields\Text::make('riferimento')->label('Riferimento'),
204 207
                                 Fields\Select2::make('evento_id')
205 208
                                 ->label('Evento')
206 209
                                 ->options(
207
-                                    collect(['Nessun evento' => ''])->merge(Evento::all()->pluck('id', 'nome'))->toArray()
210
+                                    collect(['Nessun evento' => ''])->merge(Evento::where('attivita_id', (int) $this->attivita_id)->pluck('id', 'nome'))->toArray()
208 211
                                 ),
209 212
                                 Fields\Select2::make('fornitore_id')
210 213
                                 ->label('Fornitore')
211 214
                                 ->options(
212
-                                    collect(['Nessun fornitore' => ''])->merge(Fornitore::all()->pluck('id', 'nome'))->toArray()
215
+                                    collect(['Nessun fornitore' => ''])->merge(Fornitore::where('attivita_id', (int) $this->attivita_id)->pluck('id', 'nome'))->toArray()
213 216
                                 ),
214 217
                                 
215 218
                                 // Fields\Checkbox::make('tipo_movimento')->label('Tipo movimento')->options(PrimaNota::getTipiMovimento()->pluck('value', 'label')),

+ 56
- 20
app/DataTables/PrimaNotaDataTableEditor.php View File

@@ -2,20 +2,21 @@
2 2
 
3 3
 namespace App\DataTables;
4 4
 
5
+use App\Models\Categoriacontabile;
6
+use App\Models\Evento;
7
+use App\Models\Fornitore;
8
+use App\Models\Ordine;
9
+use App\Models\Pagamento;
10
+use App\Models\Prenotazione;
5 11
 use App\Models\PrimaNota;
12
+use App\Services\Attivita\AttivitaService;
6 13
 use Illuminate\Database\Eloquent\Model;
7
-use Illuminate\Validation\Rule;
8 14
 use Yajra\DataTables\DataTablesEditor;
9
-use Illuminate\Http\Request;
10
-use Illuminate\Validation\ValidationException;
11
-use Storage;
12
-use Illuminate\Support\Str;
15
+use Illuminate\Support\Facades\Session;
13 16
 
14 17
 class PrimaNotaDataTableEditor extends DataTablesEditor
15 18
 {
16 19
   protected $model = PrimaNota::class;
17
-  // protected $uploadDir = '';
18
-  // protected $disk = 'modelliRicevute';
19 20
 
20 21
   protected $messages = [
21 22
     'riferimento.required' => 'Il riferimento è richiesto',
@@ -23,7 +24,7 @@ class PrimaNotaDataTableEditor extends DataTablesEditor
23 24
     'importo.required' => 'L\'importo è richiesto',
24 25
     'tipo_movimento.required' => 'Il tipo movimento è richiesto',
25 26
     'causale.required' => 'La causale è richiesta',
26
-    'stato.required' => 'Il stato è richiesto',
27
+    'stato.required' => 'Lo stato è richiesto',
27 28
     'categoria_contabile_id.required' => 'La categoria contabile è richiesta',
28 29
   ];
29 30
 
@@ -35,7 +36,6 @@ class PrimaNotaDataTableEditor extends DataTablesEditor
35 36
   public function createRules(): array
36 37
   {
37 38
     return [
38
-      'attivita_id' => 'required|exists:attivita,id',
39 39
       'pagamento_id' => 'nullable|exists:pagamento,id',
40 40
       'ordine_id' => 'nullable|exists:ordine,id',
41 41
       'prenotazione_id' => 'nullable|exists:prenotazione,id',
@@ -43,12 +43,9 @@ class PrimaNotaDataTableEditor extends DataTablesEditor
43 43
       'fornitore_id' => 'nullable|exists:fornitore,id',
44 44
       'riferimento' => 'nullable|string',
45 45
       'importo' => 'required|numeric',
46
-      'tipo_movimento' => 'required|string',   
46
+      'tipo_movimento' => 'required|string',
47 47
       'causale' => 'required|string',
48 48
       'categoria_contabile_id' => 'required|exists:categoria_contabile,id',
49
-      // 'stato' => 'nullable|string',
50
-      // 'note' => 'nullable|string',
51
-      // 'codice' => 'nullable|string',
52 49
       'stato_pagamento' => 'nullable|string',
53 50
       'metodo_pagamento' => 'nullable|string',
54 51
     ];
@@ -67,7 +64,6 @@ class PrimaNotaDataTableEditor extends DataTablesEditor
67 64
   public function editRules(Model $model): array
68 65
   {
69 66
     return [
70
-      'attivita_id' => 'required|exists:attivita,id',
71 67
       'pagamento_id' => 'nullable|exists:pagamento,id',
72 68
       'ordine_id' => 'nullable|exists:ordine,id',
73 69
       'prenotazione_id' => 'nullable|exists:prenotazione,id',
@@ -75,12 +71,10 @@ class PrimaNotaDataTableEditor extends DataTablesEditor
75 71
       'fornitore_id' => 'nullable|exists:fornitore,id',
76 72
       'riferimento' => 'nullable|string',
77 73
       'importo' => 'required|numeric',
78
-      'tipo_movimento' => 'required|string',   
74
+      'tipo_movimento' => 'required|string',
79 75
       'causale' => 'required|string',
80 76
       'stato' => 'nullable|string',
81 77
       'categoria_contabile_id' => 'required|exists:categoria_contabile,id',
82
-      // 'note' => 'nullable|string',
83
-      // 'codice' => 'nullable|string',
84 78
       'stato_pagamento' => 'nullable|string',
85 79
       'metodo_pagamento' => 'nullable|string',
86 80
     ];
@@ -103,19 +97,61 @@ class PrimaNotaDataTableEditor extends DataTablesEditor
103 97
 
104 98
   public function creating(Model $model, array $data): array
105 99
   {
106
-    // $model->roles()->sync([$data['ruolo_id']]);
100
+    $data['attivita_id'] = Session::get('attivita_attuale');
101
+    AttivitaService::garantisciAttivita($data['attivita_id']);
102
+    $this->garantisciRelazioniDiQuestaAttivita($data, $data['attivita_id']);
103
+
107 104
     return $data;
108 105
   }
109 106
 
110 107
   public function updating(Model $model, array $data): array
111 108
   {
112
-    // dd($data['ruolo']);
113
-    // $model->roles()->sync([$data['ruolo_id']]);
109
+    AttivitaService::garantisciAttivita($model->attivita_id);
110
+    unset($data['attivita_id']);
111
+    $this->garantisciRelazioniDiQuestaAttivita($data, $model->attivita_id, $model);
112
+
113
+    return $data;
114
+  }
115
+
116
+  public function deleting(Model $model, array $data): array
117
+  {
118
+    AttivitaService::garantisciAttivita($model->attivita_id);
119
+
114 120
     return $data;
115 121
   }
122
+
116 123
   public function messages(): array
117 124
   {
118 125
     return $this->messages;
119 126
   }
120 127
 
128
+  private function garantisciRelazioniDiQuestaAttivita(array $data, $attivitaId, ?Model $model = null): void
129
+  {
130
+    $this->garantisciStessoTenant($data['pagamento_id'] ?? $model?->pagamento_id, Pagamento::class, $attivitaId);
131
+    $this->garantisciStessoTenant($data['ordine_id'] ?? $model?->ordine_id, Ordine::class, $attivitaId);
132
+    $this->garantisciStessoTenant($data['evento_id'] ?? $model?->evento_id, Evento::class, $attivitaId);
133
+    $this->garantisciStessoTenant($data['fornitore_id'] ?? $model?->fornitore_id, Fornitore::class, $attivitaId);
134
+    $this->garantisciStessoTenant($data['categoria_contabile_id'] ?? $model?->categoria_contabile_id, Categoriacontabile::class, $attivitaId);
135
+
136
+    $prenotazioneId = $data['prenotazione_id'] ?? $model?->prenotazione_id;
137
+    if ($prenotazioneId) {
138
+      $prenotazione = Prenotazione::with('evento')->find($prenotazioneId);
139
+      if (! $prenotazione || (int) $prenotazione->evento?->attivita_id !== (int) $attivitaId) {
140
+        abort(404);
141
+      }
142
+    }
143
+  }
144
+
145
+  private function garantisciStessoTenant($id, string $class, $attivitaId): void
146
+  {
147
+    if (! $id) {
148
+      return;
149
+    }
150
+
151
+    $record = $class::find($id);
152
+    if (! $record || (int) $record->attivita_id !== (int) $attivitaId) {
153
+      abort(404);
154
+    }
155
+  }
156
+
121 157
 }

+ 3
- 1
app/Http/Controllers/MetodoPagamentoController.php View File

@@ -2,6 +2,8 @@
2 2
 
3 3
 namespace App\Http\Controllers;
4 4
 
5
+
6
+use Illuminate\Routing\Controllers\Middleware;
5 7
 use Illuminate\Http\Request;
6 8
 use App\Models\MetodoPagamento;
7 9
 use App\DataTables\MetodoPagamentoDataTable;
@@ -23,7 +25,7 @@ class MetodoPagamentoController extends Controller
23 25
     {
24 26
         return [
25 27
             new Middleware('permission:view-metodo_pagamento', only: ['index']),
26
-            new Middleware('permission:create-metodo_pagamento|edit-metodo_pagamento|delete-metodo_pagamento', only: ['store', 'update', 'destroy']),
28
+            new Middleware('permission:create-metodo_pagamento|edit-metodo_pagamento|delete-metodo_pagamento', only: ['store', 'update', 'destroy', 'segresta_wallet_chiE']),
27 29
         ];
28 30
     }
29 31
     

+ 107
- 14
app/Http/Controllers/PagamentoController.php View File

@@ -2,12 +2,15 @@
2 2
 
3 3
 namespace App\Http\Controllers;
4 4
 
5
+
6
+use Illuminate\Routing\Controllers\Middleware;
5 7
 use Illuminate\Http\Request;
6 8
 use App\Models\Pagamento;
7
-use App\Models\MetodoPagamento;
9
+use App\Models\PrimaNota;
8 10
 use App\DataTables\PagamentoDataTable;
9 11
 use App\DataTables\PagamentoDataTableEditor;
10 12
 use Illuminate\Support\Facades\Auth;
13
+use App\Services\Attivita\AttivitaService;
11 14
 
12 15
 class PagamentoController extends Controller
13 16
 {
@@ -17,13 +20,15 @@ class PagamentoController extends Controller
17 20
         'create-pagamento' => 'Crea',
18 21
         'edit-pagamento' => 'Modifica',
19 22
         'delete-pagamento' => 'Elimina',
23
+        'storna-pagamento' => 'Storna',
20 24
     ];
21 25
 
22 26
     public static function middleware(): array
23 27
     {
24 28
         return [
25
-            new Middleware('permission:view-pagamento', only: ['index', 'show']),
29
+            new Middleware('permission:view-pagamento', only: ['index', 'show', 'show_modal']),
26 30
             new Middleware('permission:create-pagamento|edit-pagamento|delete-pagamento', only: ['store', 'update', 'destroy']),
31
+            new Middleware('permission:storna-pagamento|edit-pagamento', only: ['stornaPagamento']),
27 32
         ];
28 33
     }
29 34
 
@@ -37,29 +42,117 @@ class PagamentoController extends Controller
37 42
     {
38 43
         $request = request();
39 44
         $input = $request->all();
40
-        if($request->has('action')){
41
-            switch($input['action']){
45
+        if ($request->has('action')) {
46
+            switch ($input['action']) {
42 47
                 case 'create':
43
-                    if(!Auth::user()->can('create-pagamento')) return;
48
+                    if (! Auth::user()->can('create-pagamento')) {
49
+                        return;
50
+                    }
44 51
                     break;
45 52
                 case 'edit':
46
-                    if(!Auth::user()->can('edit-pagamento')) return;
53
+                    if (! Auth::user()->can('edit-pagamento')) {
54
+                        return;
55
+                    }
47 56
                     break;
48 57
                 case 'remove':
49
-                    if(!Auth::user()->can('delete-pagamento')) return;
58
+                    if (! Auth::user()->can('delete-pagamento')) {
59
+                        return;
60
+                    }
50 61
                     break;
51
-                }
62
+            }
63
+        }
64
+
52 65
         return $editor->process($request);
53 66
     }
54
-}
67
+
68
+    public function show_modal(Request $request)
69
+    {
70
+        $pagamento = Pagamento::query()->find($request->get('pagamento_id'));
71
+        if (! $pagamento) {
72
+            return redirect()->route('pagamento.index')->with('error', 'Pagamento non trovato');
73
+        }
74
+
75
+        AttivitaService::garantisciAttivita($pagamento->attivita_id);
76
+
77
+        if (! Auth::user()->can('view-pagamento')) {
78
+            return abort(403, 'Non hai permesso di vedere questo pagamento');
79
+        }
80
+
81
+        $pagamento->loadMissing(['metodo_pagamento', 'ordine', 'prenotazione', 'attivita', 'movimenti_prima_nota.categoria_contabile']);
82
+
83
+        return view('pagamento.show_modal', compact('pagamento'));
84
+    }
55 85
 
56 86
     public function show(Request $request)
57 87
     {
58
-        $pagamento = Pagamento::find($request->get('pagamento_id'));
59
-        if(!$pagamento) return redirect()->route('pagamento.index')->with('error', 'Pagamento non trovato');
88
+        $pagamento = Pagamento::query()->find($request->get('pagamento_id'));
89
+        if (! $pagamento) {
90
+            return redirect()->route('pagamento.index')->with('error', 'Pagamento non trovato');
91
+        }
92
+
93
+        AttivitaService::garantisciAttivita($pagamento->attivita_id);
94
+
95
+        if (! Auth::user()->can('view-pagamento')) {
96
+            return abort(403, 'Non hai permesso di vedere questo pagamento');
97
+        }
98
+
99
+        $pagamento->loadMissing(['metodo_pagamento', 'ordine', 'prenotazione', 'attivita', 'movimenti_prima_nota.categoria_contabile']);
100
+
101
+        return view('pagamento.show', compact('pagamento'));
102
+    }
103
+
104
+    public function stornaPagamento(Request $request)
105
+    {
106
+        $pagamento = Pagamento::query()->find($request->get('pagamento_id'));
107
+        if (! $pagamento) {
108
+            return response()->json(['success' => false, 'message' => 'Pagamento non trovato'], 404);
109
+        }
110
+
111
+        AttivitaService::garantisciAttivita($pagamento->attivita_id);
112
+
113
+        $info = $pagamento->info ?? [];
114
+        if (($info['storno'] ?? false) || $pagamento->stato === Pagamento::ANNULLATO) {
115
+            return response()->json(['success' => false, 'message' => 'Pagamento già stornato'], 422);
116
+        }
117
+
118
+        if ($pagamento->movimenti_prima_nota()->where('tipo_movimento', PrimaNota::USCITA)->exists()) {
119
+            return response()->json(['success' => false, 'message' => 'Pagamento già stornato'], 422);
120
+        }
121
+
122
+        $movimento = $pagamento->movimenti_prima_nota()
123
+            ->whereIn('tipo_movimento', [PrimaNota::ENTRATA, PrimaNota::NO_CONTABILE])
124
+            ->first();
125
+
126
+        if (! $movimento) {
127
+            return response()->json(['success' => false, 'message' => 'Movimento originale non trovato'], 422);
128
+        }
129
+
130
+        $tipoStorno = $movimento->tipo_movimento === PrimaNota::NO_CONTABILE
131
+            ? PrimaNota::NO_CONTABILE
132
+            : PrimaNota::USCITA;
133
+
134
+        PrimaNota::create([
135
+            'attivita_id' => $pagamento->attivita_id,
136
+            'categoria_contabile_id' => $movimento->categoria_contabile_id,
137
+            'pagamento_id' => $pagamento->id,
138
+            'fornitore_id' => $movimento->fornitore_id,
139
+            'ordine_id' => $pagamento->ordine_id,
140
+            'prenotazione_id' => $pagamento->prenotazione_id,
141
+            'evento_id' => $movimento->evento_id,
142
+            'tipo_movimento' => $tipoStorno,
143
+            'importo' => $pagamento->importo,
144
+            'causale' => 'Storno pagamento #' . $pagamento->id . ' - ' . $pagamento->causale,
145
+            'riferimento' => 'storno:pagamento:' . $pagamento->id,
146
+        ]);
147
+
148
+        $pagamento->stato = Pagamento::ANNULLATO;
149
+        $pagamento->info = array_merge($info, [
150
+            'storno' => true,
151
+            'storno_data' => now()->toIso8601String(),
152
+            'storno_user' => Auth::id(),
153
+        ]);
154
+        $pagamento->save();
60 155
 
61
-        if(!Auth::user()->can('view-pagamento')) return abort(403, 'Non hai permesso di vedere questo pagamento');
62
-        // if($request->has('modal')){
63
-        return view('pagamento.show', ['pagamento' => $pagamento]);
156
+        return response()->json(['success' => true]);
64 157
     }
65 158
 }

+ 22
- 3
app/Http/Controllers/PrimaNotaController.php View File

@@ -9,6 +9,7 @@ use App\DataTables\PrimaNotaDataTable;
9 9
 use App\DataTables\PrimaNotaDataTableEditor;
10 10
 use Illuminate\Support\Facades\Auth;
11 11
 use Illuminate\Routing\Controllers\Middleware;
12
+use App\Services\Attivita\AttivitaService;
12 13
 
13 14
 class PrimaNotaController extends Controller
14 15
 { 
@@ -23,7 +24,7 @@ class PrimaNotaController extends Controller
23 24
     public static function middleware(): array
24 25
     {
25 26
         return [
26
-            new Middleware('permission:view-primanota', only: ['index', 'statistiche']),
27
+            new Middleware('permission:view-primanota', only: ['index', 'statistiche', 'show']),
27 28
             new Middleware('permission:create-primanota|edit-primanota|delete-primanota', only: ['store', 'update', 'destroy']),
28 29
         ];
29 30
     }
@@ -88,12 +89,30 @@ class PrimaNotaController extends Controller
88 89
                     break;
89 90
                 }
90 91
         return $editor->process($request);
92
+        }
91 93
     }
92
-}
94
+
93 95
 
94 96
     public function show(Request $request)
95 97
     {
96
-        $primanota = PrimaNota::find($request->get('primanota_id'));
98
+        $primanota = PrimaNota::query()->find($request->get('primanota_id'));
99
+        if (! $primanota) {
100
+            return redirect()->route('prima-nota.index')->with('error', 'Prima nota non trovata');
101
+        }
102
+        AttivitaService::garantisciAttivita($primanota->attivita_id);
103
+
104
+        $primanota->loadMissing([
105
+            'attivita',
106
+            'categoria_contabile',
107
+            'pagamento.metodo_pagamento',
108
+            'ordine',
109
+            'prenotazione',
110
+            'evento',
111
+            'fornitore',
112
+        ]);
113
+
97 114
         return view('prima_nota.show', compact('primanota'));
98 115
     }
116
+
117
+
99 118
 }

+ 5
- 0
app/Models/AbstractModels/AbstractPagamento.php View File

@@ -88,4 +88,9 @@ abstract class AbstractPagamento extends \Illuminate\Foundation\Auth\User
88 88
     {
89 89
         return $this->belongsTo('\App\Models\MetodoPagamento', 'metodo_pagamento_id', 'id');
90 90
     }
91
+
92
+    public function movimenti_prima_nota()
93
+    {
94
+        return $this->hasMany('\App\Models\PrimaNota', 'pagamento_id', 'id');
95
+    }
91 96
 }

+ 25
- 5
app/Models/Categoriacontabile.php View File

@@ -3,7 +3,6 @@
3 3
 namespace App\Models;
4 4
 
5 5
 use Illuminate\Database\Eloquent\Model;
6
-use Illuminate\Support\Facades\Session;
7 6
 
8 7
 class Categoriacontabile extends \App\Models\AbstractModels\AbstractCategoriacontabile
9 8
 {
@@ -39,10 +38,31 @@ class Categoriacontabile extends \App\Models\AbstractModels\AbstractCategoriacon
39 38
    
40 39
     }
41 40
     
42
-    public static function vendita(){
43
-        return self::where('attivita_id', session('attivita_attuale'))->where('is_attiva', true)->where('nome', 'Vendita')->first();
41
+    public static function vendita(?int $attivitaId = null)
42
+    {
43
+        $attivitaId = $attivitaId ?: (int) session('attivita_attuale');
44
+        if ($attivitaId <= 0) {
45
+            return null;
46
+        }
47
+
48
+        return self::query()
49
+            ->where('attivita_id', $attivitaId)
50
+            ->where('is_attiva', true)
51
+            ->where('nome', 'Vendita')
52
+            ->first();
44 53
     }
45
-    public static function staff(){
46
-        return self::where('attivita_id', session('attivita_attuale'))->where('is_attiva', true)->where('nome', 'Staff')->first();
54
+
55
+    public static function staff(?int $attivitaId = null)
56
+    {
57
+        $attivitaId = $attivitaId ?: (int) session('attivita_attuale');
58
+        if ($attivitaId <= 0) {
59
+            return null;
60
+        }
61
+
62
+        return self::query()
63
+            ->where('attivita_id', $attivitaId)
64
+            ->where('is_attiva', true)
65
+            ->where('nome', 'Staff')
66
+            ->first();
47 67
     }
48 68
 }

+ 48
- 0
app/Models/Pagamento.php View File

@@ -46,4 +46,52 @@ class Pagamento extends \App\Models\AbstractModels\AbstractPagamento
46 46
 
47 47
         
48 48
     }
49
+
50
+    /**
51
+     * Pagamenti che contano nell'incasso reale (cassa / bilancio).
52
+     * Esclude sempre i pagamenti staff (controvalore, non liquidità).
53
+     */
54
+    public function scopePerIncasso($query)
55
+    {
56
+        return $query
57
+            ->where('stato', self::PAGATO)
58
+            ->whereDoesntHave('metodo_pagamento', function ($metodoQuery) {
59
+                $metodoQuery->where('tipo', MetodoPagamento::STAFF);
60
+            });
61
+    }
62
+
63
+    /**
64
+     * Controvalore consumi staff (pagati ma non liquidità).
65
+     */
66
+    public function scopeStaffPagato($query)
67
+    {
68
+        return $query
69
+            ->where('stato', self::PAGATO)
70
+            ->whereHas('metodo_pagamento', function ($metodoQuery) {
71
+                $metodoQuery->where('tipo', MetodoPagamento::STAFF);
72
+            });
73
+    }
74
+
75
+    public function isStornato(): bool
76
+    {
77
+        if ($this->stato === self::ANNULLATO) {
78
+            return true;
79
+        }
80
+
81
+        $info = $this->info ?? [];
82
+        if ($info['storno'] ?? false) {
83
+            return true;
84
+        }
85
+
86
+        if ($this->relationLoaded('movimenti_prima_nota')) {
87
+            return $this->movimenti_prima_nota->contains(
88
+                'tipo_movimento',
89
+                PrimaNota::USCITA
90
+            );
91
+        }
92
+
93
+        return $this->movimenti_prima_nota()
94
+            ->where('tipo_movimento', PrimaNota::USCITA)
95
+            ->exists();
96
+    }
49 97
 }

+ 41
- 4
app/Models/PrimaNota.php View File

@@ -14,6 +14,43 @@ class PrimaNota extends \App\Models\AbstractModels\AbstractPrimaNota
14 14
     const STAFF = 'staff';
15 15
     const NO_CONTABILE = 'no_contabile';
16 16
 
17
+    /**
18
+     * Tipo per aggregazioni bilancio/report: entrata, uscita, no_contabile.
19
+     */
20
+    public static function resolveTipoAggregazione(?string $tipoMovimento, ?float $importo = null): string
21
+    {
22
+        $tipo = strtolower(trim((string) $tipoMovimento));
23
+
24
+        if ($tipo === self::NO_CONTABILE) {
25
+            return 'no_contabile';
26
+        }
27
+
28
+        if ($tipo === self::USCITA || $tipo === self::STORNO) {
29
+            return 'uscita';
30
+        }
31
+
32
+        if ($importo !== null && (float) $importo < 0) {
33
+            return 'uscita';
34
+        }
35
+
36
+        return 'entrata';
37
+    }
38
+
39
+    public function tipoPerAggregazione(): string
40
+    {
41
+        return self::resolveTipoAggregazione($this->tipo_movimento, $this->importo);
42
+    }
43
+
44
+    public function isContabilizzato(): bool
45
+    {
46
+        return $this->tipoPerAggregazione() !== 'no_contabile';
47
+    }
48
+
49
+    public function evento()
50
+    {
51
+        return $this->belongsTo(Evento::class, 'evento_id', 'id');
52
+    }
53
+
17 54
     public function scopeConFiltriIndice($query, ?array $filtri = null)
18 55
     {
19 56
         $filtri = $filtri ?? request()->only([
@@ -70,10 +107,10 @@ class PrimaNota extends \App\Models\AbstractModels\AbstractPrimaNota
70 107
                 'label' => 'Uscita',
71 108
                 'value' => self::USCITA,
72 109
             ],
73
-            // self::STORNO => [
74
-            //     'label' => 'Storno',
75
-            //     'value' => self::STORNO,
76
-            // ],
110
+            self::STORNO => [
111
+                'label' => 'Storno',
112
+                'value' => self::STORNO,
113
+            ],
77 114
             // self::NOTA_CREDITO => [
78 115
             //     'label' => 'Nota credito',
79 116
             //     'value' => self::NOTA_CREDITO,

+ 33
- 13
app/Observers/PagamentoObserver.php View File

@@ -13,6 +13,30 @@ class PagamentoObserver
13 13
     //
14 14
     public function created(Pagamento $pagamento): void
15 15
     {
16
+        //
17
+    }
18
+
19
+    public function updated(Pagamento $pagamento): void
20
+    {
21
+        if (! $pagamento->wasChanged('stato') || $pagamento->stato !== Pagamento::PAGATO) {
22
+            return;
23
+        }
24
+
25
+        // idempotenza: non duplicare se esiste già un movimento di entrata
26
+        if ($pagamento->movimenti_prima_nota()
27
+            ->whereIn('tipo_movimento', [PrimaNota::ENTRATA, PrimaNota::NO_CONTABILE])
28
+            ->exists()) {
29
+            return;
30
+        }
31
+
32
+        $metodo = $pagamento->metodo_pagamento;
33
+        if (! $metodo) {
34
+            Log::warning('Pagamento senza metodo_pagamento, prima nota non creata', [
35
+                'pagamento_id' => $pagamento->id,
36
+            ]);
37
+            return;
38
+        }
39
+
16 40
         //Crea prima nota di entrata
17 41
         $primaNota = PrimaNota::create([
18 42
             'attivita_id' => $pagamento->attivita_id,
@@ -23,27 +47,23 @@ class PagamentoObserver
23 47
             // 'categoria_contabile_id' => $pagamento->metodo_pagamento->tipo == MetodoPagamento::STAFF ? CategoriaContabile::STAFF : CategoriaContabile::VENDITA,
24 48
             'ordine_id' => $pagamento->ordine_id,
25 49
         ]);
26
-        //Assegna categoria contabile
27
-        if($pagamento->metodo_pagamento->tipo == MetodoPagamento::STAFF){
28
-            $primaNota->categoria_contabile_id = Categoriacontabile::staff()->id??null;
29
-        }else{
30
-            $primaNota->categoria_contabile_id = Categoriacontabile::vendita()->id??null;
50
+        //Assegna categoria contabile (dall'attività del pagamento, non dalla sessione)
51
+        if ($metodo->tipo == MetodoPagamento::STAFF) {
52
+            $primaNota->categoria_contabile_id = Categoriacontabile::staff((int) $pagamento->attivita_id)?->id;
53
+        } else {
54
+            $primaNota->categoria_contabile_id = Categoriacontabile::vendita((int) $pagamento->attivita_id)?->id;
31 55
         }
32
-        //Assegna tipo movimento
33
-        if($pagamento->metodo_pagamento->escludi_contabilita){
56
+
57
+        // Staff e metodi esclusi: traccia in prima nota ma fuori dal saldo (no_contabile)
58
+        if ($metodo->tipo == MetodoPagamento::STAFF || $metodo->escludi_contabilita) {
34 59
             $primaNota->tipo_movimento = PrimaNota::NO_CONTABILE;
35
-        }else{
60
+        } else {
36 61
             $primaNota->tipo_movimento = PrimaNota::ENTRATA;
37 62
         }
38 63
 
39 64
         $primaNota->save();
40 65
     }
41 66
 
42
-    public function updated(Pagamento $pagamento): void
43
-    {
44
-        //
45
-    }
46
-
47 67
     public function deleted(Pagamento $pagamento): void
48 68
     {
49 69
         //

+ 7
- 1
resources/views/pagamento/menu.blade.php View File

@@ -1,13 +1,19 @@
1 1
 <?php
2 2
 use Illuminate\Support\Facades\Auth;
3 3
 ?>
4
-@if(Auth::user()->can('edit-pagamento') || Auth::user()->can('delete-pagamento'))
4
+@if(Auth::user()->can('view-pagamento') || Auth::user()->can('edit-pagamento') || Auth::user()->can('delete-pagamento'))
5 5
 <div class="dropdown">
6 6
     <button type="button" class="btn p-0 dropdown-toggle hide-arrow" data-bs-toggle="dropdown" aria-expanded="false">
7 7
         <i class="bx bx-dots-vertical-rounded fs-large"></i>
8 8
     </button>
9 9
     <div class="dropdown-menu">
10 10
 
11
+        @if(Auth::user()->can('view-pagamento'))
12
+        <a href="{{ route('pagamento.show', ['pagamento_id' => $entity->id]) }}" class="dropdown-item">
13
+            <i class="bx bx-show me-1"></i> Dettaglio
14
+        </a>
15
+        @endif
16
+
11 17
         @if(Auth::user()->can('edit-pagamento'))
12 18
         <a href="#" class="dropdown-item editor_edit">
13 19
             <i class="bx bx-edit-alt me-1"></i> Modifica

+ 337
- 32
resources/views/pagamento/show.blade.php View File

@@ -1,33 +1,338 @@
1
+@php
2
+use App\Models\Pagamento;
3
+use App\Models\PrimaNota;
1 4
 
2
-<table class="table table-bordered w-auto">
3
-    <tbody>
4
-        <tr>
5
-            <th scope="row">ID</th>
6
-            <td>{{ $pagamento->id }}</td>
7
-        </tr>
8
-        <tr>
9
-            <th scope="row">Importo</th>
10
-            <td>{{ $pagamento->importo }}</td>
11
-        </tr>
12
-        <tr>
13
-            <th scope="row">Metodo pagamento</th>
14
-            <td>{{ $pagamento->metodo_pagamento->nome }}</td>
15
-        </tr>
16
-        <tr>
17
-            <th scope="row">Stato</th>
18
-            <td>{{ $pagamento->stato }}</td>
19
-        </tr>
20
-        <tr>
21
-            <th scope="row">Causale</th>
22
-            <td>{{ $pagamento->causale }}</td>
23
-        </tr>
24
-        <tr>
25
-            <th scope="row">Note</th>
26
-            <td>{{ $pagamento->note }}</td>
27
-        </tr>
28
-        <tr>
29
-            <th scope="row">Info</th>
30
-            <td>{{ $pagamento->info }}</td>
31
-        </tr>
32
-    </tbody>
33
-</table>
5
+$statoMeta = Pagamento::getStati()->get($pagamento->stato);
6
+$statoLabel = $statoMeta['label'] ?? ($pagamento->stato ?: '—');
7
+$importoFormatted = number_format((float) $pagamento->importo, 2, ',', '.');
8
+$infoExtra = is_array($pagamento->info) ? $pagamento->info : [];
9
+
10
+$collegamenti = [
11
+    [
12
+        'label' => 'Ordine',
13
+        'icon' => 'bx-receipt',
14
+        'id' => $pagamento->ordine_id,
15
+        'title' => $pagamento->ordine_id ? 'Ordine #'.$pagamento->ordine_id : null,
16
+        'subtitle' => $pagamento->ordine?->stato ? 'Stato: '.$pagamento->ordine->stato : null,
17
+        'url' => $pagamento->ordine_id
18
+            ? route('ordine.show', ['ordine_id' => $pagamento->ordine_id])
19
+            : null,
20
+    ],
21
+    [
22
+        'label' => 'Prenotazione',
23
+        'icon' => 'bx-calendar-check',
24
+        'id' => $pagamento->prenotazione_id,
25
+        'title' => $pagamento->prenotazione
26
+            ? trim($pagamento->prenotazione->cognome.' '.$pagamento->prenotazione->nome)
27
+            : ($pagamento->prenotazione_id ? 'Prenotazione #'.$pagamento->prenotazione_id : null),
28
+        'subtitle' => $pagamento->prenotazione?->email,
29
+        'url' => $pagamento->prenotazione_id
30
+            ? route('prenotazione.show', ['prenotazione_id' => $pagamento->prenotazione_id])
31
+            : null,
32
+    ],
33
+];
34
+
35
+foreach ($pagamento->movimenti_prima_nota as $primaNota) {
36
+    $tipoLabel = PrimaNota::getTipiMovimento()
37
+        ->firstWhere('value', $primaNota->tipo_movimento)['label'] ?? $primaNota->tipo_movimento;
38
+
39
+    $subtitleParts = array_filter([
40
+        $tipoLabel,
41
+        $primaNota->categoria_contabile?->nome,
42
+        $primaNota->causale,
43
+    ]);
44
+
45
+    $collegamenti[] = [
46
+        'label' => 'Prima nota',
47
+        'icon' => 'bx-book-content',
48
+        'id' => $primaNota->id,
49
+        'title' => 'Movimento #'.$primaNota->id,
50
+        'subtitle' => implode(' · ', $subtitleParts),
51
+        'url' => route('prima-nota.show', ['primanota_id' => $primaNota->id]),
52
+    ];
53
+}
54
+
55
+$collegamentiAttivi = collect($collegamenti)->filter(fn ($c) => filled($c['id']))->count();
56
+@endphp
57
+
58
+@extends('layouts/layoutMaster')
59
+
60
+@section('title', 'Pagamento #'.$pagamento->id)
61
+
62
+@section('pageTitle')
63
+<div class="d-flex flex-column">
64
+  <h4 class="mb-0 text-sm-small">
65
+    <i class="bx bx-money d-none d-md-inline-flex d-lg-inline-flex"></i>
66
+    Dettaglio pagamento
67
+  </h4>
68
+  <nav aria-label="breadcrumb" style="font-size: smaller;" class="d-none d-md-block d-lg-block">
69
+    <ol class="breadcrumb breadcrumb-custom-icon mb-0">
70
+      <li class="breadcrumb-item">
71
+        <a href="#">Contabilità</a>
72
+        <i class="breadcrumb-icon icon-base bx bx-chevron-right align-middle"></i>
73
+      </li>
74
+      <li class="breadcrumb-item">
75
+        <a href="{{ route('pagamento.index') }}">Pagamenti</a>
76
+        <i class="breadcrumb-icon icon-base bx bx-chevron-right align-middle"></i>
77
+      </li>
78
+      <li class="breadcrumb-item active text-primary">#{{ $pagamento->id }}</li>
79
+    </ol>
80
+  </nav>
81
+</div>
82
+@endsection
83
+
84
+@section('content')
85
+@include('_partials.status')
86
+
87
+<style>
88
+  .pg-show-hero {
89
+    border: 1px solid rgba(67, 89, 113, .12);
90
+    border-radius: 1rem;
91
+    overflow: hidden;
92
+    background: var(--bs-paper-bg, #fff);
93
+  }
94
+  .pg-show-hero__main {
95
+    display: flex;
96
+    flex-wrap: wrap;
97
+    align-items: center;
98
+    justify-content: space-between;
99
+    gap: 1.25rem;
100
+    padding: 1.35rem 1.5rem;
101
+  }
102
+  .pg-show-hero__amount {
103
+    font-size: clamp(1.75rem, 4vw, 2.35rem);
104
+    font-weight: 700;
105
+    line-height: 1.1;
106
+    letter-spacing: -0.02em;
107
+    color: var(--bs-heading-color);
108
+  }
109
+  .pg-show-hero__meta {
110
+    display: flex;
111
+    flex-wrap: wrap;
112
+    align-items: center;
113
+    gap: .5rem;
114
+  }
115
+  .pg-show-hero__causale {
116
+    font-size: 1.05rem;
117
+    font-weight: 600;
118
+    margin-bottom: .35rem;
119
+  }
120
+  .pg-show-hero__sub {
121
+    color: var(--bs-secondary-color);
122
+    font-size: .875rem;
123
+  }
124
+  .pg-show-section-title {
125
+    font-size: .8rem;
126
+    font-weight: 600;
127
+    text-transform: uppercase;
128
+    letter-spacing: .04em;
129
+    color: var(--bs-secondary-color);
130
+    margin-bottom: .75rem;
131
+  }
132
+  .pg-show-dl dt {
133
+    color: var(--bs-secondary-color);
134
+    font-weight: 500;
135
+    font-size: .8125rem;
136
+  }
137
+  .pg-show-dl dd {
138
+    font-size: .9375rem;
139
+    margin-bottom: .65rem;
140
+  }
141
+  .pg-show-link-card {
142
+    display: flex;
143
+    align-items: center;
144
+    gap: .85rem;
145
+    padding: .85rem 1rem;
146
+    border: 1px solid rgba(67, 89, 113, .12);
147
+    border-radius: .75rem;
148
+    background: var(--bs-paper-bg, #fff);
149
+    transition: border-color .15s ease, box-shadow .15s ease;
150
+  }
151
+  .pg-show-link-card:hover {
152
+    border-color: rgba(var(--bs-primary-rgb), .35);
153
+    box-shadow: 0 .125rem .5rem rgba(67, 89, 113, .08);
154
+  }
155
+  .pg-show-link-card--empty {
156
+    opacity: .72;
157
+    background: rgba(67, 89, 113, .03);
158
+  }
159
+  .pg-show-link-card__icon {
160
+    width: 2.5rem;
161
+    height: 2.5rem;
162
+    border-radius: .65rem;
163
+    display: flex;
164
+    align-items: center;
165
+    justify-content: center;
166
+    flex-shrink: 0;
167
+    background: rgba(var(--bs-primary-rgb), .1);
168
+    color: var(--bs-primary);
169
+    font-size: 1.25rem;
170
+  }
171
+  .pg-show-link-card--empty .pg-show-link-card__icon {
172
+    background: rgba(67, 89, 113, .08);
173
+    color: var(--bs-secondary-color);
174
+  }
175
+  .pg-show-link-card__body { flex: 1; min-width: 0; }
176
+  .pg-show-link-card__label {
177
+    font-size: .75rem;
178
+    color: var(--bs-secondary-color);
179
+    margin-bottom: .1rem;
180
+  }
181
+  .pg-show-link-card__title {
182
+    font-weight: 600;
183
+    font-size: .9375rem;
184
+    line-height: 1.3;
185
+  }
186
+  .pg-show-link-card__subtitle {
187
+    font-size: .8125rem;
188
+    color: var(--bs-secondary-color);
189
+    margin-top: .15rem;
190
+  }
191
+  .pg-show-tech {
192
+    font-size: .8125rem;
193
+    color: var(--bs-secondary-color);
194
+  }
195
+  .pg-show-tech code { font-size: .75rem; }
196
+</style>
197
+
198
+<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
199
+  <a href="{{ route('pagamento.index') }}" class="btn btn-label-secondary btn-sm">
200
+    <i class="bx bx-arrow-back me-1"></i> Torna ai pagamenti
201
+  </a>
202
+  <span class="text-muted small">ID pagamento <strong>#{{ $pagamento->id }}</strong></span>
203
+</div>
204
+
205
+<div class="pg-show-hero mb-4">
206
+  <div class="pg-show-hero__main">
207
+    <div class="flex-grow-1 min-w-0">
208
+      <div class="pg-show-hero__meta mb-2">
209
+        @include('prenotazione._partials.stato-prenotazione', ['stato' => $pagamento->stato])
210
+        @if($pagamento->metodo_pagamento)
211
+          <span class="badge bg-label-info">{{ $pagamento->metodo_pagamento->nome }}</span>
212
+        @endif
213
+      </div>
214
+
215
+      <div class="pg-show-hero__causale">
216
+        {{ $pagamento->causale ?: 'Pagamento senza causale' }}
217
+      </div>
218
+
219
+      <div class="pg-show-hero__sub">
220
+        <i class="bx bx-time-five me-1"></i>
221
+        Registrato il {{ $pagamento->created_at?->format('d/m/Y') }}
222
+        alle {{ $pagamento->created_at?->format('H:i') }}
223
+        @if($pagamento->attivita?->nome)
224
+          · {{ $pagamento->attivita->nome }}
225
+        @endif
226
+      </div>
227
+    </div>
228
+
229
+    <div class="text-end">
230
+      <div class="pg-show-hero__amount">€ {{ $importoFormatted }}</div>
231
+      <div class="text-muted small mt-1">Importo</div>
232
+    </div>
233
+  </div>
234
+</div>
235
+
236
+<div class="row g-4">
237
+  <div class="col-12 col-lg-4">
238
+    <div class="card h-100">
239
+      <div class="card-body">
240
+        <div class="pg-show-section-title">Informazioni</div>
241
+        <dl class="row pg-show-dl mb-0">
242
+          <dt class="col-5">Attività</dt>
243
+          <dd class="col-7 fw-medium">{{ $pagamento->attivita?->nome ?? '—' }}</dd>
244
+
245
+          <dt class="col-5">Stato</dt>
246
+          <dd class="col-7">{{ $statoLabel }}</dd>
247
+
248
+          <dt class="col-5">Metodo</dt>
249
+          <dd class="col-7">{{ $pagamento->metodo_pagamento?->nome ?? '—' }}</dd>
250
+
251
+          <dt class="col-5">Registrato</dt>
252
+          <dd class="col-7">{{ $pagamento->created_at?->format('d/m/Y H:i') ?? '—' }}</dd>
253
+
254
+          <dt class="col-5">Aggiornato</dt>
255
+          <dd class="col-7">{{ $pagamento->updated_at?->format('d/m/Y H:i') ?? '—' }}</dd>
256
+        </dl>
257
+
258
+        @if(filled($pagamento->note))
259
+          <div class="pg-show-section-title mt-3">Note</div>
260
+          <p class="mb-0">{{ $pagamento->note }}</p>
261
+        @endif
262
+      </div>
263
+    </div>
264
+  </div>
265
+
266
+  <div class="col-12 col-lg-8">
267
+    <div class="card h-100">
268
+      <div class="card-body">
269
+        <div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
270
+          <div class="pg-show-section-title mb-0">Collegamenti</div>
271
+          <span class="badge bg-label-secondary">{{ $collegamentiAttivi }} attivi su {{ count($collegamenti) }}</span>
272
+        </div>
273
+
274
+        <div class="row g-3">
275
+          @foreach($collegamenti as $collegamento)
276
+            <div class="col-12 col-md-6">
277
+              @if($collegamento['url'])
278
+                <a href="{{ $collegamento['url'] }}" class="pg-show-link-card text-body d-block text-decoration-none">
279
+                  <div class="pg-show-link-card__icon">
280
+                    <i class="bx {{ $collegamento['icon'] }}"></i>
281
+                  </div>
282
+                  <div class="pg-show-link-card__body">
283
+                    <div class="pg-show-link-card__label">{{ $collegamento['label'] }}</div>
284
+                    <div class="pg-show-link-card__title">{{ $collegamento['title'] }}</div>
285
+                    @if(filled($collegamento['subtitle']))
286
+                      <div class="pg-show-link-card__subtitle">{{ $collegamento['subtitle'] }}</div>
287
+                    @endif
288
+                  </div>
289
+                  <i class="bx bx-chevron-right text-muted"></i>
290
+                </a>
291
+              @else
292
+                <div class="pg-show-link-card pg-show-link-card--empty">
293
+                  <div class="pg-show-link-card__icon">
294
+                    <i class="bx {{ $collegamento['icon'] }}"></i>
295
+                  </div>
296
+                  <div class="pg-show-link-card__body">
297
+                    <div class="pg-show-link-card__label">{{ $collegamento['label'] }}</div>
298
+                    <div class="pg-show-link-card__title text-muted">Non collegato</div>
299
+                  </div>
300
+                </div>
301
+              @endif
302
+            </div>
303
+          @endforeach
304
+        </div>
305
+
306
+        @if($infoExtra !== [])
307
+          <div class="pg-show-section-title mt-4">Info aggiuntive</div>
308
+          <dl class="row pg-show-dl mb-0">
309
+            @foreach($infoExtra as $chiave => $valore)
310
+              <dt class="col-4 text-capitalize">{{ str_replace('_', ' ', (string) $chiave) }}</dt>
311
+              <dd class="col-8">
312
+                @if(is_array($valore) || is_object($valore))
313
+                  <code>{{ json_encode($valore, JSON_UNESCAPED_UNICODE) }}</code>
314
+                @else
315
+                  {{ filled($valore) ? $valore : '—' }}
316
+                @endif
317
+              </dd>
318
+            @endforeach
319
+          </dl>
320
+        @endif
321
+      </div>
322
+    </div>
323
+  </div>
324
+</div>
325
+
326
+<!-- <div class="card mt-4">
327
+  <div class="card-body py-3">
328
+    <div class="pg-show-section-title mb-2">Riferimenti di sistema</div>
329
+    <div class="pg-show-tech d-flex flex-wrap gap-3">
330
+      <span>attivita_id <code>{{ $pagamento->attivita_id ?? '—' }}</code></span>
331
+      <span>metodo_pagamento_id <code>{{ $pagamento->metodo_pagamento_id ?? '—' }}</code></span>
332
+      <span>ordine_id <code>{{ $pagamento->ordine_id ?? '—' }}</code></span>
333
+      <span>prenotazione_id <code>{{ $pagamento->prenotazione_id ?? '—' }}</code></span>
334
+      <span>stato <code>{{ $pagamento->stato ?? '—' }}</code></span>
335
+    </div>
336
+  </div>
337
+</div> -->
338
+@endsection

+ 44
- 0
resources/views/pagamento/show_modal.blade.php View File

@@ -0,0 +1,44 @@
1
+@php
2
+use App\Models\Pagamento;
3
+$statoLabel = Pagamento::getStati()->get($pagamento->stato)['label'] ?? ($pagamento->stato ?: '—');
4
+@endphp
5
+
6
+<table class="table table-bordered mb-0">
7
+  <tbody>
8
+    <tr>
9
+      <th scope="row" class="text-muted" style="width: 36%;">ID</th>
10
+      <td>{{ $pagamento->id }}</td>
11
+    </tr>
12
+    <tr>
13
+      <th scope="row" class="text-muted">Importo</th>
14
+      <td class="fw-medium">€ {{ number_format((float) $pagamento->importo, 2, ',', '.') }}</td>
15
+    </tr>
16
+    <tr>
17
+      <th scope="row" class="text-muted">Metodo</th>
18
+      <td>{{ $pagamento->metodo_pagamento?->nome ?? '—' }}</td>
19
+    </tr>
20
+    <tr>
21
+      <th scope="row" class="text-muted">Stato</th>
22
+      <td>
23
+        @include('prenotazione._partials.stato-prenotazione', ['stato' => $pagamento->stato])
24
+        <span class="text-muted small ms-1">{{ $statoLabel }}</span>
25
+      </td>
26
+    </tr>
27
+    <tr>
28
+      <th scope="row" class="text-muted">Causale</th>
29
+      <td>{{ $pagamento->causale ?: '—' }}</td>
30
+    </tr>
31
+    <tr>
32
+      <th scope="row" class="text-muted">Note</th>
33
+      <td>{{ $pagamento->note ?: '—' }}</td>
34
+    </tr>
35
+  </tbody>
36
+</table>
37
+
38
+@if($pagamento->id)
39
+  <div class="mt-3 text-end">
40
+    <a href="{{ route('pagamento.show', ['pagamento_id' => $pagamento->id]) }}" class="btn btn-sm btn-primary">
41
+      Apri dettaglio
42
+    </a>
43
+  </div>
44
+@endif

+ 29
- 2
resources/views/prima_nota/index.blade.php View File

@@ -241,7 +241,7 @@ function showEntita(id,tipo_entita){
241 241
 
242 242
   switch(tipo_entita){
243 243
     case 'pagamento':
244
-      $('#basicModal div.modal-body').load("{{ route('pagamento.show') }}?pagamento_id=" + id);
244
+      $('#basicModal div.modal-body').load("{{ route('pagamento.show_modal') }}?pagamento_id=" + id);
245 245
       break;
246 246
     case 'ordine':
247 247
       $('#basicModal div.modal-body').load("{{ route('ordine.show_modal') }}?ordine_id=" + id);
@@ -250,7 +250,7 @@ function showEntita(id,tipo_entita){
250 250
       $('#basicModal div.modal-body').load("{{ route('prenotazione.show') }}?prenotazione_id=" + id);
251 251
       break;
252 252
     case 'evento':
253
-      $('#basicModal div.modal-body').load("{{ route('evento.show') }}?evento_id=" + id);
253
+      $('#basicModal div.modal-body').load("{{ route('evento.show.legacy') }}?evento_id=" + id);
254 254
       break;
255 255
     case 'fornitore':
256 256
       $('#basicModal div.modal-body').load("{{ route('fornitore.show_modal') }}?fornitore_id=" + id);
@@ -263,5 +263,32 @@ function showEntita(id,tipo_entita){
263 263
   $('#basicModal').modal('show');
264 264
 
265 265
 }
266
+
267
+function stornaPagamento(id){
268
+  Swal.fire({
269
+    title: 'Storna Pagamento',
270
+    text: 'Sei sicuro di voler stornare il pagamento?',
271
+    icon: 'warning',
272
+    showCancelButton: true,
273
+    confirmButtonText: 'Storna',
274
+    cancelButtonText: 'Annulla',
275
+  }).then((result) => {
276
+    if (result.isConfirmed) {
277
+      $.post("{{ route('pagamento.storna-pagamento') }}", {
278
+        pagamento_id: id,
279
+        _token: "{{ csrf_token() }}",
280
+      }).done(function (data) {
281
+        if (data.success) {
282
+          window.LaravelDataTables['dataTable_primanota'].update();
283
+          Swal.fire('Stornato', 'Il pagamento è stato stornato.', 'success');
284
+        } else {
285
+          Swal.fire('Errore', data.message ?? 'Storno non riuscito', 'error');
286
+        }
287
+      }).fail(function (xhr) {
288
+        Swal.fire('Errore', xhr.responseJSON?.message ?? 'Storno non riuscito', 'error');
289
+      });
290
+    }
291
+  });
292
+}
266 293
 </script>
267 294
 @endsection

+ 19
- 0
resources/views/prima_nota/menu.blade.php View File

@@ -1,5 +1,6 @@
1 1
 <?php
2 2
 use Illuminate\Support\Facades\Auth;
3
+use App\Models\PrimaNota;
3 4
 ?>
4 5
 
5 6
 <div class="dropdown">
@@ -8,11 +9,29 @@ use Illuminate\Support\Facades\Auth;
8 9
     </button>
9 10
     <div class="dropdown-menu">
10 11
 
12
+        @if(Auth::user()->can('view-primanota'))
13
+        <a href="{{ route('prima-nota.show', ['primanota_id' => $entity->id]) }}" class="dropdown-item">
14
+            <i class="bx bx-show me-1"></i> Dettaglio
15
+        </a>
16
+        @endif
17
+
11 18
         @if(Auth::user()->can('edit-primanota'))
12 19
         <a href="#" class="dropdown-item editor_edit">
13 20
             <i class="bx bx-edit-alt me-1"></i> Modifica
14 21
         </a>
15 22
         @endif
23
+
24
+        @if(
25
+            $entity->pagamento_id
26
+            && $entity->tipo_movimento === PrimaNota::ENTRATA
27
+            && Auth::user()->can('storna-pagamento')
28
+            && $entity->pagamento
29
+            && ! $entity->pagamento->isStornato()
30
+        )
31
+        <a href="#" class="dropdown-item storna_pagamento" onclick="stornaPagamento({{ $entity->pagamento_id }})">
32
+            <i class="bx bx-undo me-1"></i> Storna Pagamento
33
+        </a>
34
+        @endif
16 35
        
17 36
         @if(Auth::user()->can('delete-primanota'))
18 37
         <a href="#" class="dropdown-item editor_delete">

+ 383
- 0
resources/views/prima_nota/show.blade.php View File

@@ -0,0 +1,383 @@
1
+@php
2
+use App\Models\PrimaNota;
3
+
4
+$tipoLabel = PrimaNota::getTipiMovimento()
5
+    ->firstWhere('value', $primanota->tipo_movimento)['label'] ?? $primanota->tipo_movimento ?? '—';
6
+
7
+$importoFormatted = number_format((float) $primanota->importo, 2, ',', '.');
8
+$isEntrata = $primanota->tipo_movimento === PrimaNota::ENTRATA;
9
+$isUscita = $primanota->tipo_movimento === PrimaNota::USCITA;
10
+
11
+$collegamenti = [
12
+    [
13
+        'key' => 'pagamento',
14
+        'label' => 'Pagamento',
15
+        'icon' => 'bx-money',
16
+        'id' => $primanota->pagamento_id,
17
+        'title' => $primanota->pagamento
18
+            ? 'Pagamento #' . $primanota->pagamento_id
19
+            : null,
20
+        'subtitle' => $primanota->pagamento?->metodo_pagamento?->nome,
21
+        'url' => $primanota->pagamento_id
22
+            ? route('pagamento.show', ['pagamento_id' => $primanota->pagamento_id])
23
+            : null,
24
+    ],
25
+    [
26
+        'key' => 'ordine',
27
+        'label' => 'Ordine',
28
+        'icon' => 'bx-receipt',
29
+        'id' => $primanota->ordine_id,
30
+        'title' => $primanota->ordine_id ? 'Ordine #' . $primanota->ordine_id : null,
31
+        'subtitle' => $primanota->ordine?->stato
32
+            ? 'Stato: ' . $primanota->ordine->stato
33
+            : null,
34
+        'url' => $primanota->ordine_id
35
+            ? route('ordine.show', ['ordine_id' => $primanota->ordine_id])
36
+            : null,
37
+    ],
38
+    [
39
+        'key' => 'prenotazione',
40
+        'label' => 'Prenotazione',
41
+        'icon' => 'bx-calendar-check',
42
+        'id' => $primanota->prenotazione_id,
43
+        'title' => $primanota->prenotazione
44
+            ? trim($primanota->prenotazione->cognome . ' ' . $primanota->prenotazione->nome)
45
+            : ($primanota->prenotazione_id ? 'Prenotazione #' . $primanota->prenotazione_id : null),
46
+        'subtitle' => $primanota->prenotazione?->email,
47
+        'url' => $primanota->prenotazione_id
48
+            ? route('prenotazione.show', ['prenotazione_id' => $primanota->prenotazione_id])
49
+            : null,
50
+    ],
51
+    [
52
+        'key' => 'evento',
53
+        'label' => 'Evento',
54
+        'icon' => 'bx-calendar-event',
55
+        'id' => $primanota->evento_id,
56
+        'title' => $primanota->evento?->nome ?? ($primanota->evento_id ? 'Evento #' . $primanota->evento_id : null),
57
+        'subtitle' => $primanota->evento?->data_inizio
58
+            ? \Carbon\Carbon::parse($primanota->evento->data_inizio)->format('d/m/Y')
59
+            : null,
60
+        'url' => $primanota->evento_id
61
+            ? route('evento.show.legacy', ['evento_id' => $primanota->evento_id])
62
+            : null,
63
+    ],
64
+    [
65
+        'key' => 'fornitore',
66
+        'label' => 'Fornitore',
67
+        'icon' => 'bxs-truck',
68
+        'id' => $primanota->fornitore_id,
69
+        'title' => $primanota->fornitore?->nome ?? ($primanota->fornitore_id ? 'Fornitore #' . $primanota->fornitore_id : null),
70
+        'subtitle' => $primanota->fornitore?->email,
71
+        'url' => $primanota->fornitore_id
72
+            ? route('fornitore.show', ['fornitore_id' => $primanota->fornitore_id])
73
+            : null,
74
+    ],
75
+];
76
+
77
+$collegamentiAttivi = collect($collegamenti)->filter(fn ($c) => filled($c['id']))->count();
78
+@endphp
79
+
80
+@extends('layouts/layoutMaster')
81
+
82
+@section('title', 'Movimento #' . $primanota->id)
83
+
84
+@section('pageTitle')
85
+<div class="d-flex flex-column">
86
+  <h4 class="mb-0 text-sm-small mt-lg-0 mt-md-0 mt-sm-0">
87
+    <i class="bx bx-receipt d-none d-md-inline-flex d-lg-inline-flex"></i>
88
+    Dettaglio movimento
89
+  </h4>
90
+  <nav aria-label="breadcrumb" style="font-size: smaller;" class="d-none d-md-block d-lg-block">
91
+    <ol class="breadcrumb breadcrumb-custom-icon mb-0">
92
+      <li class="breadcrumb-item">
93
+        <a href="#">Contabilità</a>
94
+        <i class="breadcrumb-icon icon-base bx bx-chevron-right align-middle"></i>
95
+      </li>
96
+      <li class="breadcrumb-item">
97
+        <a href="{{ route('prima-nota.index') }}">Prima Nota</a>
98
+        <i class="breadcrumb-icon icon-base bx bx-chevron-right align-middle"></i>
99
+      </li>
100
+      <li class="breadcrumb-item active text-primary">#{{ $primanota->id }}</li>
101
+    </ol>
102
+  </nav>
103
+</div>
104
+@endsection
105
+
106
+@section('content')
107
+@include('_partials.status')
108
+
109
+<style>
110
+  .pn-show-hero {
111
+    border: 1px solid rgba(67, 89, 113, .12);
112
+    border-radius: 1rem;
113
+    overflow: hidden;
114
+    background: var(--bs-paper-bg, #fff);
115
+  }
116
+  .pn-show-hero__main {
117
+    display: flex;
118
+    flex-wrap: wrap;
119
+    align-items: center;
120
+    justify-content: space-between;
121
+    gap: 1.25rem;
122
+    padding: 1.35rem 1.5rem;
123
+  }
124
+  .pn-show-hero__amount {
125
+    font-size: clamp(1.75rem, 4vw, 2.35rem);
126
+    font-weight: 700;
127
+    line-height: 1.1;
128
+    letter-spacing: -0.02em;
129
+  }
130
+  .pn-show-hero__amount--entrata { color: var(--bs-success); }
131
+  .pn-show-hero__amount--uscita { color: var(--bs-danger); }
132
+  .pn-show-hero__amount--neutral { color: var(--bs-heading-color); }
133
+  .pn-show-hero__meta {
134
+    display: flex;
135
+    flex-wrap: wrap;
136
+    align-items: center;
137
+    gap: .5rem;
138
+  }
139
+  .pn-show-hero__causale {
140
+    font-size: 1.05rem;
141
+    font-weight: 600;
142
+    margin-bottom: .35rem;
143
+  }
144
+  .pn-show-hero__sub {
145
+    color: var(--bs-secondary-color);
146
+    font-size: .875rem;
147
+  }
148
+  .pn-show-section-title {
149
+    font-size: .8rem;
150
+    font-weight: 600;
151
+    text-transform: uppercase;
152
+    letter-spacing: .04em;
153
+    color: var(--bs-secondary-color);
154
+    margin-bottom: .75rem;
155
+  }
156
+  .pn-show-dl dt {
157
+    color: var(--bs-secondary-color);
158
+    font-weight: 500;
159
+    font-size: .8125rem;
160
+  }
161
+  .pn-show-dl dd {
162
+    font-size: .9375rem;
163
+    margin-bottom: .65rem;
164
+  }
165
+  .pn-show-link-card {
166
+    display: flex;
167
+    align-items: center;
168
+    gap: .85rem;
169
+    padding: .85rem 1rem;
170
+    border: 1px solid rgba(67, 89, 113, .12);
171
+    border-radius: .75rem;
172
+    background: var(--bs-paper-bg, #fff);
173
+    transition: border-color .15s ease, box-shadow .15s ease;
174
+  }
175
+  .pn-show-link-card:hover {
176
+    border-color: rgba(var(--bs-primary-rgb), .35);
177
+    box-shadow: 0 .125rem .5rem rgba(67, 89, 113, .08);
178
+  }
179
+  .pn-show-link-card--empty {
180
+    opacity: .72;
181
+    background: rgba(67, 89, 113, .03);
182
+  }
183
+  .pn-show-link-card__icon {
184
+    width: 2.5rem;
185
+    height: 2.5rem;
186
+    border-radius: .65rem;
187
+    display: flex;
188
+    align-items: center;
189
+    justify-content: center;
190
+    flex-shrink: 0;
191
+    background: rgba(var(--bs-primary-rgb), .1);
192
+    color: var(--bs-primary);
193
+    font-size: 1.25rem;
194
+  }
195
+  .pn-show-link-card--empty .pn-show-link-card__icon {
196
+    background: rgba(67, 89, 113, .08);
197
+    color: var(--bs-secondary-color);
198
+  }
199
+  .pn-show-link-card__body {
200
+    flex: 1;
201
+    min-width: 0;
202
+  }
203
+  .pn-show-link-card__label {
204
+    font-size: .75rem;
205
+    color: var(--bs-secondary-color);
206
+    margin-bottom: .1rem;
207
+  }
208
+  .pn-show-link-card__title {
209
+    font-weight: 600;
210
+    font-size: .9375rem;
211
+    line-height: 1.3;
212
+  }
213
+  .pn-show-link-card__subtitle {
214
+    font-size: .8125rem;
215
+    color: var(--bs-secondary-color);
216
+    margin-top: .15rem;
217
+  }
218
+  .pn-show-tech {
219
+    font-size: .8125rem;
220
+    color: var(--bs-secondary-color);
221
+  }
222
+  .pn-show-tech code {
223
+    font-size: .75rem;
224
+  }
225
+</style>
226
+
227
+<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
228
+  <a href="{{ route('prima-nota.index') }}" class="btn btn-label-secondary btn-sm">
229
+    <i class="bx bx-arrow-back me-1"></i> Torna alla prima nota
230
+  </a>
231
+  <span class="text-muted small">ID movimento <strong>#{{ $primanota->id }}</strong></span>
232
+</div>
233
+
234
+{{-- Hero importo + causale --}}
235
+<div class="pn-show-hero mb-4">
236
+  <div class="pn-show-hero__main">
237
+    <div class="flex-grow-1 min-w-0">
238
+      <div class="pn-show-hero__meta mb-2">
239
+        @if($isEntrata)
240
+          <span class="badge bg-label-success">Entrata</span>
241
+        @elseif($isUscita)
242
+          <span class="badge bg-label-danger">Uscita</span>
243
+        @else
244
+          <span class="badge bg-label-secondary">{{ $tipoLabel }}</span>
245
+        @endif
246
+
247
+        @if($primanota->categoria_contabile)
248
+          @include('_partials.badge', ['entity' => $primanota])
249
+        @endif
250
+
251
+        @if(filled($primanota->stato))
252
+          <span class="badge bg-label-primary">{{ $primanota->stato }}</span>
253
+        @endif
254
+      </div>
255
+
256
+      <div class="pn-show-hero__causale">
257
+        {{ $primanota->causale ?: 'Movimento senza causale' }}
258
+      </div>
259
+
260
+      @if(filled($primanota->riferimento))
261
+        <div class="pn-show-hero__sub">
262
+          <i class="bx bx-hash me-1"></i>Riferimento: {{ $primanota->riferimento }}
263
+        </div>
264
+      @endif
265
+
266
+      <div class="pn-show-hero__sub mt-1">
267
+        <i class="bx bx-time-five me-1"></i>
268
+        Registrato il {{ $primanota->created_at?->format('d/m/Y') }}
269
+        alle {{ $primanota->created_at?->format('H:i') }}
270
+        @if($primanota->attivita?->nome)
271
+          · {{ $primanota->attivita->nome }}
272
+        @endif
273
+      </div>
274
+    </div>
275
+
276
+    <div class="text-end">
277
+      <div class="pn-show-hero__amount {{ $isEntrata ? 'pn-show-hero__amount--entrata' : ($isUscita ? 'pn-show-hero__amount--uscita' : 'pn-show-hero__amount--neutral') }}">
278
+        @if($isUscita)−@elseif($isEntrata)+@endif€ {{ $importoFormatted }}
279
+      </div>
280
+      <div class="text-muted small mt-1">Importo movimento</div>
281
+    </div>
282
+  </div>
283
+</div>
284
+
285
+<div class="row g-4">
286
+  {{-- Informazioni --}}
287
+  <div class="col-12 col-lg-4">
288
+    <div class="card h-100">
289
+      <div class="card-body">
290
+        <div class="pn-show-section-title">Informazioni</div>
291
+        <dl class="row pn-show-dl mb-0">
292
+          <dt class="col-5">Attività</dt>
293
+          <dd class="col-7 fw-medium">{{ $primanota->attivita?->nome ?? '—' }}</dd>
294
+
295
+          <dt class="col-5">Tipo</dt>
296
+          <dd class="col-7">{{ $tipoLabel }}</dd>
297
+
298
+          <dt class="col-5">Categoria</dt>
299
+          <dd class="col-7">
300
+            @if($primanota->categoria_contabile)
301
+              {{ $primanota->categoria_contabile->nome }}
302
+            @else
303
+              —
304
+            @endif
305
+          </dd>
306
+
307
+          <dt class="col-5">Stato</dt>
308
+          <dd class="col-7">{{ $primanota->stato ?? '—' }}</dd>
309
+
310
+          <dt class="col-5">Registrato</dt>
311
+          <dd class="col-7">{{ $primanota->created_at?->format('d/m/Y H:i') ?? '—' }}</dd>
312
+
313
+          <dt class="col-5">Aggiornato</dt>
314
+          <dd class="col-7">{{ $primanota->updated_at?->format('d/m/Y H:i') ?? '—' }}</dd>
315
+        </dl>
316
+      </div>
317
+    </div>
318
+  </div>
319
+
320
+  {{-- Collegamenti --}}
321
+  <div class="col-12 col-lg-8">
322
+    <div class="card h-100">
323
+      <div class="card-body">
324
+        <div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
325
+          <div class="pn-show-section-title mb-0">Collegamenti</div>
326
+          <span class="badge bg-label-secondary">
327
+            {{ $collegamentiAttivi }} attivi su {{ count($collegamenti) }}
328
+          </span>
329
+        </div>
330
+
331
+        <div class="row g-3">
332
+          @foreach($collegamenti as $collegamento)
333
+            <div class="col-12 col-md-6">
334
+              @if($collegamento['url'])
335
+                <a href="{{ $collegamento['url'] }}" class="pn-show-link-card text-body d-block text-decoration-none">
336
+                  <div class="pn-show-link-card__icon">
337
+                    <i class="bx {{ $collegamento['icon'] }}"></i>
338
+                  </div>
339
+                  <div class="pn-show-link-card__body">
340
+                    <div class="pn-show-link-card__label">{{ $collegamento['label'] }}</div>
341
+                    <div class="pn-show-link-card__title">{{ $collegamento['title'] }}</div>
342
+                    @if(filled($collegamento['subtitle']))
343
+                      <div class="pn-show-link-card__subtitle">{{ $collegamento['subtitle'] }}</div>
344
+                    @endif
345
+                  </div>
346
+                  <i class="bx bx-chevron-right text-muted"></i>
347
+                </a>
348
+              @else
349
+                <div class="pn-show-link-card pn-show-link-card--empty">
350
+                  <div class="pn-show-link-card__icon">
351
+                    <i class="bx {{ $collegamento['icon'] }}"></i>
352
+                  </div>
353
+                  <div class="pn-show-link-card__body">
354
+                    <div class="pn-show-link-card__label">{{ $collegamento['label'] }}</div>
355
+                    <div class="pn-show-link-card__title text-muted">Non collegato</div>
356
+                  </div>
357
+                </div>
358
+              @endif
359
+            </div>
360
+          @endforeach
361
+        </div>
362
+      </div>
363
+    </div>
364
+  </div>
365
+</div>
366
+
367
+{{-- Dettagli tecnici (compatto) --}}
368
+<div class="card mt-4">
369
+  <div class="card-body py-3">
370
+    <div class="pn-show-section-title mb-2">Riferimenti di sistema</div>
371
+    <div class="pn-show-tech d-flex flex-wrap gap-3">
372
+      <span>attivita_id <code>{{ $primanota->attivita_id ?? '—' }}</code></span>
373
+      <span>categoria_contabile_id <code>{{ $primanota->categoria_contabile_id ?? '—' }}</code></span>
374
+      <span>tipo_movimento <code>{{ $primanota->tipo_movimento ?? '—' }}</code></span>
375
+      <span>pagamento_id <code>{{ $primanota->pagamento_id ?? '—' }}</code></span>
376
+      <span>ordine_id <code>{{ $primanota->ordine_id ?? '—' }}</code></span>
377
+      <span>prenotazione_id <code>{{ $primanota->prenotazione_id ?? '—' }}</code></span>
378
+      <span>evento_id <code>{{ $primanota->evento_id ?? '—' }}</code></span>
379
+      <span>fornitore_id <code>{{ $primanota->fornitore_id ?? '—' }}</code></span>
380
+    </div>
381
+  </div>
382
+</div>
383
+@endsection

Loading…
Cancel
Save