Browse Source

Tavolo, fix riepilogo cassa (coperti), calcolo coperti con impostazione su cucina, fix dashboard, fix AttivitaMiddleware

marcofalabretti 1 month ago
parent
commit
0c865244a9
29 changed files with 1128 additions and 502 deletions
  1. 7
    1
      .env
  2. 9
    6
      app/DataTables/CucinaDataTable.php
  3. 9
    6
      app/DataTables/CucinaDataTableEditor.php
  4. 119
    0
      app/DataTables/TavoloDataTable.php
  5. 98
    0
      app/DataTables/TavoloDataTableEditor.php
  6. 7
    5
      app/Http/Controllers/CarrelloController.php
  7. 4
    193
      app/Http/Controllers/HomePageController.php
  8. 53
    0
      app/Http/Controllers/TavoloController.php
  9. 0
    32
      app/Http/Controllers/VariatePiattoController.php
  10. 118
    14
      app/Http/Middleware/AttivitaMiddleware.php
  11. 1
    1
      app/Models/AbstractModels/AbstractCucina.php
  12. 67
    0
      app/Models/AbstractModels/AbstractTavolo.php
  13. 35
    0
      app/Models/Attivita.php
  14. 27
    1
      app/Models/Cucina.php
  15. 10
    0
      app/Models/Tavolo.php
  16. 94
    73
      app/Services/Carrello/CarrelloService.php
  17. 12
    7
      app/Services/Stampa/CreaComandaCucina.php
  18. 38
    0
      database/migrations/2026_06_23_225038_tavolo.php
  19. 15
    0
      deploy/supervisor/fest-horizon.conf.example
  20. 37
    8
      resources/menu/verticalMenu.json
  21. 1
    1
      resources/views/punto_operatore/index.blade.php
  22. 2
    2
      resources/views/punto_operatore/menu.blade.php
  23. 134
    73
      resources/views/punto_vendita/cassa/_partials/carrello/pagamento/checkout.blade.php
  24. 67
    65
      resources/views/punto_vendita/cassa/_partials/carrello/riepilogo.blade.php
  25. 7
    11
      resources/views/punto_vendita/cassa/_partials/scripts.blade.php
  26. 1
    2
      resources/views/punto_vendita/edit.blade.php
  27. 130
    0
      resources/views/tavolo/index.blade.php
  28. 22
    0
      resources/views/tavolo/menu.blade.php
  29. 4
    1
      routes/web.php

+ 7
- 1
.env View File

@@ -1,7 +1,10 @@
1 1
 APP_NAME="SegrestaFest"
2
-APP_ENV=local
2
+# APP_ENV=production
3
+# APP_DEBUG=false
3 4
 APP_KEY=base64:LpyZj196ZCEQkXhkaTSZ4Dy1uIjO/KhcXBJ4cGUrYg4=
4 5
 APP_DEBUG=true
6
+APP_ENV=local
7
+#APP_URL=https://app.segrestafest.it
5 8
 APP_URL=http://fest.test
6 9
 #APP_URL=http://localhost:8000
7 10
 APP_LOCALE=it
@@ -28,8 +31,11 @@ ADMIN_PASSWORD=fest2026
28 31
 BROADCAST_DRIVER=log
29 32
 CACHE_DRIVER=redis
30 33
 QUEUE_CONNECTION=redis
34
+
31 35
 SESSION_DRIVER=file
32 36
 SESSION_LIFETIME=120
37
+SESSION_SECURE=false
38
+SESSION_SECURE_COOKIE=false
33 39
 
34 40
 MEMCACHED_HOST=127.0.0.1
35 41
 

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

@@ -118,13 +118,14 @@ class CucinaDataTable extends DataTable
118 118
                         ->ajax(route('cucina.store'))
119 119
                         ->fields([
120 120
                           Fields\Hidden::make('attivita_id')->label('Attività')->default($this->attivita_id),
121
-                            Fields\Text::make('colore')->label('Colore')->set('attr', ['type' => 'color']),
122
-                            Fields\Text::make('nome')->label('Nome'),
123
-                            Fields\Text::make('descrizione')->label('Descrizione'),
124
-                            Fields\Image::make('immagine')->label('Immagine'),
125
-                            Fields\Boolean::make('is_attiva')->label('Attiva')->default(true),  
121
+                          Fields\Boolean::make('is_attiva')->label('Attiva')->default(true),  
122
+                          Fields\Text::make('nome')->label('Nome'),
123
+                          Fields\Text::make('descrizione')->label('Descrizione'),
124
+                          Fields\Select2::make('stampante_id')->label('Stampante')->options(Dispositivo::where('tipo', Dispositivo::STAMPANTE)->pluck('id', 'nome')),
125
+                          Fields\Text::make('colore')->label('Colore')->set('attr', ['type' => 'color']),
126
+                          Fields\Boolean::make('coperto_come_piatto')->label('Ogni piatto ordinato corrisponde ad un coperto'),  
127
+                            // Fields\Image::make('immagine')->label('Immagine'),
126 128
                             // Fields\Select2::make('cucina_id')->label('Cucina')->options(Cucina::where('is_attiva', true)->pluck('id', 'nome')),
127
-                            Fields\Select2::make('stampante_id')->label('Stampante')->options(Dispositivo::where('tipo', Dispositivo::STAMPANTE)->pluck('id', 'nome')),
128 129
                         ])
129 130
                     )
130 131
                     ->buttons($buttons)
@@ -151,6 +152,7 @@ class CucinaDataTable extends DataTable
151 152
         Column::make('is_attiva_display')->title('')->addClass('text-center')->responsivePriority(1),
152 153
         Column::make('badge_colore')->title('Cucina')->addClass('text-left')->responsivePriority(1),
153 154
         Column::computed('piatti_count')->title('Piatti')->addClass('text-center'),
155
+        Column::make('coperto_come_piatto')->visible(false),
154 156
         Column::computed('action')
155 157
         ->title('')
156 158
         ->exportable(false)
@@ -171,6 +173,7 @@ class CucinaDataTable extends DataTable
171 173
             Column::make('attivita_id')->data('attivita_id_display')->title('Attività')->addClass('text-center')->visible(false),
172 174
             Column::make('stampante_id')->data('stampante_display')->title('Stampante')->addClass('text-center'),
173 175
             // Column::make('cucina_id')->data('cucina_id_display')->title('Cucina')->addClass('text-center'),
176
+            Column::make('coperto_come_piatto')->visible(false),
174 177
             Column::make('action')
175 178
             ->title('')
176 179
             ->exportable(false)

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

@@ -22,6 +22,7 @@ class CucinaDataTableEditor extends DataTablesEditor
22 22
     'descrizione.required' => 'La descrizione è richiesta',
23 23
     'immagine.required' => 'L\'immagine è richiesta',
24 24
     'is_attiva.required' => 'Indicare una delle due opzioni',
25
+    'coperto_come_piatto.boolean' => 'Indicare una delle due opzioni',
25 26
   ];
26 27
 
27 28
   /**
@@ -78,9 +79,11 @@ class CucinaDataTableEditor extends DataTablesEditor
78 79
   {
79 80
     // $model->roles()->sync([$data['ruolo_id']]);
80 81
     $data['is_attiva'] = isset($data['is_attiva']) ? true : false;
81
-    $data['info'] =  [
82
-      'colore' => $data['colore'],
83
-    ];
82
+    
83
+    $data['info']['coperto_come_piatto'] = filter_var($data['coperto_come_piatto'] ?? false, FILTER_VALIDATE_BOOLEAN);
84
+    $data['info']['colore'] = $data['colore'];
85
+
86
+    // $data['info']['coperto_come_piatto'] = isset($data['coperto_come_piatto']) ? true : false;
84 87
     return $data;
85 88
   }
86 89
 
@@ -89,9 +92,9 @@ class CucinaDataTableEditor extends DataTablesEditor
89 92
     // dd($data['ruolo']);
90 93
     // $model->roles()->sync([$data['ruolo_id']]);
91 94
     $data['is_attiva'] = isset($data['is_attiva']) ? true : false;
92
-    $data['info'] =  [
93
-      'colore' => $data['colore'],
94
-    ];
95
+    $data['info']['colore'] = $data['colore'];
96
+    $data['info']['coperto_come_piatto'] = filter_var($data['coperto_come_piatto'] ?? false, FILTER_VALIDATE_BOOLEAN);
97
+
95 98
     return $data;
96 99
   }
97 100
   public function messages(): array

+ 119
- 0
app/DataTables/TavoloDataTable.php View File

@@ -0,0 +1,119 @@
1
+<?php
2
+
3
+namespace App\DataTables;
4
+
5
+use App\Models\Tavolo;
6
+use Illuminate\Database\Eloquent\Builder as QueryBuilder;
7
+use Yajra\DataTables\EloquentDataTable;
8
+use Yajra\DataTables\Html\Builder as HtmlBuilder;
9
+use Yajra\DataTables\Html\Button;
10
+use Yajra\DataTables\Html\Column;
11
+use Yajra\DataTables\Html\Editor\Editor;
12
+use Yajra\DataTables\Html\Editor\Fields;
13
+use Yajra\DataTables\Services\DataTable;
14
+use Illuminate\Support\Facades\Auth;
15
+
16
+class TavoloDataTable extends DataTable
17
+{
18
+    public function __construct(){
19
+        $this->dataTableVariable = 'dataTable_tavolo';
20
+    }
21
+    /**
22
+     * Build the DataTable class.
23
+     *
24
+     * @param QueryBuilder<Tavolo> $query Results from query() method.
25
+     */
26
+    public function dataTable(QueryBuilder $query): EloquentDataTable
27
+    {
28
+        return (new EloquentDataTable($query))
29
+            ->addColumn('action', function($entity){
30
+                return view('tavolo.menu', ['entity' => $entity]);
31
+            })
32
+            ->addColumn('is_attivo_display', function($entity){
33
+                return view('_partials.available', ['available' => $entity->is_attivo]);
34
+            })
35
+            ->setRowId('id');
36
+    }
37
+
38
+    /**
39
+     * Get the query source of dataTable.
40
+     *
41
+     * @return QueryBuilder<Tavolo>
42
+     */
43
+    public function query(Tavolo $model): QueryBuilder
44
+    {
45
+        if($this->attivita_id){
46
+            return $model->newQuery()->where('attivita_id', $this->attivita_id);
47
+        }else{
48
+            return $model->newQuery()->whereRaw('1 = 0');
49
+        }
50
+    }
51
+
52
+    /**
53
+     * Optional method if you want to use the html builder.
54
+     */
55
+    public function html(): HtmlBuilder
56
+    {
57
+        $buttons = [];
58
+        if(Auth::user()->can('create-tavolo')){
59
+            array_push($buttons, Button::make('create')
60
+                ->editor('editor')
61
+                ->formTitle('Crea nuovo tavolo')
62
+                ->className('btn btn-sm btn-primary mb-4')
63
+                ->text('<i class="fas fa-plus"></i> Nuovo tavolo'));
64
+        }
65
+
66
+        return $this->builder()
67
+                    ->setTableId($this->dataTableVariable)
68
+                    ->columns($this->getColumns())
69
+                    ->minifiedAjax()
70
+                    ->language(asset('assets/Italian.json'))
71
+                    ->dom(count($buttons) == 0 ? 'rtip' : 'Bfrtip')
72
+                    ->buttons($buttons)
73
+                    ->orderBy(1)
74
+                    ->selectStyleSingle()
75
+                    ->responsive(true)
76
+                    ->editor(
77
+                        Editor::make()
78
+                            ->fields([
79
+                                Fields\Hidden::make('attivita_id')->label('Attività')->default($this->attivita_id),
80
+                                Fields\Text::make('label')->label('Label'),
81
+                                Fields\Text::make('posti')->label('Posti'),
82
+                                Fields\Text::make('ubicazione')->label('Ubicazione'),
83
+                                // Fields\Text::make('info')->label('Info'),
84
+                                Fields\Boolean::make('is_attivo')->label('Attivo')->default(true),
85
+                            ])
86
+                    )
87
+                    ->initComplete("function(settings, json){
88
+                        initComplete_tavolo();
89
+                    }");
90
+    }
91
+
92
+    /**
93
+     * Get the dataTable columns definition.
94
+     */
95
+    public function getColumns(): array
96
+    {
97
+        return [
98
+            Column::make('is_attivo_display')->title(''),
99
+            Column::make('label')->title('Nome')->responsivePriority(1),
100
+            Column::make('posti')->title('Posti')->responsivePriority(1),
101
+            Column::make('ubicazione')->title('Ubicazione')->responsivePriority(1),
102
+            Column::computed('action')
103
+                  ->title('')
104
+                  ->exportable(false)
105
+                  ->printable(false)
106
+                  ->width(60)
107
+                  ->addClass('text-center'),
108
+            Column::make('id')->visible(false),
109
+        ];
110
+    }
111
+
112
+    /**
113
+     * Get the filename for export.
114
+     */
115
+    protected function filename(): string
116
+    {
117
+        return 'Tavolo_' . date('YmdHis');
118
+    }
119
+}

+ 98
- 0
app/DataTables/TavoloDataTableEditor.php View File

@@ -0,0 +1,98 @@
1
+<?php
2
+
3
+namespace App\DataTables;
4
+
5
+use App\Models\Tavolo;
6
+use Illuminate\Database\Eloquent\Model;
7
+use Illuminate\Validation\Rule;
8
+use Yajra\DataTables\DataTablesEditor;
9
+use Illuminate\Http\Request;
10
+use Illuminate\Validation\ValidationException;
11
+use Storage;
12
+use Illuminate\Support\Str;
13
+
14
+class TavoloDataTableEditor extends DataTablesEditor
15
+{
16
+  protected $model = Tavolo::class;
17
+  // protected $uploadDir = '';
18
+  // protected $disk = 'modelliRicevute';
19
+
20
+  protected $messages = [
21
+    'label.required' => 'Il label è richiesto',
22
+    'posti.required' => 'Il numero di posti è richiesto',
23
+    'ubicazione.required' => 'L\'ubicazione è richiesta',
24
+    'is_attivo.required' => 'Indicare una delle due opzioni',
25
+  ];
26
+
27
+  /**
28
+  * Get create action validation rules.
29
+  *
30
+  * @return array
31
+  */
32
+  public function createRules(): array
33
+  {
34
+    return [
35
+      'label'  => 'required',
36
+      'attivita_id' => 'required|exists:attivita,id',
37
+      'posti' => 'nullable|numeric|min:0',
38
+      'ubicazione' => 'nullable',
39
+      'is_attivo' => 'boolean',
40
+    ];
41
+  }
42
+
43
+  public function createMessages(): array{
44
+    return $this->messages;
45
+  }
46
+
47
+  /**
48
+  * Get edit action validation rules.
49
+  *
50
+  * @param Model $model
51
+  * @return array
52
+  */
53
+  public function editRules(Model $model): array
54
+  {
55
+    return [
56
+      'label'  => 'required',
57
+      'attivita_id' => 'required|exists:attivita,id',
58
+      'posti' => 'nullable|numeric|min:0',
59
+      'ubicazione' => 'nullable',
60
+      'is_attivo' => 'boolean',
61
+    ];
62
+  }
63
+
64
+  public function editMessages(): array{
65
+    return $this->messages;
66
+  }
67
+
68
+  /**
69
+  * Get remove action validation rules.
70
+  *
71
+  * @param Model $model
72
+  * @return array
73
+  */
74
+  public function removeRules(Model $model): array
75
+  {
76
+    return [];
77
+  }
78
+
79
+  public function creating(Model $model, array $data): array
80
+  {
81
+    // $model->roles()->sync([$data['ruolo_id']]);
82
+    $data['is_attivo'] = isset($data['is_attivo']) ? true : false;
83
+    return $data;
84
+  }
85
+
86
+  public function updating(Model $model, array $data): array
87
+  {
88
+    // dd($data['ruolo']);
89
+    // $model->roles()->sync([$data['ruolo_id']]);
90
+    $data['is_attivo'] = isset($data['is_attivo']) ? true : false;
91
+    return $data;
92
+  }
93
+  public function messages(): array
94
+  {
95
+    return $this->messages;
96
+  }
97
+
98
+}

+ 7
- 5
app/Http/Controllers/CarrelloController.php View File

@@ -50,6 +50,7 @@ class CarrelloController extends Controller
50 50
             'piatto_id' => 'required|integer|exists:piatto,id',
51 51
             'dispositivo_id' => 'required|integer|exists:dispositivo,id',
52 52
             'attivita_id' => 'nullable|integer|exists:attivita,id',
53
+            'riga_ordine_id' => 'nullable|integer|exists:riga_ordine,id',
53 54
         ]);
54 55
        
55 56
        $result = $this->carrelloService->aumentaQuantita($request->dispositivo_id, $request->attivita_id, $request->ordine_id, $request->piatto_id, $request->riga_ordine_id);
@@ -223,7 +224,7 @@ class CarrelloController extends Controller
223 224
         ]);
224 225
         $result = $this->carrelloService->eliminaVariante($request->riga_ordine_id, $request->variante_id);
225 226
         return response()->json($result);
226
-    }
227
+    }   
227 228
 
228 229
     public function azzera(Request $request){
229 230
         $request->validate([
@@ -311,10 +312,11 @@ class CarrelloController extends Controller
311 312
                 'data' => null,
312 313
             ]);
313 314
         }
314
-        $ordine->info = [
315
-            'cliente' => $request->cliente,
316
-            'tavolo' => $request->tavolo,
317
-        ];
315
+        $info = is_array($ordine->info) ? $ordine->info : [];
316
+        $info['cliente'] = $request->cliente;
317
+        $info['tavolo'] = $request->tavolo;
318
+        $info['coperti'] = $request->filled('coperti') ? (int) $request->coperti : null;
319
+        $ordine->info = $info;
318 320
         $ordine->save();
319 321
         return response()->json([
320 322
             'success' => true,  

+ 4
- 193
app/Http/Controllers/HomePageController.php View File

@@ -50,6 +50,7 @@ class HomePageController extends Controller
50 50
     $endYear = Carbon::create($year, 12, 31)->endOfDay();
51 51
 
52 52
     $base = Ordine::query()
53
+    ->where('attivita_id', session()->get('attivita_attuale'))
53 54
       ->whereNotNull('prezzo')
54 55
       ->where('prezzo', '>', 0)
55 56
       ->whereNotIn('stato', [
@@ -138,10 +139,11 @@ class HomePageController extends Controller
138 139
 
139 140
   private function getStatOrdini(): array
140 141
   {
141
-    $total = Ordine::query()->count();
142
-    $totaleIncasso = (float) Ordine::query()->whereNotNull('prezzo')->sum('prezzo');
142
+    $total = Ordine::query()->where('attivita_id', session()->get('attivita_attuale'))->count();
143
+    $totaleIncasso = (float) Ordine::query()->where('attivita_id', session()->get('attivita_attuale'))->whereNotNull('prezzo')->sum('prezzo');
143 144
 
144 145
     $byStato = Ordine::query()
146
+    ->where('attivita_id', session()->get('attivita_attuale'))
145 147
       ->select('stato', DB::raw('COUNT(*) as totale'))
146 148
       ->groupBy('stato')
147 149
       ->orderByDesc('totale')
@@ -201,195 +203,4 @@ class HomePageController extends Controller
201 203
 
202 204
   }
203 205
 
204
-  private function getDashboardData()
205
-  {
206
-    // KPI - Statistiche generali
207
-    $totaleSegnalazioni = Segnalazione::count();
208
-    $segnalazioniAperte = Segnalazione::where('stato', Config::getStatiSegnalazione('aperte'))->count();
209
-    $segnalazioniInLavorazione = Segnalazione::whereIn('stato', Config::getStatiSegnalazione('in_lavorazione'))->count();
210
-    $segnalazioniChiuse = Segnalazione::whereIn('stato', Config::getStatiSegnalazione('chiuse'))->count();
211
-
212
-    // Statistiche mese corrente
213
-    $meseCorrente = Carbon::now()->startOfMonth();
214
-    $fineMeseCorrente = Carbon::now()->endOfMonth();
215
-    $mesePrecedente = Carbon::now()->subMonth()->startOfMonth();
216
-    $fineMesePrecedente = Carbon::now()->subMonth()->endOfMonth();
217
-
218
-    $costiMese = Annotazione::whereBetween('created_at', [$meseCorrente, $fineMeseCorrente])->sum('costo');
219
-
220
-    $costiMesePrecedente = Annotazione::whereBetween('created_at', [$mesePrecedente, $fineMesePrecedente])->sum('costo');
221
-
222
-    $variazioneCosti = $costiMesePrecedente > 0
223
-    ? round((($costiMese - $costiMesePrecedente) / $costiMesePrecedente) * 100, 2)
224
-    : ($costiMese > 0 ? 100 : 0);
225
-
226
-    $tempiMese = Annotazione::whereBetween('tempo_data', [$meseCorrente, $fineMeseCorrente])->sum('tempo_minuti');
227
-    $oreTotaliMese = Config::formatTempo($tempiMese);
228
-
229
-    // Tempo medio di risoluzione (in giorni)
230
-    $segnalazioniChiuseConTempo = Segnalazione::whereIn('stato', Config::getStatiSegnalazione('chiuse'))
231
-    ->whereNotNull('updated_at')
232
-    ->whereNotNull('created_at')
233
-    ->get();
234
-
235
-    $tempoMedioRisoluzione = 0;
236
-    if ($segnalazioniChiuseConTempo->count() > 0) {
237
-      $giorniTotali = $segnalazioniChiuseConTempo->sum(function($segnalazione) {
238
-        return $segnalazione->created_at->diffInDays($segnalazione->updated_at);
239
-      });
240
-      $tempoMedioRisoluzione = round($giorniTotali / $segnalazioniChiuseConTempo->count(), 1);
241
-    }
242
-
243
-
244
-    $segnalazioniUrgentiAperte = Segnalazione::whereNotIn('stato', Config::getStatiSegnalazione('chiuse'))
245
-    ->whereIn('priorita', ['urgente', 'emergenza_immediata'])
246
-    ->count();
247
-
248
-    // Segnalazioni senza assegnazione
249
-    $segnalazioniSenzaAssegnazione = Segnalazione::whereNotIn('stato', Config::getStatiSegnalazione('chiuse'))->whereNull('user_id')
250
-    ->count();
251
-
252
-    // Segnalazioni aperte da più di 7 giorni
253
-    $dataLimite = Carbon::now()->subDays(7);
254
-    $segnalazioniAperteVecchie = Segnalazione::whereNotIn('stato', Config::getStatiSegnalazione('chiuse'))
255
-    ->where('created_at', '<', $dataLimite)
256
-    ->count();
257
-
258
-    // Grafico segnalazioni nel tempo (ultimi 30 giorni)
259
-    $giorni = 30;
260
-    $dataInizio = Carbon::now()->subDays($giorni);
261
-    $segnalazioniTempo = Segnalazione::where('created_at', '>=', $dataInizio)
262
-    ->selectRaw('DATE(created_at) as data, COUNT(*) as totale')
263
-    ->groupBy('data')
264
-    ->orderBy('data')
265
-    ->get();
266
-
267
-    $labelsTempo = [];
268
-    $datiTempo = [];
269
-    $dataCorrente = $dataInizio->copy();
270
-    while ($dataCorrente <= Carbon::now()) {
271
-      $dataStr = $dataCorrente->format('Y-m-d');
272
-      $labelsTempo[] = $dataCorrente->format('d/m');
273
-      $datiTempo[] = $segnalazioniTempo->firstWhere('data', $dataStr)->totale ?? 0;
274
-      $dataCorrente->addDay();
275
-    }
276
-
277
-    // Distribuzione per stato
278
-    $distribuzioneStato = Segnalazione::select('stato', DB::raw('COUNT(*) as totale'))
279
-    ->groupBy('stato')
280
-    ->get();
281
-
282
-    $labelsStato = [];
283
-    $datiStato = [];
284
-    foreach ($distribuzioneStato as $item) {
285
-      $config = ConfiguraSegnalazione::where('gruppo', 'stato')
286
-      ->where('value', $item->stato)
287
-      ->first();
288
-      $labelsStato[] = $config ? $config->label : $item->stato;
289
-      $datiStato[] = $item->totale;
290
-    }
291
-
292
-    // Distribuzione per priorità
293
-    $distribuzionePriorita = Segnalazione::select('priorita', DB::raw('COUNT(*) as totale'))
294
-    ->whereNotNull('priorita')
295
-    ->groupBy('priorita')
296
-    ->get();
297
-
298
-    $labelsPriorita = [];
299
-    $datiPriorita = [];
300
-    foreach(ConfiguraSegnalazione::where('gruppo', 'Priorità')->orderBy('order', 'asc')->get() as $config) {
301
-      $labelsPriorita[] = $config->label;
302
-      $datiPriorita[] = Segnalazione::where('priorita', $config->value)->count();
303
-    }
304
-
305
-    // Distribuzione per tipo
306
-    $distribuzioneTipo = Segnalazione::select('tipo', DB::raw('COUNT(*) as totale'))
307
-    ->whereNotNull('tipo')
308
-    ->groupBy('tipo')
309
-    ->get();
310
-
311
-    $labelsTipo = [];
312
-    $datiTipo = [];
313
-    foreach ($distribuzioneTipo as $item) {
314
-      $config = ConfiguraSegnalazione::where('gruppo', 'Tipo')
315
-      ->where('value', $item->tipo)
316
-      ->first();
317
-      $labelsTipo[] = $config ? $config->label : $item->tipo;
318
-      $datiTipo[] = $item->totale;
319
-    }
320
-
321
-    // Distribuzione per reparto
322
-    $labelsReparto = [];
323
-    $datiReparto = [];
324
-
325
-    foreach(Reparto::where('is_attivo', true)->orderBy('label', 'asc')->get() as $reparto) {
326
-      $labelsReparto[] = $reparto->label;
327
-      $datiReparto[] = Segnalazione::where('reparto_id', $reparto->id)->count();
328
-    }
329
-
330
-    // Assicurati che tutti gli array non siano vuoti per evitare errori nei grafici
331
-    if (empty($labelsTempo)) {
332
-      $labelsTempo = ['Nessun dato'];
333
-      $datiTempo = [0];
334
-    }
335
-    if (empty($labelsStato)) {
336
-      $labelsStato = ['Nessun dato'];
337
-      $datiStato = [0];
338
-    }
339
-    if (empty($labelsPriorita)) {
340
-      $labelsPriorita = ['Nessun dato'];
341
-      $datiPriorita = [0];
342
-    }
343
-    if (empty($labelsReparto)) {
344
-      $labelsReparto = ['Nessun dato'];
345
-      $datiReparto = [0];
346
-    }
347
-    if (empty($labelsManutentori)) {
348
-      $labelsManutentori = ['Nessun dato'];
349
-      $datiManutentori = [0];
350
-    }
351
-    if (empty($labelsCostiReparto)) {
352
-      $labelsCostiReparto = ['Nessun dato'];
353
-      $datiCostiReparto = [0];
354
-    }
355
-    if (empty($labelsTipo)) {
356
-      $labelsTipo = ['Nessun dato'];
357
-      $datiTipo = [0];
358
-    }
359
-    if (empty($labelsCostiTipo)) {
360
-      $labelsCostiTipo = ['Nessun dato'];
361
-      $datiCostiTipo = [0];
362
-    }
363
-
364
-    return [
365
-      'totaleSegnalazioni' => $totaleSegnalazioni,
366
-      'segnalazioniAperte' => $segnalazioniAperte,
367
-      'segnalazioniInLavorazione' => $segnalazioniInLavorazione,
368
-      'segnalazioniChiuse' => $segnalazioniChiuse,
369
-      'costiMese' => $costiMese,
370
-      'costiMesePrecedente' => $costiMesePrecedente,
371
-      'variazioneCosti' => $variazioneCosti,
372
-      'oreTotaliMese' => $oreTotaliMese,
373
-      'tempoMedioRisoluzione' => $tempoMedioRisoluzione,
374
-      'segnalazioniUrgentiAperte' => $segnalazioniUrgentiAperte,
375
-      'segnalazioniSenzaAssegnazione' => $segnalazioniSenzaAssegnazione,
376
-      'segnalazioniAperteVecchie' => $segnalazioniAperteVecchie,
377
-      'labelsTempo' => $labelsTempo,
378
-      'datiTempo' => $datiTempo,
379
-      'labelsStato' => $labelsStato,
380
-      'datiStato' => $datiStato,
381
-      'labelsPriorita' => $labelsPriorita,
382
-      'datiPriorita' => $datiPriorita,
383
-      'labelsReparto' => $labelsReparto,
384
-      'datiReparto' => $datiReparto,
385
-      'labelsManutentori' => $labelsManutentori,
386
-      'datiManutentori' => $datiManutentori,
387
-      'labelsCostiReparto' => $labelsCostiReparto,
388
-      'datiCostiReparto' => $datiCostiReparto,
389
-      'labelsTipo' => $labelsTipo,
390
-      'datiTipo' => $datiTipo,
391
-      'labelsCostiTipo' => $labelsCostiTipo,
392
-      'datiCostiTipo' => $datiCostiTipo,
393
-    ];
394
-  }
395 206
 }

+ 53
- 0
app/Http/Controllers/TavoloController.php View File

@@ -0,0 +1,53 @@
1
+<?php
2
+
3
+namespace App\Http\Controllers;
4
+
5
+use App\Models\Tavolo;
6
+use App\DataTables\TavoloDataTable;
7
+use App\DataTables\TavoloDataTableEditor;
8
+use Illuminate\Http\Request;
9
+use Illuminate\Support\Facades\Auth;
10
+
11
+class TavoloController extends Controller
12
+{
13
+    public static $permission_group = "Tavolo";
14
+    public static $permissions = [
15
+        'view-tavolo' => 'Vedi',
16
+        'create-tavolo' => 'Crea',
17
+        'edit-tavolo' => 'Modifica',
18
+        'delete-tavolo' => 'Elimina',
19
+    ];
20
+    
21
+    public static function middleware(): array
22
+    {
23
+        return [
24
+            new Middleware('permission:view-tavolo', only: ['index']),
25
+            new Middleware('permission:create-tavolo|edit-tavolo|delete-tavolo', only: ['store', 'update', 'destroy']),
26
+        ];
27
+    }
28
+
29
+    public function index(TavoloDataTable $dataTable)
30
+    {
31
+        $dataTable->attivita_id = session()->get('attivita_attuale');
32
+        return $dataTable->render('tavolo.index');
33
+    }
34
+    public function store(TavoloDataTableEditor $editor)
35
+    {
36
+        $request = request();
37
+        $input = $request->all();
38
+        if($request->has('action')){
39
+            switch($input['action']){
40
+                case 'create':
41
+                    if(!Auth::user()->can('create-tavolo')) return;
42
+                    break;
43
+                case 'edit':
44
+                    if(!Auth::user()->can('edit-tavolo')) return;
45
+                    break;
46
+                case 'delete':
47
+                    if(!Auth::user()->can('delete-tavolo')) return;
48
+                    break;
49
+            }
50
+        }
51
+        return $editor->process($request);
52
+    }
53
+}

+ 0
- 32
app/Http/Controllers/VariatePiattoController.php View File

@@ -1,32 +0,0 @@
1
-<?php
2
-
3
-namespace App\Http\Controllers;
4
-
5
-use Illuminate\Http\Request;
6
-use App\Models\VariantePiatto;
7
-
8
-class VariantePiattoController extends Controller
9
-{
10
-    public static $permission_group = "Varianti Piatto";
11
-    public static $permissions = [
12
-        'view-variante-piatto' => 'Vedi',
13
-        'create-variante-piatto' => 'Crea',
14
-        'edit-variante-piatto' => 'Modifica',
15
-        'delete-variante-piatto' => 'Elimina',
16
-    ];
17
-
18
-    public static function middleware(): array
19
-    {
20
-        return [
21
-            new Middleware('permission:view-variante-piatto', only: ['index']),
22
-            new Middleware('permission:create-variante-piatto|edit-variante-piatto|delete-variante-piatto', only: ['store', 'update', 'destroy']),
23
-        ];
24
-    }
25
-
26
-    public function index(VariantePiattoDataTable $dataTable)
27
-    {
28
-        $dataTable->attivita_id = Session::get('attivita_attuale');
29
-        return $dataTable->render('variante_piatto.index');
30
-    }
31
-
32
-}

+ 118
- 14
app/Http/Middleware/AttivitaMiddleware.php View File

@@ -2,25 +2,129 @@
2 2
 
3 3
 namespace App\Http\Middleware;
4 4
 
5
+use App\Models\Attivita;
6
+use App\Models\Dispositivo;
5 7
 use Closure;
6 8
 use Illuminate\Http\Request;
9
+use Illuminate\Support\Facades\Auth;
7 10
 use Symfony\Component\HttpFoundation\Response;
8
-use App\Models\Attivita;
9 11
 
10 12
 class AttivitaMiddleware
11 13
 {
12
-  /**
13
-   * Handle an incoming request.
14
-   *
15
-   * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
16
-   */
17
-  public function handle(Request $request, Closure $next): Response
18
-  {
19
-    // Attività is selected and active
20
-
21
-    if (!session()->has('attivita_attuale') || session()->get('attivita_attuale') == null || session()->get('attivita_attuale') == 0 || !Attivita::find(session()->get('attivita_attuale'))->is_attiva) {
22
-      return redirect()->route('attivita.index')->with('danger', 'Seleziona un\'attività per continuare');
14
+    /**
15
+     * Handle an incoming request.
16
+     *
17
+     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
18
+     */
19
+    public function handle(Request $request, Closure $next): Response
20
+    {
21
+        $attivitaId = $this->resolveAttivitaId($request);
22
+        if (! $attivitaId) {
23
+            return $this->deny($request, 'Seleziona un\'attività per continuare');
24
+        }
25
+
26
+        $attivita = Attivita::find($attivitaId);
27
+        if (! $attivita) {
28
+            return $this->deny($request, 'Attività non trovata');
29
+        }
30
+
31
+        if (! $attivita->is_attiva) {
32
+            return $this->deny($request, 'L\'attività non è attiva');
33
+        }
34
+
35
+        $user = Auth::user();
36
+        if (! $user) {
37
+            return $this->deny($request, 'Utente non autenticato');
38
+        }
39
+
40
+        $isSuperadmin = method_exists($user, 'hasRole') && $user->hasRole('superadmin');
41
+        $isOwner = (int) $attivita->user_id === (int) $user->id;
42
+        $isCassaContext = $this->isCassaContextForAttivita((int) $attivita->id);
43
+
44
+        if (! $isSuperadmin && ! $isOwner && ! $isCassaContext) {
45
+            return $this->deny($request, 'Non autorizzato ad accedere a questa attività');
46
+        }
47
+
48
+        // Mantiene allineata la sessione usata da controller e DataTable.
49
+        if ((int) session('attivita_attuale') !== (int) $attivita->id) {
50
+            session(['attivita_attuale' => (int) $attivita->id]);
51
+        }
52
+
53
+        return $next($request);
54
+    }
55
+
56
+    private function resolveAttivitaId(Request $request): ?int
57
+    {
58
+        // 1) Se la route identifica un punto vendita, usa prima quello:
59
+        // evita mismatch con una "attivita_attuale" rimasta in sessione.
60
+        $puntoVenditaId = (int) $request->input('punto_vendita_id');
61
+        if ($puntoVenditaId > 0) {
62
+            $puntoVendita = Dispositivo::find($puntoVenditaId);
63
+            if ($puntoVendita && (int) $puntoVendita->attivita_id > 0) {
64
+                return (int) $puntoVendita->attivita_id;
65
+            }
66
+        }
67
+
68
+        $candidates = [
69
+            $request->input('attivita_id'),
70
+            session('attivita_attuale'),
71
+            session('attivita_id'),
72
+        ];
73
+
74
+        foreach ($candidates as $candidate) {
75
+            $value = (int) $candidate;
76
+            if ($value > 0) {
77
+                return $value;
78
+            }
79
+        }
80
+
81
+        $dispositivoId = (int) session('dispositivo_id');
82
+        if ($dispositivoId > 0) {
83
+            $dispositivo = Dispositivo::find($dispositivoId);
84
+            if ($dispositivo && (int) $dispositivo->attivita_id > 0) {
85
+                return (int) $dispositivo->attivita_id;
86
+            }
87
+        }
88
+
89
+        return null;
90
+    }
91
+
92
+    private function isCassaContextForAttivita(int $attivitaId): bool
93
+    {
94
+        $dispositivoId = (int) session('dispositivo_id');
95
+        if ($dispositivoId > 0) {
96
+            $dispositivo = Dispositivo::find($dispositivoId);
97
+            if ($dispositivo && (int) $dispositivo->attivita_id === $attivitaId) {
98
+                return true;
99
+            }
100
+        }
101
+
102
+        // Fallback: alcune chiamate (es. DataTable) passano solo punto_vendita_id.
103
+        $puntoVenditaId = (int) request()->input('punto_vendita_id');
104
+        if ($puntoVenditaId > 0) {
105
+            $puntoVendita = Dispositivo::find($puntoVenditaId);
106
+            if ($puntoVendita && (int) $puntoVendita->attivita_id === $attivitaId) {
107
+                return true;
108
+            }
109
+        }
110
+
111
+        return false;
112
+    }
113
+
114
+    private function deny(Request $request, string $message): Response
115
+    {
116
+        $isDataTableRequest = $request->has('draw');
117
+        $isAjaxLike = $request->expectsJson() || $request->wantsJson() || $request->ajax() || $isDataTableRequest;
118
+
119
+        if ($isAjaxLike) {
120
+            return response()->json(['success' => false, 'message' => $message], 403);
121
+        }
122
+
123
+        // Evita redirect infinito: attivita.index è dentro lo stesso middleware.
124
+        if ($request->routeIs('attivita.index')) {
125
+            abort(403, $message);
126
+        }
127
+
128
+        return redirect()->route('attivita.index')->with('danger', $message);
23 129
     }
24
-    return $next($request);
25
-  }
26 130
 }

+ 1
- 1
app/Models/AbstractModels/AbstractCucina.php View File

@@ -27,7 +27,7 @@ abstract class AbstractCucina extends \Illuminate\Foundation\Auth\User
27 27
      * 
28 28
      * @var array
29 29
      */
30
-    // protected $attributes = ['is_gruppo' => 0];
30
+    protected $attributes = ['info' => '{"coperto_come_piatto": false , "colore": "#000000"}'];
31 31
     
32 32
     /**  
33 33
      * The attributes that should be cast to native types.

+ 67
- 0
app/Models/AbstractModels/AbstractTavolo.php View File

@@ -0,0 +1,67 @@
1
+<?php
2
+/**
3
+ * Model object generated by: Skipper (http://www.skipper18.com)
4
+ * Do not modify this file manually.
5
+ */
6
+
7
+namespace App\Models\AbstractModels;
8
+
9
+abstract class AbstractTavolo extends \Illuminate\Foundation\Auth\User
10
+{
11
+    /**  
12
+     * The table associated with the model.
13
+     * 
14
+     * @var string
15
+     */
16
+    protected $table = 'tavolo';
17
+    
18
+    /**  
19
+     * Primary key type.
20
+     * 
21
+     * @var string
22
+     */
23
+    protected $keyType = 'bigInteger';
24
+    
25
+    /**  
26
+     * The model's default values for attributes.
27
+     * 
28
+     * @var array
29
+     */
30
+    // protected $attributes = ['is_gruppo' => 0];
31
+    
32
+    /**  
33
+     * The attributes that should be cast to native types.
34
+     * 
35
+     * @var array
36
+     */
37
+    protected $casts = [
38
+        'id' => 'integer',
39
+        'label' => 'string',
40
+        'posti' => 'integer',
41
+        'ubicazione' => 'string',
42
+        'coperti' => 'integer',
43
+        'info' => 'json',
44
+        'is_attivo' => 'boolean',
45
+        'attivita_id' => 'integer',
46
+        'created_at' => 'datetime',
47
+        'updated_at' => 'datetime'
48
+    ];
49
+    
50
+    /**  
51
+     * The attributes that are mass assignable.
52
+     * 
53
+     * @var array
54
+     */
55
+    protected $fillable = [
56
+        'id',
57
+        'label',
58
+        'posti',
59
+        'ubicazione',
60
+        'coperti',
61
+        'info',
62
+        'is_attivo',
63
+        'attivita_id',
64
+        'created_at', 
65
+        'updated_at'
66
+    ];
67
+}

+ 35
- 0
app/Models/Attivita.php View File

@@ -3,6 +3,7 @@
3 3
 namespace App\Models;
4 4
 
5 5
 use Illuminate\Database\Eloquent\Model;
6
+use Illuminate\Support\Facades\Auth;
6 7
 
7 8
 class Attivita extends \App\Models\AbstractModels\AbstractAttivita
8 9
 {
@@ -42,4 +43,38 @@ class Attivita extends \App\Models\AbstractModels\AbstractAttivita
42 43
 
43 44
         return mb_strtoupper(mb_substr($parts[0], 0, 1) . mb_substr($parts[1], 0, 1));
44 45
     }
46
+
47
+    public static function E_Mia($attivita_id)
48
+    {
49
+        return self::where('id', $attivita_id)->where('user_id', Auth::user()->id)->exists();
50
+    }
51
+
52
+    public static function userCanAccess(?User $user, Attivita $attivita, ?Request $request = null): bool
53
+        {
54
+            if (!$user) return false;
55
+
56
+            // Superadmin: accesso globale 
57
+            if ($user->hasRole('superadmin')) {
58
+                return true;
59
+            }
60
+
61
+            // Proprietario
62
+            if ((int) $attivita->user_id === (int) $user->id) {
63
+                return true;
64
+            }
65
+
66
+            // Cassa: dispositivo legato a questa attività (binding ok)
67
+            $dispositivoId = session('dispositivo_id');
68
+            if ($dispositivoId) {
69
+                $pv = Dispositivo::find($dispositivoId);
70
+                if ($pv && (int) $pv->attivita_id === (int) $attivita->id) {
71
+                    return true; // oppure: + permesso view-punto_vendita
72
+                }
73
+            }
74
+
75
+            // Futuro: staff dell'attività, permessi Spatie, ecc.
76
+            // return $user->can('view-attivita') && ...
77
+
78
+            return false;
79
+        }
45 80
 }

+ 27
- 1
app/Models/Cucina.php View File

@@ -6,5 +6,31 @@ use Illuminate\Database\Eloquent\Model;
6 6
 
7 7
 class Cucina extends \App\Models\AbstractModels\AbstractCucina
8 8
 {
9
-    
9
+    protected $appends = ['coperto_come_piatto', 'colore'];
10
+
11
+    public function getCopertoComePiattoAttribute()
12
+    {
13
+        return (bool)($this->info['coperto_come_piatto'] ?? false);
14
+    }
15
+
16
+    public function setCopertoComePiattoAttribute($value)
17
+    {
18
+    $info = is_array($this->info) ? $this->info : [];
19
+    $info['coperto_come_piatto'] = filter_var($value, FILTER_VALIDATE_BOOLEAN);
20
+    $this->attributes['info'] = json_encode($info);
21
+    }
22
+
23
+    public function getColoreAttribute()
24
+    {
25
+        return $this->info['colore'] ?? '#000000';
26
+    }
27
+
28
+
29
+    public function setColoreAttribute($value)
30
+    {
31
+    $info = is_array($this->info) ? $this->info : [];
32
+    $info['colore'] = $value;
33
+    $this->attributes['info'] = json_encode($info);
34
+    }
35
+
10 36
 }

+ 10
- 0
app/Models/Tavolo.php View File

@@ -0,0 +1,10 @@
1
+<?php
2
+
3
+namespace App\Models;
4
+
5
+use Illuminate\Database\Eloquent\Model;
6
+
7
+class Tavolo extends \App\Models\AbstractModels\AbstractTavolo
8
+{
9
+    //
10
+}

+ 94
- 73
app/Services/Carrello/CarrelloService.php View File

@@ -22,7 +22,7 @@ class CarrelloService
22 22
         return $ordine;
23 23
     }
24 24
 
25
-    public function aumentaQuantita(int $dispositivo_id ,?int $attivita_id, ?int $ordine_id, int $piatto_id, int $riga_ordine_id = null): array
25
+    public function aumentaQuantita(int $dispositivo_id ,?int $attivita_id, ?int $ordine_id, int $piatto_id, ?int $riga_ordine_id = null): array
26 26
     {
27 27
     // Tolleranza su ordine_id stale: se non e` valido, usa/crea il carrello corrente per dispositivo+attivita.
28 28
         $carrelloBaseQuery = Ordine::query()
@@ -55,28 +55,37 @@ class CarrelloService
55 55
         if($riga_ordine_id){
56 56
             $riga = RigaOrdine::findOrFail($riga_ordine_id);
57 57
         }else{
58
-            $riga = RigaOrdine::where('ordine_id', $carrello->id)->where('piatto_id', $piatto_id)->first();
58
+            $riga = RigaOrdine::where('ordine_id', $carrello->id)->where('piatto_id', $piatto_id)->whereDoesntHave('has_variante')->oldest('id')->first();
59 59
         }
60
+        // else{
61
+        //     $riga = RigaOrdine::where('ordine_id', $carrello->id)->where('piatto_id', $piatto_id)->first();
62
+        // }
63
+
64
+        // $piatto = Piatto::findOrFail($piatto_id);
60 65
 
61
-        $piatto = Piatto::findOrFail($piatto_id);
66
+        
62 67
         if($riga){
63
-            $riga->quantita += 1;
64
-            $parziale = $piatto->prezzo*$riga->quantita;
65
-            //Se ha delle varianti a pagamento, aggiungo il prezzo delle varianti * quantita
66
-            if($riga->has_variante->isNotEmpty()){
67
-                foreach($riga->has_variante as $variante){
68
-                    $parziale += $variante->variante_piatto->prezzo * $riga->quantita;
69
-                }
68
+            $piatto = $riga->piatto;
69
+            if($piatto == null){
70
+                return [
71
+                    'success' => false,
72
+                    'message' => 'Piatto non trovato.',
73
+                    'data' => null,
74
+                ];
70 75
             }
71
-            $riga->prezzo = $parziale ;//$piatto->prezzo*$riga->quantita;
76
+
77
+            $riga->quantita += 1;
78
+            $riga->prezzo = $this->calcolaPrezzoRiga($riga);
72 79
             $riga->save();
73 80
         }else{
74 81
             $riga = RigaOrdine::create([
75 82
                 'ordine_id' => $carrello->id,
76 83
                 'piatto_id' => $piatto_id,
77 84
                 'quantita' => 1,
78
-                'prezzo' => $piatto->prezzo,
85
+                // 'prezzo' => $piatto->prezzo,
79 86
             ]);
87
+            $riga->prezzo = $this->calcolaPrezzoRiga($riga);
88
+            $riga->save();
80 89
         }
81 90
         // dd($riga);
82 91
         return [
@@ -91,19 +100,19 @@ class CarrelloService
91 100
         // $riga = RigaOrdine::where('ordine_id', $carrello->id)->where('piatto_id', $piatto_id)->first();
92 101
         $riga = RigaOrdine::findOrFail($riga_ordine_id);
93 102
         if($riga && $riga->id == $riga_ordine_id){
94
-            $piatto = Piatto::findOrFail($piatto_id);
103
+            // $piatto = Piatto::findOrFail($piatto_id);
104
+            $piatto = $riga->piatto;
95 105
             $riga->quantita -= 1;
96
-            // dd($riga->quantita);
97 106
 
98
-            $parziale = $piatto->prezzo*$riga->quantita;
99
-            // dd($parziale);
100
-            //Se ha delle varianti a pagamento, aggiungo il prezzo delle varianti * quantita
101
-            if($riga->has_variante->isNotEmpty()){
102
-                foreach($riga->has_variante as $variante){
103
-                    $parziale += $variante->variante_piatto->prezzo * $riga->quantita;
104
-                }
105
-            }
106
-            $riga->prezzo = $parziale ;//$piatto->prezzo*$riga->quantita;
107
+            if($riga->quantita == 0){
108
+                $riga->delete();
109
+                return [
110
+                    'ordine' => $carrello,
111
+                    'riga' => null,
112
+                ];
113
+            };
114
+
115
+            $riga->prezzo = $this->calcolaPrezzoRiga($riga);
107 116
             $riga->save();   
108 117
         }
109 118
         return [
@@ -170,7 +179,7 @@ class CarrelloService
170 179
         return $riga;
171 180
     }
172 181
 
173
-    public function aggiungiVarianteSingoloPiatto(int $piattoId, int $rigaOrdineId, int $varianteId = null, string $nota = null): array
182
+    public function aggiungiVarianteSingoloPiatto(int $piattoId, int $rigaOrdineId, int $varianteId, string $nota = null): array
174 183
     {
175 184
         $riga = RigaOrdine::findOrFail($rigaOrdineId);
176 185
         if($riga == null){
@@ -204,7 +213,7 @@ class CarrelloService
204 213
                 // 'nota' => $nota,
205 214
             ]);
206 215
 
207
-            $riga->prezzo = ($piatto->prezzo * $riga->quantita) + ($variante->prezzo * $riga->quantita);
216
+            $riga->prezzo = $this->calcolaPrezzoRiga($riga);
208 217
             $riga->save();
209 218
             return [
210 219
                 'success' => true,
@@ -228,9 +237,10 @@ class CarrelloService
228 237
         ]);
229 238
 
230 239
         $riga->quantita -= 1;
240
+        $riga->prezzo = $this->calcolaPrezzoRiga($riga);
231 241
         $riga->save();
232 242
 
233
-        $NuovaRigaPerVariante->prezzo = ($piatto->prezzo * $NuovaRigaPerVariante->quantita) + ($variante->prezzo * $NuovaRigaPerVariante->quantita);
243
+        $NuovaRigaPerVariante->prezzo = $this->calcolaPrezzoRiga($NuovaRigaPerVariante);
234 244
         $NuovaRigaPerVariante->save();
235 245
         // dd($NuovaRigaPerVariante ,$riga, $pivotVariante);
236 246
         return [
@@ -240,7 +250,64 @@ class CarrelloService
240 250
         ];
241 251
     }
242 252
 
243
-    // public function aggiungiVarianteTuttiPiatto(int $piattoId, int $rigaOrdineId, int $varianteId, string $nota): array
253
+  
254
+
255
+    public function eliminaVariante(int $rigaOrdineId, int $varianteId): array
256
+    {
257
+        $riga = RigaOrdine::findOrFail($rigaOrdineId);
258
+        if($riga == null){
259
+            return [
260
+                'success' => false,
261
+                'message' => 'Riga ordine non trovata.',
262
+                'data' => null,
263
+            ];
264
+        }
265
+        $variante = VariantePiatto::findOrFail($varianteId);
266
+        if($variante == null){
267
+            return [
268
+                'success' => false,
269
+                'message' => 'Variante non trovata.',
270
+                'data' => null,
271
+            ];
272
+        }
273
+        $pivotVariante = RigaOrdineHasVariante::where('riga_ordine_id', $riga->id)->where('variante_id', $varianteId)->first();
274
+        if($pivotVariante == null){
275
+            return [
276
+                'success' => false,
277
+                'message' => 'Variante non trovata.',
278
+                'data' => null,
279
+            ];
280
+        }
281
+        $pivotVariante->delete();
282
+        
283
+        $riga->prezzo = $this->calcolaPrezzoRiga($riga);
284
+        $riga->save();  
285
+            
286
+        return [
287
+            'success' => true,
288
+            'message' => 'Variante eliminata',
289
+            'data' => null,
290
+        ];
291
+    }
292
+
293
+    private function calcolaPrezzoRiga(RigaOrdine $riga): float
294
+    {
295
+        $parziale = $riga->piatto->prezzo*$riga->quantita;
296
+
297
+        if($riga->has_variante->isNotEmpty()){
298
+            foreach($riga->has_variante as $variante){
299
+                if($variante != null && 
300
+                $variante->variante_piatto != null && 
301
+                $variante->variante_piatto->prezzo != null && 
302
+                $variante->variante_piatto->prezzo != 0){
303
+                    $parziale += $variante->variante_piatto->prezzo * $riga->quantita;
304
+                }
305
+            }
306
+        }
307
+        return $parziale;
308
+    }
309
+
310
+      // public function aggiungiVarianteTuttiPiatto(int $piattoId, int $rigaOrdineId, int $varianteId, string $nota): array
244 311
     // {
245 312
     //     $riga = RigaOrdine::find($rigaOrdineId);
246 313
     //     if (!$riga) {
@@ -288,50 +355,4 @@ class CarrelloService
288 355
     //         'data' => null,
289 356
     //     ];
290 357
     // }
291
-
292
-    public function eliminaVariante(int $rigaOrdineId, int $varianteId): array
293
-    {
294
-        $riga = RigaOrdine::findOrFail($rigaOrdineId);
295
-        if($riga == null){
296
-            return [
297
-                'success' => false,
298
-                'message' => 'Riga ordine non trovata.',
299
-                'data' => null,
300
-            ];
301
-        }
302
-        $variante = VariantePiatto::findOrFail($varianteId);
303
-        if($variante == null){
304
-            return [
305
-                'success' => false,
306
-                'message' => 'Variante non trovata.',
307
-                'data' => null,
308
-            ];
309
-        }
310
-        $pivotVariante = RigaOrdineHasVariante::where('riga_ordine_id', $riga->id)->where('variante_id', $varianteId)->first();
311
-        if($pivotVariante == null){
312
-            return [
313
-                'success' => false,
314
-                'message' => 'Variante non trovata.',
315
-                'data' => null,
316
-            ];
317
-        }
318
-        $pivotVariante->delete();
319
-        
320
-        $parziale = $riga->piatto->prezzo*$riga->quantita;
321
-            // dd($parziale);
322
-            //Se ha delle varianti a pagamento, aggiungo il prezzo delle varianti * quantita
323
-            if($riga->has_variante->isNotEmpty()){
324
-                foreach($riga->has_variante as $variante){
325
-                    $parziale += $variante->variante_piatto->prezzo * $riga->quantita;
326
-                }
327
-            }
328
-            $riga->prezzo = $parziale ;//$piatto->prezzo*$riga->quantita;
329
-            $riga->save();  
330
-            
331
-        return [
332
-            'success' => true,
333
-            'message' => 'Variante eliminata',
334
-            'data' => null,
335
-        ];
336
-    }
337 358
 }

+ 12
- 7
app/Services/Stampa/CreaComandaCucina.php View File

@@ -40,7 +40,7 @@ class CreaComandaCucina
40 40
         $this->printItemsTable($printer, $ordine, $cucina_id);
41 41
 
42 42
         $printer->text("\n");
43
-        $this->printFooter($printer, $ordine, $info, $dataOra);
43
+        $this->printFooter($printer, $ordine, $info, $dataOra, $cucina);
44 44
 
45 45
         $printer->feed(2);
46 46
         $printer->cut();
@@ -137,15 +137,20 @@ class CreaComandaCucina
137 137
         }
138 138
     }
139 139
 
140
-    private function printFooter(Printer $printer, Ordine $ordine, array $info, string $dataOra): void
140
+    private function printFooter(Printer $printer, Ordine $ordine, array $info, string $dataOra, Cucina $cucina): void
141 141
     {
142
-        $coperti = $info['coperti'] ?? null;
143
-        $copertiDisplay = ($coperti !== null && $coperti !== '') ? (string) $coperti : 'ND';
142
+        if($cucina->coperto_come_piatto){
143
+            $coperti = $ordine->righe_ordine->filter(function ($riga) use ($ordine, $cucina) {
144
+                return (int) ($riga->piatto?->cucina_id ?? 0) === $cucina->id;
145
+            });
146
+            $coperti = $coperti->sum('quantita');
147
+            $printer->setTextSize(2, 2);
148
+            $printer->text('Coperti: '.$this->sanitizeLine($coperti)."\n");
149
+        }
144 150
 
145
-        $printer->text('Totale coperti: '.$this->sanitizeLine($copertiDisplay)."\n");
146 151
         $printer->setTextSize(2, 2);
147 152
         $printer->text($this->sanitizeLine($dataOra)."\n");
148
-        $printer->setTextSize(1, 1);
153
+        $printer->setTextSize(2, 2);
149 154
    
150 155
         $printer->text($this->sanitizeLine("Op:".(string) ($ordine->dispositivo?->nome ?? 'ND'))."\n");
151 156
     }
@@ -189,7 +194,7 @@ class CreaComandaCucina
189 194
             if($riga->has_variante->isNotEmpty()){
190 195
                 foreach($riga->has_variante as $variante){
191 196
                     $printer->setTextSize(1, 2);
192
-                    $printer->text(">> ".$variante->variante_piatto->label."\n");
197
+                    $printer->text(str_repeat(' ', self::COL_QTA_WIDTH).">> ".$variante->variante_piatto->label."\n");
193 198
                     $printer->setTextSize(1, 1);
194 199
                
195 200
                 }

+ 38
- 0
database/migrations/2026_06_23_225038_tavolo.php View File

@@ -0,0 +1,38 @@
1
+<?php
2
+
3
+use Illuminate\Database\Migrations\Migration;
4
+use Illuminate\Database\Schema\Blueprint;
5
+use Illuminate\Support\Facades\Schema;
6
+
7
+return new class extends Migration
8
+{
9
+    /**
10
+     * Run the migrations.
11
+     */
12
+    public function up(): void
13
+    {
14
+        Schema::create('tavolo', function (Blueprint $table) {
15
+            $table->bigIncrements('id');
16
+            $table->string('label');
17
+            $table->integer('posti')->nullable();
18
+            $table->string('ubicazione')->nullable();
19
+            $table->integer('coperti')->nullable();
20
+            $table->json('info')->nullable();
21
+            $table->boolean('is_attivo')->default(true);
22
+            $table->bigInteger('attivita_id')->unsigned()->nullable();
23
+            $table->timestamps();
24
+       
25
+        });
26
+        Schema::table('tavolo', function (Blueprint $table) {
27
+            $table->foreign('attivita_id')->references('id')->on('attivita')->onDelete('cascade');
28
+        });
29
+    }
30
+
31
+    /**
32
+     * Reverse the migrations.
33
+     */
34
+    public function down(): void
35
+    {
36
+        Schema::dropIfExists('tavolo');
37
+    }
38
+};

+ 15
- 0
deploy/supervisor/fest-horizon.conf.example View File

@@ -0,0 +1,15 @@
1
+; Copia in /etc/supervisor/conf.d/fest-horizon.conf
2
+; Adatta path, utente e log al server Debian.
3
+;
4
+;   sudo supervisorctl reread && sudo supervisorctl update && sudo supervisorctl start fest-horizon
5
+
6
+[program:fest-horizon]
7
+process_name=%(program_name)s
8
+command=php /var/www/fest/artisan horizon
9
+autostart=true
10
+autorestart=true
11
+user=www-data
12
+numprocs=1
13
+redirect_stderr=true
14
+stdout_logfile=/var/www/fest/storage/logs/horizon.log
15
+stopwaitsecs=3600

+ 37
- 8
resources/menu/verticalMenu.json View File

@@ -18,14 +18,29 @@
18 18
       "name": "Dashboard",
19 19
       "icon": "menu-icon icon-base bx bx-home-smile",
20 20
       "slug": "dashboard",
21
-      "url": "admin/dashboard"
22
-    },
23
-    {
24
-      "name": "Mappa Dispositivi",
25
-      "icon": "menu-icon icon-base bx bx-map",
26
-      "slug": "mappa_dispositivi.index",
27
-      "url": "admin/mappa-dispositivi",
28
-      "can": "permission:view-mappa_dispositivi"
21
+      "submenu": [
22
+        {
23
+          "name": "Dashboard",
24
+          "icon": "menu-icon icon-base bx bx-home-smile",
25
+          "slug": "dashboard",
26
+          "url": "admin/dashboard",
27
+          "permission": "role:superadmin,amministratore"
28
+        },
29
+        {
30
+          "name": "Dispositivi",
31
+          "icon": "menu-icon icon-base bx bx-devices",
32
+          "slug": "mappa_dispositivi.index",
33
+          "url": "admin/mappa-dispositivi",
34
+          "can": "permission:view-mappa_dispositivi"
35
+        },
36
+        {
37
+          "name": "Cucine",
38
+          "icon": "menu-icon icon-base bx bx-chef-hat",
39
+          "slug": "cucina.dashboard",
40
+          "url": "admin/cucina/dashboard",
41
+          "can": "permission:view-cucina"
42
+        }
43
+      ]
29 44
     },
30 45
     {
31 46
       "name": "Oggi",
@@ -34,6 +49,13 @@
34 49
       "url": "admin/bilancio/oggi",
35 50
       "can": "permission:view-bilancio-oggi"
36 51
     },
52
+    {
53
+      "name": "Mappa Dispositivi",
54
+      "icon": "menu-icon icon-base bx bx-map",
55
+      "slug": "mappa_dispositivi.index",
56
+      "url": "admin/mappa-dispositivi",
57
+      "can": "permission:view-mappa_dispositivi"
58
+    },
37 59
     {
38 60
       "name": "Tombola",
39 61
       "icon": "menu-icon icon-base bx bx-trophy",
@@ -320,6 +342,13 @@
320 342
           "url": "admin/piatto",
321 343
           "can": "permission:view-piatto"
322 344
         },
345
+        {
346
+          "name": "Tavoli",
347
+          "icon": "menu-icon icon-base bx bx-table",
348
+          "slug": "tavolo.index",
349
+          "url": "admin/tavolo",
350
+          "can": "permission:view-tavolo"
351
+        },
323 352
         {
324 353
           "name": "Testi",
325 354
           "icon": "menu-icon icon-base bx bx-text",

+ 1
- 1
resources/views/punto_operatore/index.blade.php View File

@@ -8,7 +8,7 @@ $configData = Helper::appClasses();
8 8
 
9 9
 @extends('layouts/layoutMaster')
10 10
 
11
-@section('title', 'Punti Operatore')
11
+@section('title', 'Pass')
12 12
 
13 13
 @section('vendor-style')
14 14
 @vite([

+ 2
- 2
resources/views/punto_operatore/menu.blade.php View File

@@ -12,14 +12,14 @@ use Illuminate\Support\Facades\Auth;
12 12
             <i class="bx bx-show-alt me-1"></i> Visualizza
13 13
         </a>
14 14
         @endif
15
-        @if(Auth::user()->can('edit-punto_operatore_dispositivo'))
15
+        @if(Auth::user()->can('edit-punto_operatore'))
16 16
         <a href="#" class="dropdown-item editor_edit">
17 17
             <i class="bx bx-edit-alt me-1"></i> Modifica
18 18
         </a>
19 19
         @endif
20 20
 
21 21
 
22
-        @if(Auth::user()->can('delete-punto_operatore_dispositivo'))
22
+        @if(Auth::user()->can('delete-punto_operatore'))
23 23
         <a href="#" class="dropdown-item editor_delete">
24 24
             <i class="bx bx-trash me-1"></i> Elimina
25 25
         </a>

+ 134
- 73
resources/views/punto_vendita/cassa/_partials/carrello/pagamento/checkout.blade.php View File

@@ -8,8 +8,45 @@ $ordine = $ordine ?? null;
8 8
 $subtotaleRighe = $ordine ? $ordine->righe_ordine->sum('prezzo') : 0;
9 9
 $totaleOrdine = ($ordine && $ordine->totale > 0) ? (float) $ordine->totale : (float) $subtotaleRighe;
10 10
 $nRighe = $ordine ? $ordine->righe_ordine->count() : 0;
11
+$infoDispositivo = data_get($dispositivo, 'info', []);
12
+if (is_string($infoDispositivo)) {
13
+    $decodedInfo = json_decode($infoDispositivo, true);
14
+    $infoDispositivo = is_array($decodedInfo) ? $decodedInfo : [];
15
+}
11 16
 
17
+$campiOrdine = $infoDispositivo['campiOrdine'] ?? null;
18
+if (!is_array($campiOrdine) || empty($campiOrdine)) {
19
+    $campiOrdine = $infoDispositivo['campi_ordine'] ?? null;
20
+}
21
+if (!is_array($campiOrdine) || empty($campiOrdine)) {
22
+    $campiOrdine = $infoDispositivo['info_cliente'] ?? null;
23
+}
24
+if (!is_array($campiOrdine) || empty($campiOrdine)) {
25
+    $campiOrdine = $infoDispositivo['infoCliente'] ?? null;
26
+}
27
+if (!is_array($campiOrdine)) {
28
+    $campiOrdine = [];
29
+}
30
+
31
+if (empty($campiOrdine) || !is_array($campiOrdine)) {
32
+    $campiOrdine = [
33
+        'cliente' => ['label' => 'Cliente', 'type' => 'text', 'mostra' => true, 'required' => true, 'placeholder' => 'Cliente'],
34
+        'tavolo' => ['label' => 'Tavolo', 'type' => 'text', 'mostra' => true, 'required' => true, 'placeholder' => 'Tavolo'],
35
+    ];
36
+}
12 37
 
38
+$tipiSupportati = ['text', 'tel', 'email', 'number', 'checkbox'];
39
+$campiOrdineVisibili = collect($campiOrdine)
40
+    ->filter(function ($campo) {
41
+        if (!is_array($campo)) {
42
+            return false;
43
+        }
44
+        // Se il flag non e` presente, il campo e` considerato visibile.
45
+        if (!array_key_exists('mostra', $campo)) {
46
+            return true;
47
+        }
48
+        return filter_var($campo['mostra'], FILTER_VALIDATE_BOOLEAN);
49
+    });
13 50
 @endphp
14 51
 <form action="{{ route('carrello.paga_ordine') }}" method="POST">
15 52
         @csrf
@@ -20,27 +57,103 @@ $nRighe = $ordine ? $ordine->righe_ordine->count() : 0;
20 57
 
21 58
 <div class="card px-2 px-sm-3 border-0 shadow-none bg-transparent">
22 59
   <div class="row g-0">
23
-    <div class="col-lg-7 card-body border-end border-secondary border-opacity-10 p-md-6 p-3">
24
-      <h4 class="mb-2">Metodo di pagamento</h4>
25
-        <p class="mb-0 text-muted small">
26
-            Scegli il metodo di pagamento per completare il pagamento.
60
+    <div class="col-lg-5 card-body border-end border-secondary border-opacity-10 p-md-6 p-3">
61
+      <h4 class="mb-2">Info utente</h4>
62
+      <p class="mb-4 text-muted small">
63
+        Dati ordine raccolti in cassa.
27 64
       </p>
28 65
 
66
+      <div class="border rounded p-3 bg-lighter mb-4">
67
+        @if($campiOrdineVisibili->isNotEmpty())
68
+          @foreach($campiOrdineVisibili as $chiave => $campo)
69
+            @php
70
+              $tipo = in_array(($campo['type'] ?? 'text'), $tipiSupportati, true) ? $campo['type'] : 'text';
71
+              $label = $campo['label'] ?? ucfirst(str_replace('_', ' ', $chiave));
72
+              $placeholder = $campo['placeholder'] ?? $label;
73
+              $required = !empty($campo['required']);
74
+              $valore = data_get($ordine, "info.$chiave");
75
+              $inputId = 'checkout-info-'.$chiave;
76
+            @endphp
77
+
78
+            @if($tipo === 'checkbox')
79
+              <div class="form-check form-switch mb-3 pb-2 border-bottom border-light-subtle">
80
+                <input type="hidden" name="info[{{ $chiave }}]" value="0">
81
+                <input
82
+                  class="form-check-input"
83
+                  type="checkbox"
84
+                  role="switch"
85
+                  id="{{ $inputId }}"
86
+                  name="info[{{ $chiave }}]"
87
+                  value="1"
88
+                  data-campo-ordine="{{ $chiave }}"
89
+                  {{ filter_var($valore, FILTER_VALIDATE_BOOLEAN) ? 'checked' : '' }}
90
+                  {{ $required ? 'required' : '' }}
91
+                >
92
+                <label class="form-check-label text-body" for="{{ $inputId }}">{{ $label }}</label>
93
+              </div>
94
+            @else
95
+              <div class="mb-3 pb-2 border-bottom border-light-subtle">
96
+                <label class="form-label mb-1 text-capitalize" for="{{ $inputId }}">{{ $label }}</label>
97
+                <input
98
+                  type="{{ $tipo }}"
99
+                  class="form-control form-control-sm"
100
+                  id="{{ $inputId }}"
101
+                  name="info[{{ $chiave }}]"
102
+                  value="{{ $valore ?? '' }}"
103
+                  placeholder="{{ $placeholder }}"
104
+                  data-campo-ordine="{{ $chiave }}"
105
+                  {{ $required ? 'required' : '' }}
106
+                >
107
+              </div>
108
+            @endif
109
+          @endforeach
110
+        @else
111
+          <p class="text-muted small mb-0">Nessun campo utente configurato per questo punto vendita.</p>
112
+        @endif
113
+      </div>
114
+
115
+      <h5 class="mb-3">Note</h5>
116
+      <div class="row g-3">
117
+        <div class="col-12">
118
+          <label class="form-label" for="checkout-note-cassa">Annotazioni (opzionale)</label>
119
+          <textarea id="checkout-note-cassa" class="form-control" rows="2" placeholder="Es. scontrino fattura, resto..."></textarea>
120
+        </div>
121
+      </div>
122
+    </div>
123
+
124
+    <div class="col-lg-7 card-body p-md-6 p-3 d-flex flex-column">
125
+      <div class="pt-2 border-top">
126
+        <div class="bg-lighter p-3 rounded">
127
+          <p class="mb-1 fw-medium">Totale da incassare</p>
128
+          <div class="d-flex align-items-baseline justify-content-between">
129
+            <p class="small text-muted mb-0">{{ $nRighe }} {{ $nRighe === 1 ? 'articolo' : 'articoli' }}</p>
130
+            <h3 class="text-primary mb-0">€ {{ number_format($totaleOrdine, 2, ',', '.') }}</h3>
131
+          </div>
132
+          @if($ordine && $ordine->totale > 0 && abs((float) $ordine->totale - (float) $subtotaleRighe) > 0.009)
133
+            <div class="d-flex justify-content-between align-items-center mt-2">
134
+              <p class="mb-0 small text-muted">Totale ordine (sistema)</p>
135
+              <h6 class="mb-0">€ {{ number_format((float) $ordine->totale, 2, ',', '.') }}</h6>
136
+            </div>
137
+          @endif
138
+        </div>
139
+      </div>
140
+
141
+      <h6 class="mb-1 mt-3">Metodo di pagamento</h6>
142
+      <p class="mb-0 text-muted small">
143
+        Seleziona il metodo e conferma.
144
+      </p>
29 145
 
30
-    
31 146
       @if($metodi->isEmpty())
32 147
         <p class="text-warning mt-4 mb-0">Nessun metodo di pagamento attivo.</p>
33 148
       @else
34
-        <div class="row g-4 py-6">
149
+        <div class="row g-2 py-4">
35 150
           @foreach($metodi as $metodo)
36
-
37
-            <div class="col-md col-lg-12 col-xl-12">
151
+            <div class="col-12">
38 152
               <div class="form-check custom-option custom-option-basic {{ $loop->first ? 'checked' : '' }}">
39
-                <label class="form-check-label custom-option-content form-check-input-payment d-flex gap-3 gap-md-4 align-items-center" for="metodoPagamento{{ $metodo->id }}">
153
+                <label class="form-check-label custom-option-content form-check-input-payment d-flex gap-3 align-items-center py-2" for="metodoPagamento{{ $metodo->id }}">
40 154
                   <input
41 155
                     name="metodo_pagamento_id"
42 156
                     id="metodoPagamento{{ $metodo->id }}"
43
-
44 157
                     type="radio"
45 158
                     value="{{ $metodo->id }}"
46 159
                     @if($metodo->tipo == MetodoPagamento::SEGRESTA_WALLET)
@@ -48,22 +161,21 @@ $nRighe = $ordine ? $ordine->righe_ordine->count() : 0;
48 161
                       data-tipo="{{ MetodoPagamento::SEGRESTA_WALLET }}"
49 162
                     @else
50 163
                       class="form-check-input"
51
-                      @endif
164
+                    @endif
52 165
                     {{ $loop->first ? 'checked' : '' }}
53 166
                   />
54 167
                   <span class="custom-option-body d-flex align-items-center">
55 168
                     @if(!$metodo->path_img)
56
-                      <span class="d-inline-flex align-items-center justify-content-center rounded bg-label-primary bg-opacity-10 flex-shrink-0" style="width: 58px; height: 58px;">
57
-                        <i class="bx {{ $metodo->getIcona() }} fs-2 text-primary"></i>
169
+                      <span class="d-inline-flex align-items-center justify-content-center rounded bg-label-primary bg-opacity-10 flex-shrink-0" style="width: 46px; height: 46px;">
170
+                        <i class="bx {{ $metodo->getIcona() }} fs-4 text-primary"></i>
58 171
                       </span>
59 172
                     @endif
60
-                    <span class="ms-3 fw-medium text-heading text-start">
173
+                    <span class="ms-2 fw-medium text-heading text-start">
61 174
                       {{ $metodo->nome }}
62 175
                       @if($metodo->descrizione)
63
-                        <span class="d-block small text-muted fw-normal mt-1">{{ Str::limit($metodo->descrizione, 80) }}</span>
176
+                        <span class="d-block small text-muted fw-normal mt-1">{{ Str::limit($metodo->descrizione, 60) }}</span>
64 177
                       @endif
65 178
                     </span>
66
-
67 179
                   </span>
68 180
                 </label>
69 181
                 @if($metodo->tipo == MetodoPagamento::SEGRESTA_WALLET)
@@ -77,69 +189,18 @@ $nRighe = $ordine ? $ordine->righe_ordine->count() : 0;
77 189
         </div>
78 190
       @endif
79 191
 
80
-      <h5 class="mb-4 mt-2">Note</h5>
81
-      <div class="row g-4">
82
-        <div class="col-12">
83
-          <label class="form-label" for="checkout-note-cassa">Annotazioni (opzionale)</label>
84
-          <textarea id="checkout-note-cassa" class="form-control" rows="2" placeholder="Es. scontrino fattura, resto…"></textarea>
85
-        </div>
86
-      </div>
87
-    </div>
88
-
89
-    <div class="col-lg-5 card-body p-md-6 p-3">
90
-      <h4 class="mb-2">Riepilogo ordine</h4>
91
-      <p class="mb-6 text-muted small">
92
-        Verifica importi prima di incassare.
93
-      </p>
94
-
95
-      <div class="bg-lighter p-4 rounded">
96
-        <p class="mb-1 fw-medium mb-2">Ordine cassa</p>
97
-        <div class="d-flex align-items-baseline mb-0 justify-content-center">
98
-          <h1 class="text-primary mb-0">€ {{ number_format($totaleOrdine, 2, ',', '.') }}</h1>
99
-        </div>
100
-        <p class="small text-muted mb-0 mt-2">{{ $nRighe }} {{ $nRighe === 1 ? 'articolo' : 'articoli' }}</p>
101
-      </div>
102
-
103
-      <div class="mt-5">
104
-        <div class="d-flex justify-content-between align-items-center">
105
-          <!-- <p class="mb-0">Info ordine</p> -->
106
-          <h6 class="mb-0 small text-muted text-start">
107
-            @if($ordine->info)
108
-              @foreach($ordine->info as $key => $value)
109
-                <p class="mb-0 text-capitalize">{{ $key }}: <span class="fw-medium fw-bold">{{ $value }}</span></p>
110
-              @endforeach
111
-            @else
112
-              Nessuna info
113
-            @endif
114
-
115
-          </h6>
116
-        </div>
117
-        @if($ordine && $ordine->totale > 0 && abs((float) $ordine->totale - (float) $subtotaleRighe) > 0.009)
118
-          <div class="d-flex justify-content-between align-items-center mt-2">
119
-            <p class="mb-0 small text-muted">Totale ordine (sistema)</p>
120
-            <h6 class="mb-0">€ {{ number_format((float) $ordine->totale, 2, ',', '.') }}</h6>
121
-          </div>
122
-        @endif
123
-        <hr>
124
-        <div class="d-flex justify-content-between align-items-center mt-4 pb-1">
125
-          <p class="mb-0 fw-semibold">Da incassare</p>
126
-          <h5 class="mb-0">€ {{ number_format($totaleOrdine, 2, ',', '.') }}</h5>
127
-        </div>
128
-        <div class="d-grid mt-5">
192
+      <div class="mt-auto pt-3">
193
+        <div class="d-grid">
129 194
           <button type="submit" id="btn-conferma-pagamento" class="btn btn-success" {{ $metodi->isEmpty() ? 'disabled' : '' }}>
130
-
131
-          <span class="spinner-border spinner-border-sm d-none" id="checkout-submit-spinner" role="status"></span>
132
-
195
+            <span class="spinner-border spinner-border-sm d-none" id="checkout-submit-spinner" role="status"></span>
133 196
             <span class="me-2" id="checkout-submit-label">Conferma pagamento</span>
134 197
             <i class="bx bx-right-arrow-alt scaleX-n1-rtl" id="checkout-submit-icon"></i>
135 198
           </button>
136
-
137 199
         </div>
138
-        <p class="mt-6 mb-0 small text-muted" id="segrestaWalletChiE" style="display: none;">
139
-        </p>
140 200
 
141
-        <p class="mt-6 mb-0 small text-muted">
142
-          Confermando, registri il pagamento secondo il metodo selezionato. Verifica sempre l’importo in cassa.
201
+        <p class="mt-3 mb-0 small text-muted" id="segrestaWalletChiE" style="display: none;"></p>
202
+        <p class="mt-3 mb-0 small text-muted">
203
+          Confermando, registri il pagamento secondo il metodo selezionato. Verifica sempre l'importo in cassa.
143 204
         </p>
144 205
       </div>
145 206
     </div>

+ 67
- 65
resources/views/punto_vendita/cassa/_partials/carrello/riepilogo.blade.php View File

@@ -1,72 +1,74 @@
1 1
 <div class="col-12">
2
-    <div class="card">
3
-      <div class="card-widget-separator-wrapper">
4
-        <div class="p-2 card-widget-separator">
5
-          <div class="gy-4 gy-sm-1 justify-content-around row">
6
-            <div class="col-sm-6 col-md-6 col-lg-6">
7
-              <div class="d-flex justify-content-between align-items-center card-widget-1  pb-4 pb-sm-0">
8
-                <div>
9
-                  <h4 class="mb-0">
10
-                    <input type="text" placeholder="Cliente" class="form-control text-primary border-0 fw-bold border-bottom border-primary border-radius-0" style="border-radius: 0px;" id="cliente" value="{{ $ordine->info['cliente']??'' }}" aria-describedby="defaultFormControlHelp" @if($punto_vendita->info['campi_ordine']['cliente']['required'] ?? false) required @endif>
11
-                </h4>
12
-                  <small class="mb-0 text-primary">Cliente</small>
13
-                </div>
2
+  <div class="card shadow-sm border-0 mb-2">
3
+    <div class="card-body p-3">
4
+      <div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-2">
5
+        <h6 class="mb-0 text-primary">Dati ordine</h6>
6
+      </div>
14 7
 
15
-              </div>
16
-              <hr class="d-none d-sm-block d-lg-none me-6">
17
-            </div>
18
-            <div class="col-sm-3 col-md-3 col-lg-3">
19
-              <div class="d-flex justify-content-between align-items-center card-widget-2  pb-4 pb-sm-0">
20
-                <div>
21
-                  <h4 class="mb-0">
22
-                  <input type="text" placeholder="Tavolo" class="form-control border-0 fw-bold border-bottom border-primary border-radius-0"  style="border-radius: 0px;" id="tavolo" value="{{ $ordine->info['tavolo']??'' }}" aria-describedby="defaultFormControlHelp" @if($punto_vendita->info['campi_ordine']['tavolo']['required'] ?? false) required @endif>
23
-                </h4>
24
-                  <small class="mb-0 text-primary">Tavolo</small>
25
-                </div>
26
-
27
-              </div>
28
-              <hr class="d-none d-sm-block d-lg-none">
29
-            </div>
30
-            <!-- <div class="col-sm-3 col-md-3 col-lg-3">
31
-              <div class="d-flex justify-content-between align-items-center  pb-4 pb-sm-0 card-widget-3 ">
32
-                <div>
33
-                  <h4 class="mb-0 text-primary fw-bold border-bottom border-primary border-radius-0" id="totale_carrello">
34
-                    @if($ordine->totale > 0)
35
-                     € {{ number_format($ordine->totale, 2, ',', '.') }}
36
-                    @else
37
-                    € {{ number_format($ordine->righe_ordine->sum('prezzo'), 2, ',', ',') }}
38
-                    @endif
8
+      <div class="row g-2">
9
+        <div class="col-12 col-md-6 col-lg-5">
10
+          <label class="form-label small text-muted mb-1" for="cliente">Cliente</label>
11
+          <input
12
+            type="text"
13
+            id="cliente"
14
+            placeholder="Cliente"
15
+            class="form-control fw-semibold"
16
+            value="{{ $ordine->info['cliente'] ?? '' }}"
17
+            @if($punto_vendita->info['campi_ordine']['cliente']['required'] ?? false) required @endif
18
+          >
19
+        </div>
39 20
 
40
-                  </h4>
41
-                  <small class="mb-0 text-primary">Totale</small>
42
-                </div>
21
+        <div class="col-6 col-md-3 col-lg-3">
22
+          <label class="form-label small text-muted mb-1" for="tavolo">Tavolo</label>
23
+          <input
24
+            type="text"
25
+            id="tavolo"
26
+            placeholder="Tavolo"
27
+            class="form-control fw-semibold"
28
+            value="{{ $ordine->info['tavolo'] ?? '' }}"
29
+            @if($punto_vendita->info['campi_ordine']['tavolo']['required'] ?? false) required @endif
30
+          >
31
+        </div>
43 32
 
44
-              </div>
45
-            </div> -->
33
+        <div class="col-6 col-md-3 col-lg-2">
34
+          <label class="form-label small text-muted mb-1" for="coperti">Coperti</label>
35
+          <input
36
+            type="number"
37
+            id="coperti"
38
+            min="0"
39
+            step="1"
40
+            placeholder="0"
41
+            class="form-control fw-semibold"
42
+            value="{{ $ordine->info['coperti'] ?? '' }}"
43
+            @if($punto_vendita->info['campi_ordine']['coperti']['required'] ?? false) required @endif
44
+          >
45
+        </div>
46
+      </div>
46 47
 
47
-          </div>
48
+      <div class="d-flex flex-wrap justify-content-end gap-2 mt-2 pt-2 border-top">
49
+        <button class="btn btn-outline-danger" onclick="azzeraCarrello()">
50
+          <i class="bx bx-x-circle me-1"></i>
51
+          Svuota carrello
52
+        </button>
53
+        <button class="btn btn-primary" onclick="checkout()">
54
+          <i class="bx bx-credit-card me-1"></i>
55
+          Vai al pagamento
56
+        </button>
57
+      </div>
58
+    </div>
59
+  </div>
48 60
 
49
-          <!-- vai al pagamento -->
50
-          <div class="col-12 text-center mt-2">
51
-          <button class="btn btn-danger" onclick="azzeraCarrello()">
52
-              <i class="bx bx-x-circle fs-4 mx-2"></i>
53
-              Svuota carrello
54
-            </button>
55
-            <button class="btn btn-primary" onclick="checkout()">
56
-              <i class="bx bx-credit-card"></i>
57
-              Vai al pagamento
58
-            </button>
59
-            <div class="modal fade" id="modalPagamento" tabindex="-1" aria-hidden="true">
60
-              <div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable" role="document">
61
-                <div class="modal-content">
62
-                  <div class="modal-header">
63
-                    <h5 class="modal-title" >Pagamento</h5>
64
-                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
65
-                  </div>
66
-                  <div class="modal-body">
67
-                  //Checkout
68
-                </div>
69
-                </div>
70
-            </div>
61
+  <div class="modal fade" id="modalPagamento" tabindex="-1" aria-hidden="true">
62
+    <div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable" role="document">
63
+      <div class="modal-content">
64
+        <div class="modal-header">
65
+          <h5 class="modal-title">Pagamento</h5>
66
+          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
67
+        </div>
68
+        <div class="modal-body">
69
+          //Checkout
71 70
         </div>
72
-    </div>
71
+      </div>
72
+    </div>
73
+  </div>
74
+</div>

+ 7
- 11
resources/views/punto_vendita/cassa/_partials/scripts.blade.php View File

@@ -469,6 +469,9 @@ function azzeraCarrelloAjax() {
469 469
     $("#tavolo").on('change', function() {
470 470
       aggiornaInfoOrdine();
471 471
     });
472
+    $("#coperti").on('change', function() {
473
+      aggiornaInfoOrdine();
474
+    });
472 475
     
473 476
 
474 477
         // Editor edit
@@ -555,16 +558,8 @@ function azzeraCarrelloAjax() {
555 558
     if (!card || !el) {
556 559
       return;
557 560
     }
558
-
559
-    var footer = document.querySelector('.cassa-footer');
560
-    var footerGap = footer ? footer.getBoundingClientRect().height + 8 : 8;
561
-    var available = window.innerHeight - el.getBoundingClientRect().top - footerGap;
562
-
563
-    if (available > 80) {
564
-      el.style.maxHeight = Math.floor(available) + 'px';
565
-    } else {
566
-      el.style.maxHeight = '';
567
-    }
561
+    el.style.flex = '0 1 auto';
562
+    el.style.maxHeight = '';
568 563
     el.style.overflowY = 'auto';
569 564
 
570 565
     window.requestAnimationFrame(function() {
@@ -622,7 +617,8 @@ function azzeraCarrelloAjax() {
622 617
       data: {
623 618
         ordine_id: getOrdineId(),
624 619
         cliente: $("#cliente").val(),
625
-        tavolo: $("#tavolo").val(), 
620
+        tavolo: $("#tavolo").val(),
621
+        coperti: $("#coperti").val(),
626 622
         _token: "{{ csrf_token() }}",
627 623
       },
628 624
       success: function(response) {

+ 1
- 2
resources/views/punto_vendita/edit.blade.php View File

@@ -73,9 +73,8 @@ $configData = Helper::appClasses();
73 73
   $campiOrdineCatalogo = [
74 74
     'cliente' => ['label' => 'Cliente', 'icon' => 'bx-user', 'type' => 'text', 'mostra' => true, 'required' => true, 'stampa' => true, 'placeholder' => 'Cliente'],
75 75
     'tavolo' => ['label' => 'Tavolo', 'icon' => 'bx-chair', 'type' => 'text', 'mostra' => true, 'required' => true, 'stampa' => true, 'placeholder' => 'Tavolo'],
76
-    'telefono' => ['label' => 'Telefono', 'icon' => 'bx-phone', 'type' => 'tel', 'mostra' => false, 'required' => false, 'stampa' => false, 'placeholder' => 'Telefono'],
77
-    'note_ordine' => ['label' => 'Note ordine', 'icon' => 'bx-note', 'type' => 'text', 'mostra' => false, 'required' => false, 'stampa' => true, 'placeholder' => 'Note per la cucina'],
78 76
     'coperti' => ['label' => 'N. coperti', 'icon' => 'bx-group', 'type' => 'number', 'mostra' => false, 'required' => false, 'stampa' => false, 'placeholder' => 'Coperti'],
77
+    'telefono' => ['label' => 'Telefono', 'icon' => 'bx-phone', 'type' => 'tel', 'mostra' => false, 'required' => false, 'stampa' => false, 'placeholder' => 'Telefono'],
79 78
   ];
80 79
   $campiOrdineSalvati = $puntoVendita->info['campi_ordine'] ?? [];
81 80
   $campiOrdine = [];

+ 130
- 0
resources/views/tavolo/index.blade.php View File

@@ -0,0 +1,130 @@
1
+<?php
2
+use App\Models\Role;
3
+use Illuminate\Support\Facades\Auth;
4
+?>
5
+@php
6
+$configData = Helper::appClasses();
7
+@endphp
8
+
9
+@extends('layouts/layoutMaster')
10
+
11
+@section('title', 'Tavoli')
12
+
13
+@section('vendor-style')
14
+@vite([
15
+'resources/assets/vendor/libs/datatables-bs5/datatables.bootstrap5.scss',
16
+'resources/assets/vendor/libs/datatables-responsive-bs5/responsive.bootstrap5.scss',
17
+'resources/assets/vendor/libs/datatables-buttons-bs5/buttons.bootstrap5.scss',
18
+'resources/assets/vendor/libs/flatpickr/flatpickr.scss',
19
+'resources/assets/vendor/libs/@form-validation/form-validation.scss'
20
+])
21
+@endsection
22
+
23
+<!-- Vendor Scripts -->
24
+@section('vendor-script')
25
+@vite([
26
+'resources/assets/vendor/libs/moment/moment.js',
27
+'resources/assets/vendor/libs/flatpickr/flatpickr.js',
28
+'resources/assets/vendor/libs/@form-validation/popular.js',
29
+'resources/assets/vendor/libs/@form-validation/bootstrap5.js',
30
+'resources/assets/vendor/libs/@form-validation/auto-focus.js',
31
+'resources/assets/vendor/libs/@form-validation/popular.js',
32
+'resources/assets/vendor/libs/@form-validation/bootstrap5.js',
33
+'resources/assets/vendor/libs/@form-validation/auto-focus.js',
34
+'resources/assets/vendor/libs/datatables-bs5/datatables-bootstrap5.js'
35
+])
36
+@endsection
37
+
38
+@section('pageTitle')
39
+<div class="d-flex flex-column">
40
+  <h4 class="mb-1 text-sm-small mt-md-4 mt-lg-4"> 
41
+    <i class="bx bx-table d-none d-md-inline-flex d-lg-inline-flex"></i> Tavoli</h4>
42
+    <nav aria-label="breadcrumb" style="font-size: smaller;" class="d-none d-md-block d-lg-block">
43
+            <ol class="breadcrumb breadcrumb-custom-icon">
44
+      
45
+              <li class="breadcrumb-item">
46
+                <a href="#">Configurazioni</a>
47
+                <i class="breadcrumb-icon icon-base bx bx-chevron-right align-middle"></i>
48
+              </li>
49
+              <li class="breadcrumb-item active text-primary">
50
+                <a href="{{ route('tavolo.index') }}">Tavoli</a>
51
+              </li>
52
+            </ol>
53
+          </nav>
54
+</div>
55
+@endsection
56
+
57
+@section('content')
58
+<style>
59
+  div.upload button:first-child{
60
+    display: none !important;
61
+  }
62
+
63
+.bx-chef-hat{
64
+  --svg: url("data:image/svg+xml,%3csvg width='24' height='24' fill='currentColor' viewBox='0 0 24 24' transform='' xmlns='http://www.w3.org/2000/svg'%3e%3c!--Boxicons v3.0.8 https://boxicons.com %7c License https://docs.boxicons.com/free--%3e%3cpath d='M17.13 5.54C16.33 3.42 14.32 2 12 2S7.67 3.42 6.87 5.54A5.506 5.506 0 0 0 2 11c0 2.07 1.18 3.95 3 4.88V18c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-2.12c1.82-.93 3-2.81 3-4.88 0-2.82-2.13-5.15-4.87-5.46m.53 8.75c-.4.14-.67.52-.67.94v1.78H7v-1.78c0-.42-.27-.8-.67-.94-1.4-.5-2.33-1.82-2.33-3.28 0-1.93 1.57-3.5 3.42-3.5.04 0 .14.01.18.02.49 0 .9-.31 1-.78.36-1.61 1.76-2.73 3.41-2.73s3.05 1.12 3.41 2.73c.1.47.51.81 1 .78.06 0 .12 0 .09-.01 1.93 0 3.5 1.57 3.5 3.5 0 1.47-.94 2.79-2.33 3.28ZM5 20h14v2H5z'%3e%3c/path%3e%3c/svg%3e");
65
+}
66
+</style>
67
+
68
+@include('_partials.status')
69
+
70
+<div class="row justify-content-center">
71
+  <div class="col-xxl mb-4 mt-2">
72
+    <div class="card">
73
+      <div class="card-body">
74
+        {{ $dataTable_tavolo->table(['class' => 'table table-bordered']) }}
75
+      </div>
76
+    </div>
77
+  </div>
78
+</div>
79
+
80
+
81
+
82
+@endsection
83
+
84
+@section('page-script')
85
+
86
+{{$dataTable_tavolo->scripts(attributes: ['type' => 'module'])}}
87
+
88
+
89
+<script type="module">
90
+  $(document).ready(function(){
91
+    // Editor edit
92
+    $("#dataTable_tavolo").on('click', 'a.editor_edit', function (e) {
93
+      e.preventDefault();
94
+      window.LaravelDataTables["dataTable_tavolo-editor"].edit( $(this).closest('tr'), {
95
+        title: 'Modifica',
96
+        buttons: 'Aggiorna',
97
+      });
98
+    });
99
+
100
+    // Editor delete
101
+    $("#dataTable_tavolo").on('click', 'a.editor_delete', function (e) {
102
+      e.preventDefault();
103
+      window.LaravelDataTables["dataTable_tavolo-editor"].remove($(this).closest('tr'), {
104
+        title: 'Cancella record',
105
+        message: 'Sei sicuro di voler eliminare il record selezionato?',
106
+        buttons: 'Cancella record'
107
+      });
108
+    } );
109
+
110
+    $("#dataTable_tavolo").on('dblclick', 'tbody td', function (e) {
111
+      window.LaravelDataTables["dataTable_tavolo-editor"].edit( $(this).closest('tr'), {
112
+        title: 'Modifica',
113
+        buttons: 'Aggiorna',
114
+      });
115
+    });
116
+
117
+  });
118
+
119
+</script>
120
+
121
+<script>
122
+  function initComplete_tavolo(){
123
+    $('div.dt-buttons button').removeClass('btn-secondary');
124
+    $('div.dt-buttons').removeClass('btn-group');
125
+    $('div.dt-search').addClass('mt-0 mb-4');
126
+  }
127
+
128
+
129
+</script>
130
+@endsection

+ 22
- 0
resources/views/tavolo/menu.blade.php View File

@@ -0,0 +1,22 @@
1
+<?php
2
+use Illuminate\Support\Facades\Auth;
3
+?>
4
+<div class="dropdown">
5
+    <button type="button" class="btn p-0 dropdown-toggle hide-arrow" data-bs-toggle="dropdown" aria-expanded="false">
6
+        <i class="bx bx-dots-vertical-rounded fs-large"></i>
7
+    </button>
8
+    <div class="dropdown-menu">
9
+
10
+
11
+    @if(Auth::user()->can('edit-tavolo'))
12
+        <a href="#" class="dropdown-item editor_edit">
13
+            <i class="bx bx-edit-alt me-1"></i> Modifica
14
+        </a>
15
+        @endif
16
+        @if(Auth::user()->can('delete-tavolo'))
17
+        <a href="#" class="dropdown-item editor_delete">
18
+            <i class="bx bx-trash me-1"></i> Elimina
19
+        </a>
20
+        @endif
21
+    </div>
22
+</div>

+ 4
- 1
routes/web.php View File

@@ -43,7 +43,8 @@ use App\Http\Controllers\OperatoreController;
43 43
 use App\Http\Controllers\TombolaController;
44 44
 use App\Http\Controllers\ChiusuraServizioController;
45 45
 use App\Http\Controllers\ImpostazioniController;
46
-use App\Http\Controllers\VariantePiattoController;
46
+use App\Http\Controllers\VariantePiattoController;  
47
+use App\Http\Controllers\TavoloController;
47 48
 
48 49
 Route::get('/', [HomePageController::class, 'welcome'])->name('welcome');
49 50
 
@@ -250,6 +251,8 @@ Route::get('code-stampa/show', [PrintJobController::class, 'show'])->name('code-
250 251
 // Testi
251 252
 Route::resource('testi', TestiController::class, ['only' => ['index', 'store']]);
252 253
 
254
+// Tavolo
255
+Route::resource('tavolo', TavoloController::class, ['only' => ['index', 'store']]);
253 256
 
254 257
 //Tombola
255 258
 Route::resource('tombola', TombolaController::class, ['only' => ['index', 'store']]);

Loading…
Cancel
Save