Browse Source

asporto, tavoli, welcome, public e fix var

marcofalabretti 2 days ago
parent
commit
f6741c8ad4
100 changed files with 5692 additions and 495 deletions
  1. 0
    76
      .env
  2. 3
    0
      .env.example
  3. 1
    1
      .gitignore
  4. 1
    1
      app/DataTables/AllergeneDataTable.php
  5. 2
    2
      app/DataTables/AttivitaDataTable.php
  6. 58
    29
      app/DataTables/AttivitaDataTableEditor.php
  7. 0
    2
      app/DataTables/ContenutoBachecaDataTableEditor.php
  8. 1
    1
      app/DataTables/DispositivoDataTable.php
  9. 76
    19
      app/DataTables/EndpointDataTable.php
  10. 232
    0
      app/DataTables/EndpointDataTableEditor.php
  11. 128
    0
      app/DataTables/FatturaDataTable.php
  12. 108
    0
      app/DataTables/FatturaDataTableEditor.php
  13. 36
    12
      app/DataTables/ImpostazioniDataTable.php
  14. 133
    0
      app/DataTables/ImpostazioniDataTableEditor.php
  15. 133
    0
      app/DataTables/ListinoPrezziDataTable.php
  16. 104
    0
      app/DataTables/ListinoPrezziDataTableEditor.php
  17. 7
    3
      app/DataTables/OrdineDataTable.php
  18. 138
    0
      app/DataTables/PrenotazioneTavoloDataTable.php
  19. 108
    0
      app/DataTables/PrenotazioneTavoloDataTableEditor.php
  20. 22
    12
      app/DataTables/PuntoVenditaDataTable.php
  21. 187
    0
      app/DataTables/RigaFatturazioneDataTable.php
  22. 100
    0
      app/DataTables/RigaFatturazioneDataTableEditor.php
  23. 3
    17
      app/DataTables/RigaOrdineDataTable.php
  24. 33
    15
      app/DataTables/StampanteDataTable.php
  25. 9
    4
      app/DataTables/TavoloDataTable.php
  26. 4
    3
      app/DataTables/VariantePiattoDataTable.php
  27. 6
    5
      app/DataTables/VariantePiattoDataTableEditor.php
  28. 457
    0
      app/Http/Controllers/AsportoController.php
  29. 9
    18
      app/Http/Controllers/BilancioController.php
  30. 20
    7
      app/Http/Controllers/CarrelloController.php
  31. 182
    29
      app/Http/Controllers/EndpointController.php
  32. 82
    0
      app/Http/Controllers/FatturaController.php
  33. 19
    3
      app/Http/Controllers/HomePageController.php
  34. 23
    2
      app/Http/Controllers/ImpostazioniController.php
  35. 90
    0
      app/Http/Controllers/ListinoPrezziController.php
  36. 281
    51
      app/Http/Controllers/MappaDispositiviController.php
  37. 119
    0
      app/Http/Controllers/PrenotazioneTavoloController.php
  38. 169
    45
      app/Http/Controllers/PuntoVenditaController.php
  39. 81
    0
      app/Http/Controllers/RigaFatturazioneController.php
  40. 154
    52
      app/Http/Controllers/SaltacodaController.php
  41. 1
    0
      app/Http/Controllers/TavoloController.php
  42. 54
    1
      app/Http/Controllers/UserController.php
  43. 68
    0
      app/Models/AbstractModels/AbstractAsporto.php
  44. 2
    0
      app/Models/AbstractModels/AbstractEndpoint.php
  45. 98
    0
      app/Models/AbstractModels/AbstractFattura.php
  46. 74
    0
      app/Models/AbstractModels/AbstractListinoPrezzi.php
  47. 5
    0
      app/Models/AbstractModels/AbstractOrdine.php
  48. 83
    0
      app/Models/AbstractModels/AbstractPrenotazioneTavolo.php
  49. 91
    0
      app/Models/AbstractModels/AbstractRigaFatturazione.php
  50. 6
    0
      app/Models/AbstractModels/AbstractSaltacoda.php
  51. 2
    2
      app/Models/AbstractModels/AbstractSaltacodaOrdine.php
  52. 69
    0
      app/Models/AbstractModels/AbstractSaltacodaRigaOrdineHasVariante.php
  53. 4
    0
      app/Models/AbstractModels/AbstractSaltacodaRigaordine.php
  54. 77
    0
      app/Models/AbstractModels/AbstractSlotTemporaliPrenotazioneTavolo.php
  55. 4
    0
      app/Models/AbstractModels/AbstractUser.php
  56. 151
    0
      app/Models/Asporto.php
  57. 40
    3
      app/Models/Attivita.php
  58. 32
    0
      app/Models/Categoriacontabile.php
  59. 51
    0
      app/Models/Dispositivo.php
  60. 19
    0
      app/Models/Fattura.php
  61. 22
    0
      app/Models/Impostazioni.php
  62. 77
    0
      app/Models/ListinoPrezzi.php
  63. 40
    0
      app/Models/PrenotazioneTavolo.php
  64. 86
    0
      app/Models/RigaFatturazione.php
  65. 73
    1
      app/Models/Saltacoda.php
  66. 2
    0
      app/Models/SaltacodaOrdine.php
  67. 10
    0
      app/Models/SaltacodaRigaOrdineHasVariante.php
  68. 10
    0
      app/Models/SlotTemporaliPrenotazioneTavolo.php
  69. 1
    0
      app/Models/User.php
  70. 99
    0
      app/Observers/AttivitaObserver.php
  71. 28
    0
      app/Observers/OrdineObserver.php
  72. 3
    0
      app/Providers/AppServiceProvider.php
  73. 66
    0
      app/Services/Asporto/AsportoService.php
  74. 24
    23
      app/Services/Bilancio/BilancioServizioService.php
  75. 73
    54
      app/Services/Carrello/CarrelloService.php
  76. 79
    0
      app/Services/Fattura/GeneraFatturaMensile.php
  77. 85
    0
      app/Services/Fatturazione/RigaFatturazioneService.php
  78. 96
    0
      app/Services/ListinoPrezzi/ListinoPubblicoPdfExporter.php
  79. 11
    0
      config/filesystems.php
  80. 1
    1
      database/migrations/2026_03_25_212234_modifiche_alle_tabelle.php
  81. 1
    1
      database/migrations/2026_06_18_164218_contabilizzare_metodo_di_pagamento.php
  82. 41
    0
      database/migrations/2026_06_25_131429_prenotazione_tavolo.php
  83. 38
    0
      database/migrations/2026_06_26_160233_slot_temporali_prenotazione_tavoli.php
  84. 28
    0
      database/migrations/2026_06_29_225649_deeplink_colonna.php
  85. 34
    0
      database/migrations/2026_07_09_145153_saltacoda_riga_ordine_has_variante.php
  86. 41
    0
      database/migrations/2026_07_09_181834_fatturazione.php
  87. 28
    0
      database/migrations/2026_07_12_175737_modifica_saltacoda.php
  88. 33
    0
      database/migrations/2026_07_18_160138_listino_prezzi.php
  89. 32
    0
      database/migrations/2026_07_19_175351_edit_fatturazione.php
  90. 122
    0
      database/migrations/2026_07_19_182854_edit_fatturazione_to_riga_fatturazione.php
  91. 29
    0
      database/migrations/2026_07_19_193750_edit_listino_prezzi.php
  92. 24
    0
      database/migrations/2026_07_20_224254_fatture_emesse.php
  93. 36
    0
      database/migrations/2026_07_24_154650_asporto.php
  94. 1
    0
      database/seeders/DatabaseSeeder.php
  95. 68
    0
      database/seeders/ListinoPrezziSeed.php
  96. BIN
      public/assets/img/cover_login.jpeg
  97. BIN
      public/assets/img/cover_login.png
  98. BIN
      public/assets/img/cover_login1.jpg
  99. BIN
      public/assets/img/logo_elephantech_icon.png
  100. 0
    0
      public/assets/img/logo_fest.png

+ 0
- 76
.env View File

@@ -1,76 +0,0 @@
1
-APP_NAME="SegrestaFest"
2
-# APP_ENV=production
3
-# APP_DEBUG=false
4
-APP_KEY=base64:LpyZj196ZCEQkXhkaTSZ4Dy1uIjO/KhcXBJ4cGUrYg4=
5
-APP_DEBUG=true
6
-APP_ENV=local
7
-#APP_URL=https://app.segrestafest.it
8
-APP_URL=http://fest.test
9
-#APP_URL=http://localhost:8000
10
-APP_LOCALE=it
11
-
12
-# REDMINE_URL=assistenza.elephantech.it
13
-# REDMINE_USER=segresta
14
-# REDMINE_PWD=8Z9qDUNnKQm8NVy
15
-# REDMINE_PROJECT=15
16
-
17
-LOG_CHANNEL=daily
18
-LOG_LEVEL=debug
19
-
20
-DB_CONNECTION=mysql
21
-DB_HOST=localhost
22
-DB_PORT=3306
23
-DB_DATABASE=segresta_fest_2025_project
24
-#DB_DATABASE=segresta_fest
25
-DB_USERNAME=root
26
-DB_PASSWORD=
27
-
28
-ADMIN_PASSWORD=fest2026
29
-
30
-
31
-BROADCAST_DRIVER=log
32
-CACHE_DRIVER=redis
33
-QUEUE_CONNECTION=redis
34
-
35
-SESSION_DRIVER=file
36
-SESSION_LIFETIME=120
37
-SESSION_SECURE=false
38
-SESSION_SECURE_COOKIE=false
39
-
40
-MEMCACHED_HOST=127.0.0.1
41
-
42
-REDIS_URL=localhost
43
-REDIS_HOST=localhost
44
-REDIS_PASSWORD=
45
-REDIS_PORT=6379
46
-REDIS_CLIENT=predis
47
-REDIS_DB=0
48
-
49
-# MAIL_MAILER=smtp
50
-# MAIL_HOST=vsmtp.segresta.it
51
-# MAIL_PORT=465
52
-# MAIL_USERNAME=futuranetwork@vsmtp.segresta.it
53
-# MAIL_PASSWORD=bdF01sEzPOHNYyKgX
54
-# MAIL_ENCRYPTION=ssl
55
-# MAIL_FROM_ADDRESS=futuranetwork@vsmtp.segresta.it
56
-# MAIL_FROM_NAME="Futura"
57
-# MAIL_REPLY_TO=info@elephantech.it
58
-
59
-# GOOGLE_CLIENT_ID=222725687752-rdues2edc7rabretqhv605olg8rmre2v.apps.googleusercontent.com
60
-# GOOGLE_CLIENT_SECRET=X6JdT4TAWHhSM8sPUU49wLUr
61
-# GOOGLE_REDIRECT=/auth/google/callback
62
-
63
-# FACEBOOK_APP_ID=573215073574741
64
-# FACEBOOK_APP_SECRET=008679dea9d7b630780b53413aca90d3
65
-# FACEBOOK_REDIRECT=/auth/facebook/callback
66
-
67
-# PAYPAL_MODE=sandbox
68
-# PAYPAL_CURRENCY=CHF
69
-
70
-
71
-FIREBASE_CREDENTIALS=storage/app/firebase-credentials.json
72
-FIREBASE_PROJECT_ID=fest-6f8db
73
-FIREBASE_WEB_API_KEY=AIzaSyD2qHjGcEkHpPJKtj6moHgWxOF9bgiPzo4
74
-FIREBASE_MESSAGING_SENDER_ID=1058806233312
75
-FIREBASE_APP_ID=1:1058806233312:web:ad9f595d3e47a1cec3b885
76
-FIREBASE_VAPID_KEY=BEKtGbo6MB5LqrjUpUyVs6ENo5XQ3lukvV8KqrTnJ5HU5wj_Hz-XQBh3mCAc-4kiCP6-7ZPjOxFWD30HSRdxkrU

+ 3
- 0
.env.example View File

@@ -63,3 +63,6 @@ AWS_BUCKET=
63 63
 AWS_USE_PATH_STYLE_ENDPOINT=false
64 64
 
65 65
 VITE_APP_NAME="${APP_NAME}"
66
+
67
+# Base URL raggiungibile da Chrome headless per export PDF listino pubblico
68
+LISTINO_PDF_BASE_URL=http://fest.test

+ 1
- 1
.gitignore View File

@@ -7,7 +7,7 @@
7 7
 /storage/pail
8 8
 /vendor
9 9
 
10
-# .env
10
+.env
11 11
 .env.backup
12 12
 .env.production
13 13
 .phpactor.json

+ 1
- 1
app/DataTables/AllergeneDataTable.php View File

@@ -54,7 +54,7 @@ class AllergeneDataTable extends DataTable
54 54
     public function html(): HtmlBuilder
55 55
     {
56 56
         $buttons = [];
57
-        if(Auth::user()->can('create-allergene')){
57
+        if(Auth::user()->hasRole('superadmin')){
58 58
             array_push($buttons, Button::make('create')
59 59
             ->editor('editor')
60 60
             ->formTitle('Crea nuovo allergene')

+ 2
- 2
app/DataTables/AttivitaDataTable.php View File

@@ -87,12 +87,12 @@ class AttivitaDataTable extends DataTable
87 87
                         Editor::make()
88 88
                             ->fields([
89 89
                                 Fields\Hidden::make('user_id')->default(Auth::user()->id),
90
+                                Fields\Boolean::make('is_attiva')->label('Attiva')->default(true),
90 91
                                 Fields\Text::make('nome')->label('Nome'),
91 92
                                 Fields\Text::make('descrizione')->label('Descrizione'),
92 93
                                 Fields\Image::make('path_logo')->label('Logo'),
93 94
                                 Fields\Image::make('path_icon')->label('Icona'),
94
-                                Fields\Image::make('path_image')->label('Immagine'),
95
-                                Fields\Boolean::make('is_attiva')->label('Attiva')->default(true),
95
+                                Fields\File::make('path_image')->label('Copertina'),
96 96
                             ])
97 97
                     )
98 98
                     ->initComplete("function(settings, json){

+ 58
- 29
app/DataTables/AttivitaDataTableEditor.php View File

@@ -8,14 +8,15 @@ use Illuminate\Validation\Rule;
8 8
 use Yajra\DataTables\DataTablesEditor;
9 9
 use Illuminate\Http\Request;
10 10
 use Illuminate\Validation\ValidationException;
11
-use Storage;
11
+use Illuminate\Support\Facades\Storage;
12 12
 use Illuminate\Support\Str;
13
+use Illuminate\Http\JsonResponse;
13 14
 
14 15
 class AttivitaDataTableEditor extends DataTablesEditor
15 16
 {
16 17
   protected $model = Attivita::class;
17
-  // protected $uploadDir = '';
18
-  // protected $disk = 'modelliRicevute';
18
+  protected string $uploadDir = '';
19
+  protected string $disk = 'copertinaAttivita';
19 20
 
20 21
   protected $messages = [
21 22
     'nome.required' => 'Il nome è richiesto',
@@ -97,35 +98,63 @@ class AttivitaDataTableEditor extends DataTablesEditor
97 98
 
98 99
   public function created(Model $model, array $data): Model
99 100
   {
100
-    $model->categorie_contabili()->create([
101
-      'nome' => 'Vendita',
102
-      'descrizione' => 'Vendita di prodotti e servizi',
103
-      'colore' => '#61db0f',
104
-    ]);
105
-    $model->categorie_contabili()->create([
106
-      'nome' => 'Acquisto',
107
-      'descrizione' => 'Acquisto di prodotti e servizi',
108
-      'colore' => '#dc3545',
109
-    ]);
110
-    $model->categorie_contabili()->create([
111
-      'nome' => 'Spese generali',
112
-      'descrizione' => 'Spese generali',
113
-      'colore' => '#dc3545',
114
-    ]);
115
-    $model->categorie_contabili()->create([
116
-      'nome' => 'Utenze',
117
-      'descrizione' => 'Spese per utenze (Luce, Acqua, Gas, Telefono)',
118
-      'colore' => '#dc3545',
119
-    ]);
120
-    $model->categorie_contabili()->create([
121
-      'nome' => 'Staff',
122
-      'descrizione' => 'Spese per staff',
123
-      'colore' => '#dc3545',
124
-    ]);
125
-
126 101
     return $model;
127 102
   }
128 103
 
104
+  public function upload(Request $request): JsonResponse
105
+  {
106
+    $this->action = 'upload';
107
+
108
+    $field   = $request->input('uploadField');
109
+    $storage = Storage::disk($this->disk);
110
+
111
+    try {
112
+      $rules      = $this->uploadRules();
113
+      $fieldRules = ['upload' => data_get($rules, $field, [])];
114
+
115
+      $this->validate($request, $fieldRules, $this->messages(), $this->attributes());
116
+
117
+      $uploadedFile = $request->file('upload');
118
+      $filename     = $this->getUploadedFilename($field, $uploadedFile);
119
+      $id           = $storage->putFileAs($request->id, $uploadedFile, $filename);
120
+
121
+      if (method_exists($this, 'uploaded')) {
122
+        $id = $this->uploaded($id);
123
+      }
124
+
125
+      return response()->json([
126
+        'action' => $this->action,
127
+        'data'   => [],
128
+        'files'  => [
129
+          'files' => [
130
+            $id => [
131
+              'filename'      => $id,
132
+              'original_name' => $uploadedFile->getClientOriginalName(),
133
+              'size'          => $uploadedFile->getSize(),
134
+              'directory'     => $request->id,
135
+              'disk'          => $this->disk,
136
+              'url'           => $storage->url($id),
137
+            ],
138
+          ],
139
+        ],
140
+        'upload' => [
141
+          'id' => $id,
142
+        ],
143
+      ]);
144
+    } catch (ValidationException $exception) {
145
+      return response()->json([
146
+        'action'      => $this->action,
147
+        'data'        => [],
148
+        'fieldErrors' => [
149
+          [
150
+            'name'   => $field,
151
+            'status' => str_replace('upload', $field, $exception->errors()['upload'][0]),
152
+          ],
153
+        ],
154
+      ]);
155
+    }
156
+  }
157
+
129 158
   public function messages(): array
130 159
   {
131 160
     return $this->messages;

+ 0
- 2
app/DataTables/ContenutoBachecaDataTableEditor.php View File

@@ -93,8 +93,6 @@ class ContenutoBachecaDataTableEditor extends DataTablesEditor
93 93
   }
94 94
 
95 95
   public function upload(Request $request): JsonResponse
96
-  
97
-  
98 96
   {
99 97
     $this->action = 'upload';
100 98
 

+ 1
- 1
app/DataTables/DispositivoDataTable.php View File

@@ -74,10 +74,10 @@ class DispositivoDataTable extends DataTable
74 74
                     ->editor(
75 75
                         Editor::make()
76 76
                             ->fields([
77
+                                Fields\Hidden::make('attivita_id')->label('Attività')->default($this->attivita_id),
77 78
                                 Fields\Hidden::make('endpoint_id')->label('Endpoint ID'),
78 79
                                 Fields\Text::make('nome')->label('Nome'),
79 80
                                 Fields\Select2::make('tipo')->label('Tipo')->options(Dispositivo::getTipoDispositivo()->pluck('value', 'label')),
80
-                                Fields\Select2::make('attivita_id')->label('Attività')->options(Attivita::where('is_attiva', true)->pluck('id', 'nome')),
81 81
                                 Fields\Text::make('licenza')->label('Licenza'),
82 82
                                 Fields\Text::make('url_stampante')->label('URL della stampante'),
83 83
                                 Fields\Text::make('ubicazione')->label('Ubicazione'),

+ 76
- 19
app/DataTables/EndpointDataTable.php View File

@@ -3,6 +3,7 @@
3 3
 namespace App\DataTables;
4 4
 
5 5
 use App\Models\Endpoint;
6
+use App\Models\Attivita;
6 7
 use Illuminate\Database\Eloquent\Builder as QueryBuilder;
7 8
 use Yajra\DataTables\EloquentDataTable;
8 9
 use Yajra\DataTables\Html\Builder as HtmlBuilder;
@@ -12,6 +13,8 @@ use Yajra\DataTables\Html\Editor\Editor;
12 13
 use Yajra\DataTables\Html\Editor\Fields;
13 14
 use Yajra\DataTables\Services\DataTable;
14 15
 use Illuminate\Support\Facades\Auth;
16
+use Illuminate\Support\Facades\Session;
17
+
15 18
 
16 19
 class EndpointDataTable extends DataTable
17 20
 {
@@ -38,11 +41,21 @@ class EndpointDataTable extends DataTable
38 41
                 return Endpoint::find($entity->id)->stampanti->pluck('nome')->implode(', ');
39 42
             })
40 43
             ->addColumn('product_key_display', function($entity){
41
-                return view('endpoint.nuovo_product_key', ['success' => true, 'endpoint' => $entity, 'datatable' => true]);
44
+                // return view('endpoint._partials.status', ['status' => $entity->status]);
45
+                return view('endpoint.nuovo_product_key', ['success' => true, 'endpoint' => $entity, 'datatable' => true, 'status' => Endpoint::getStati()[$entity->status]]);
42 46
             })
43 47
             ->addColumn('status_display', function($entity){
44 48
                 return view('endpoint._partials.status', ['status' => $entity->status]);
45 49
             })
50
+            ->addColumn('attivita_id_display', function($entity){
51
+                return Attivita::find($entity->attivita_id)->nome;
52
+            })
53
+            ->addColumn('status_local', function($entity){
54
+                return '';
55
+            })
56
+            ->addColumn('last_heartbeat', function($entity){
57
+                return $entity->last_heartbeat ? $entity->last_heartbeat->diffForHumans() : '';
58
+            })
46 59
             ->setRowId('id');
47 60
     }
48 61
 
@@ -53,7 +66,11 @@ class EndpointDataTable extends DataTable
53 66
      */
54 67
     public function query(Endpoint $model): QueryBuilder
55 68
     {
56
-        return $model->newQuery()->where('attivita_id', session()->get('attivita_attuale'));
69
+        if($this->attivita_id){
70
+            return $model->newQuery()->where('attivita_id', $this->attivita_id);
71
+        }else{
72
+            return $model->newQuery()->where('user_id', Auth::user()->id);
73
+        }
57 74
     }
58 75
 
59 76
     /**
@@ -68,20 +85,62 @@ class EndpointDataTable extends DataTable
68 85
             //     ->className('btn btn-sm btn-primary mb-4')
69 86
             //     ->formTitle('Crea nuovo endpoint')
70 87
             //     ->text('<i class="fas fa-plus"></i> Nuovo endpoint'));
88
+            if(Auth::user()->hasRole('superadmin')){
71 89
             array_push($buttons, Button::raw()
72 90
                 ->className('btn btn-sm btn-primary mb-4')
73 91
                 ->action('function(){
74 92
                     richiediNuovoProductKey();
75 93
                 }')
76 94
                 ->text('<i class="fas fa-plus"></i> Richiedi nuovo product key'));
95
+            }
96
+
97
+            array_push($buttons, Button::raw()
98
+                ->className('btn btn-sm btn-primary mb-4')
99
+                ->action('function(){
100
+                    collegaQuestoPc();
101
+                }')
102
+                ->text('<i class="fas fa-link"></i> Collega questo PC'));
77 103
         }
78 104
         if(Auth::user()->can('view-endpoint')){
79 105
             array_push($buttons, Button::raw()
80 106
                 ->className('btn btn-sm btn-primary mb-4')
81 107
                 ->action('istruzioni()')
82 108
                 ->text('<i class="fas fa-info-circle"></i> Istruzioni'));
83
-        }
84 109
 
110
+            array_push($buttons, Button::raw()
111
+                ->className('btn btn-sm btn-primary mb-4')
112
+                ->action('function(){
113
+                    getLocalFestAgentStatus(true);
114
+                }')
115
+                ->text('<i class="fas fa-sync"></i> Controlla stato locale FestAgent'));
116
+                
117
+            array_push($buttons, Button::raw()
118
+            ->className('btn btn-sm btn-primary mb-4')
119
+            ->action('function(){
120
+                downloadFestAgent();
121
+            }')
122
+            ->text('<i class="fas fa-sync"></i> Download FestAgent'));
123
+        };
124
+
125
+
126
+        $fields = [];
127
+        if($this->attivita_id){
128
+            $fields[] = Fields\Select2::make('attivita_id')->label('Attività associata')
129
+            ->default($this->attivita_id  ? $this->attivita_id : Session::get('attivita_attuale'))
130
+            ->options(Attivita::where('is_attiva', true)->where('user_id', Attivita::find($this->attivita_id)->user_id)->pluck('id', 'nome'));
131
+
132
+        }else{
133
+            $fields[] = Fields\Select2::make('attivita_id')->label('Attività associata')
134
+            ->default(Session::get('attivita_attuale'))
135
+            ->options(Attivita::where('is_attiva', true)->pluck('id', 'nome'));
136
+        };
137
+        $fields[] = Fields\Text::make('label')->label('Label');
138
+        $fields[] = Fields\Text::make('descrizione')->label('Descrizione');
139
+        $fields[] = Fields\Text::make('ubicazione')->label('Ubicazione');
140
+        if(Auth::user()->hasRole('superadmin')){
141
+        $fields[] = Fields\Text::make('url')->label('URL');
142
+        }
143
+        
85 144
         return $this->builder()
86 145
                     ->setTableId($this->dataTableVariable)
87 146
                     ->columns($this->getColumns())
@@ -93,19 +152,14 @@ class EndpointDataTable extends DataTable
93 152
                     ->responsive(true)
94 153
                     ->editor(
95 154
                         Editor::make()
96
-                            ->fields([
97
-                                Fields\Text::make('label')->label('Label'),
98
-                                Fields\Text::make('descrizione')->label('Descrizione'),
99
-                                Fields\Text::make('ubicazione')->label('Ubicazione'),
100
-                                Fields\Text::make('url')->label('URL'),
101
-                            ])
102
-                    )
103
-                    ->initComplete("function(settings, json){
104
-                        initComplete_endpoint();
105
-                    }")
106
-                    ->selectStyleSingle()
107
-                    ->buttons($buttons);
108
-    }
155
+                        ->fields($fields)
156
+                        )
157
+                            ->initComplete("function(settings, json){
158
+                                initComplete_endpoint();
159
+                                }")
160
+                                ->selectStyleSingle()
161
+                                ->buttons($buttons);
162
+                            }
109 163
 
110 164
     /**
111 165
      * Get the dataTable columns definition.
@@ -115,10 +169,12 @@ class EndpointDataTable extends DataTable
115 169
         return [
116 170
             Column::make('id')->title('ID')->addClass('text-center w-10')->responsivePriority(1),
117 171
             Column::make('status')->title('Status')->data('status_display'),
172
+            Column::make('attivita_id')->data('attivita_id_display')->title('Attività associata')->responsivePriority(1),
118 173
             Column::make('label')->title('Etichetta')->responsivePriority(1),
119 174
             Column::make('descrizione')->title('Descrizione'),
120 175
             Column::make('ubicazione')->title('Ubicazione'),
121
-            Column::make('stampanti_display')->title('Stampanti'),
176
+            Column::make('stampanti_count')->title('Stampanti'),
177
+            // Column::make('stampanti_display')->title('Stampanti'),
122 178
             Column::make('url')->title('URL')->visible(false),
123 179
             Column::make('user_id')->title('User ID')->visible(false),
124 180
             Column::make('product_key_display')->title('Product Key'),
@@ -126,12 +182,13 @@ class EndpointDataTable extends DataTable
126 182
             // Column::make('secret')->title('Secret'),
127 183
             // Column::make('type')->title('Type'),
128 184
             // Column::make('version')->title('Version'),
129
-            Column::make('licenza_id')->title('Licenza ID'),
130
-            Column::make('stampanti_count')->title('Stampanti'),
185
+            // Column::make('licenza_id')->title('Licenza ID'),
131 186
             // Column::make('last_heartbeat')->title('Last Heartbeat'),
132 187
             // Column::make('last_activity')->title('Last Activity'),
133 188
             // Column::make('created_at')->title('Created At'),
134 189
             // Column::make('updated_at')->title('Updated At'),
190
+            Column::computed('status_local')->title('Stato locale'),
191
+            Column::computed('last_heartbeat')->title('Ultimo heartbeat'),
135 192
             Column::computed('action')
136 193
                   ->title('')
137 194
                   ->exportable(false)

+ 232
- 0
app/DataTables/EndpointDataTableEditor.php View File

@@ -0,0 +1,232 @@
1
+<?php
2
+
3
+namespace App\DataTables;
4
+
5
+use App\Models\Endpoint;
6
+use Illuminate\Database\Eloquent\Model;
7
+use Illuminate\Http\JsonResponse;
8
+use Illuminate\Http\UploadedFile;
9
+use Yajra\DataTables\DataTablesEditor;
10
+use Illuminate\Http\Request;
11
+use Illuminate\Validation\ValidationException;
12
+use Illuminate\Support\Facades\Storage;
13
+use Illuminate\Support\Str;
14
+
15
+class EndpointDataTableEditor extends DataTablesEditor
16
+{
17
+  protected $model = Endpoint::class;
18
+
19
+  /** Disco per le locandine (storage/app/public/eventi/...) */
20
+  protected string $disk = 'endpoint';
21
+
22
+  protected string $uploadDir = ''; //'eventi';
23
+
24
+  protected $messages = [
25
+    'nome.required' => 'Il nome è richiesto',
26
+    'attivita_id.required' => 'La attività è richiesta',
27
+    'label.required' => 'Il label è richiesto',
28
+    'descrizione.required' => 'La descrizione è richiesta',
29
+    'ubicazione.required' => 'L\'ubicazione è richiesta',
30
+  ];
31
+
32
+  /**
33
+  * Get create action validation rules.
34
+  *
35
+  * @return array
36
+  */
37
+  public function createRules(): array
38
+  {
39
+    return [
40
+      'attivita_id'  => 'required',
41
+      'label' => 'required',
42
+      'descrizione' => 'nullable',
43
+      'ubicazione' => 'nullable',
44
+    ];
45
+  }
46
+
47
+  public function createMessages(): array{
48
+    return $this->messages;
49
+  }
50
+
51
+  /**
52
+  * Get edit action validation rules.
53
+  *
54
+  * @param Model $model
55
+  * @return array
56
+  */
57
+  public function editRules(Model $model): array
58
+  {
59
+    return [
60
+      'attivita_id'  => 'required',
61
+      'label' => 'required',
62
+      'descrizione' => 'nullable',
63
+      'ubicazione' => 'nullable',
64
+    ];
65
+  }
66
+
67
+  public function editMessages(): array{
68
+    return $this->messages;
69
+  }
70
+
71
+  /**
72
+  * Get remove action validation rules.
73
+  *
74
+  * @param Model $model
75
+  * @return array
76
+  */
77
+  public function removeRules(Model $model): array
78
+  {
79
+    return [];
80
+  }
81
+
82
+  public function creating(Model $model, array $data): array
83
+  {
84
+    // $model->roles()->sync([$data['ruolo_id']]);
85
+    return $data;
86
+  }
87
+
88
+  public function updating(Model $model, array $data): array
89
+  {
90
+    // dd($data['ruolo']);
91
+    // $model->roles()->sync([$data['ruolo_id']]);
92
+    return $data;
93
+  }
94
+
95
+  /**
96
+   * Dopo il salvataggio: se la locandina era in tmp (nuovo evento), spostala sotto eventi/{id}.
97
+   */
98
+  public function saved(Model $model, array $data): Model
99
+  {
100
+    if (! $model instanceof Evento) {
101
+      return $model;
102
+    }
103
+
104
+    $path = $model->immagine;
105
+    if (! is_string($path) || $path === '') {
106
+      return $model;
107
+    }
108
+
109
+    $tmpPrefix = $this->uploadDir; //$this->uploadDir.'/tmp/';
110
+    if (! str_starts_with($path, $tmpPrefix)) {
111
+      return $model;
112
+    }
113
+
114
+    $storage = Storage::disk($this->disk);
115
+    if (! $storage->exists($path)) {
116
+      return $model;
117
+    }
118
+
119
+    $basename = basename($path);
120
+    $destDir = $this->uploadDir.'/'.$model->getKey();
121
+    $destPath = $destDir.'/'.$basename;
122
+
123
+    $storage->makeDirectory($destDir);
124
+    if ($storage->exists($destPath)) {
125
+      $storage->delete($destPath);
126
+    }
127
+    $storage->move($path, $destPath);
128
+
129
+    $model->immagine = $destPath;
130
+    $model->saveQuietly();
131
+
132
+    return $model;
133
+  }
134
+
135
+  public function messages(): array
136
+  {
137
+    return $this->messages;
138
+  }
139
+
140
+  /**
141
+   * Regole validazione upload (campo Editor: immagine).
142
+   */
143
+  public function uploadRules(): array
144
+  {
145
+    return [
146
+      'immagine' => ['required', 'file', 'image', 'mimes:jpeg,png,jpg,gif,webp', 'max:5120'],
147
+    ];
148
+  }
149
+
150
+  public function upload(Request $request): JsonResponse
151
+  {
152
+    $this->action = 'upload';
153
+
154
+    $field = $request->input('uploadField');
155
+    if (! $field || ! is_string($field)) {
156
+      $field = 'file';
157
+    }
158
+
159
+    try {
160
+      $storage = Storage::disk($this->disk);
161
+      $rules = $this->uploadRules();
162
+      $fieldRules = ['upload' => $rules[$field] ?? []];
163
+
164
+      $this->validate($request, $fieldRules, $this->messages(), $this->attributes());
165
+
166
+      /** @var UploadedFile $uploadedFile */
167
+      $uploadedFile = $request->file('upload');
168
+      $directory = $this->resolveUploadDirectory($request);
169
+      $filename = $this->getUploadedFilename($field, $uploadedFile);
170
+      $id = $storage->putFileAs($directory, $uploadedFile, $filename);
171
+
172
+      if (! is_string($id)) {
173
+        throw ValidationException::withMessages([
174
+          'upload' => 'Impossibile salvare il file caricato.',
175
+        ]);
176
+      }
177
+
178
+      $id = $this->uploaded($id);
179
+
180
+      return response()->json([
181
+        'action' => $this->action,
182
+        'data' => [],
183
+        'files' => [
184
+          'files' => [
185
+            $id => [
186
+              'filename' => $id,
187
+              'original_name' => $uploadedFile->getClientOriginalName(),
188
+              'size' => $uploadedFile->getSize(),
189
+              'directory' => $directory,
190
+              'disk' => $this->disk,
191
+              'url' => $storage->url($id),
192
+            ],
193
+          ],
194
+        ],
195
+        'upload' => [
196
+          'id' => $id,
197
+        ],
198
+      ]);
199
+    } catch (ValidationException $exception) {
200
+      return response()->json([
201
+        'action' => $this->action,
202
+        'data' => [],
203
+        'fieldErrors' => [
204
+          [
205
+            'name' => $field,
206
+            'status' => str_replace('upload', $field, (string) ($exception->errors()['upload'][0] ?? 'Errore di validazione')),
207
+          ],
208
+        ],
209
+      ]);
210
+    }
211
+  }
212
+
213
+  /**
214
+   * Sottocartella per riga (edit) o temporanea per nuovo evento.
215
+   */
216
+  protected function resolveUploadDirectory(Request $request): string
217
+  {
218
+    $id = $request->input('id');
219
+    if ($id !== null && $id !== '' && is_numeric($id)) {
220
+      return $this->uploadDir.'/'.(int) $id;
221
+    }
222
+
223
+    return $this->uploadDir; //$this->uploadDir.'/tmp';
224
+  }
225
+
226
+  protected function getUploadedFilename(string $field, UploadedFile $uploadedFile): string
227
+  {
228
+    $ext = $uploadedFile->getClientOriginalExtension();
229
+
230
+    return $field.'_'.time().'_'.Str::random(4).($ext !== '' ? '.'.$ext : '');
231
+  }
232
+}

+ 128
- 0
app/DataTables/FatturaDataTable.php View File

@@ -0,0 +1,128 @@
1
+<?php
2
+
3
+namespace App\DataTables;
4
+
5
+use App\Models\Fattura;
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 FatturaDataTable extends DataTable
17
+{
18
+    public function __construct()
19
+    {
20
+        $this->dataTableVariable = 'dataTable_fattura';
21
+    }
22
+    /**
23
+     * Build the DataTable class.
24
+     *
25
+     * @param QueryBuilder<Fattura> $query Results from query() method.
26
+     */
27
+    public function dataTable(QueryBuilder $query): EloquentDataTable
28
+    {
29
+        return (new EloquentDataTable($query))
30
+            ->addColumn('action', function ($entity) {
31
+                return view('fattura.menu', ['entity' => $entity]);
32
+            })
33
+            ->addColumn('data_fattura', function ($entity) {
34
+                return $entity->data_fattura->format('d/m/Y');
35
+            })
36
+            ->addColumn('totale_imponibile', function ($entity) {
37
+                return '€ '.number_format($entity->totale_imponibile, 2, ',', '.');
38
+            })
39
+            ->addColumn('totale_netto', function ($entity) {
40
+                return '€ '.number_format($entity->totale_netto, 2, ',', '.');
41
+            })
42
+            ->addColumn('stato', function ($entity) {
43
+                return view('fattura.stato', ['entity' => $entity]);
44
+            })
45
+            ->setRowId('id');
46
+    }
47
+
48
+    /**
49
+     * Get the query source of dataTable.
50
+     *
51
+     * @return QueryBuilder<Fattura>
52
+     */
53
+    public function query(Fattura $model): QueryBuilder
54
+    {
55
+        return $model->newQuery();
56
+    }
57
+
58
+    /**
59
+     * Optional method if you want to use the html builder.
60
+     */
61
+    public function html(): HtmlBuilder
62
+    {
63
+        $buttons = [];
64
+        if(Auth::user()->can('create-fattura') || Auth::user()->hasRole('superadmin')){
65
+            array_push($buttons, Button::make('create')
66
+                ->text('<i class="bx bx-plus"></i> Crea fattura')
67
+                ->className('btn btn-primary')
68
+                ->editor('editor')
69
+                ->formTitle('Crea nuova fattura'));
70
+
71
+                array_push($buttons, Button::raw('')
72
+                ->text('<i class="bx bx-plus"></i> Genera fattura mensile')
73
+                ->className('btn btn-primary')
74
+                ->action('function () {
75
+                    window.location.href = "'.route("fattura.need_fatturazione_mensile").'";
76
+                }')
77
+            );
78
+        }
79
+        return $this->builder()
80
+                    ->setTableId($this->dataTableVariable)
81
+                    ->columns($this->getColumns())
82
+                    ->minifiedAjax()
83
+                    ->orderBy(1)
84
+                    ->pageLength(25)
85
+                    ->lengthMenu([25, 50, 100, 200])
86
+                    ->responsive(true)
87
+                    ->language(asset('assets/Italian.json'))
88
+                    ->dom('Bfrtipl')
89
+                    ->editor(Editor::make())
90
+                    ->selectStyleSingle()
91
+                    ->buttons($buttons)
92
+                    ->initComplete('function ($query) {
93
+                        initComplete_fattura();
94
+                    }');
95
+    }
96
+
97
+    /**
98
+     * Get the dataTable columns definition.
99
+     */
100
+    public function getColumns(): array
101
+    {
102
+        return [
103
+            Column::make('id'),
104
+            Column::make('numero_fattura'),
105
+            Column::make('data_fattura'),
106
+            Column::make('stato'),
107
+            Column::make('note'),
108
+            Column::make('path_pdf'),
109
+            Column::make('path_xml'),
110
+            Column::make('metadata'),
111
+            Column::make('totale_imponibile'),
112
+            Column::make('totale_netto'),
113
+            Column::make('action')
114
+                  ->exportable(false)
115
+                  ->printable(false)
116
+                  ->width(60)
117
+                  ->addClass('text-center'),
118
+        ];
119
+    }
120
+
121
+    /**
122
+     * Get the filename for export.
123
+     */
124
+    protected function filename(): string
125
+    {
126
+        return 'Fattura_' . date('YmdHis');
127
+    }
128
+}

+ 108
- 0
app/DataTables/FatturaDataTableEditor.php View File

@@ -0,0 +1,108 @@
1
+<?php
2
+
3
+namespace App\DataTables;
4
+
5
+use App\Models\Fattura;
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 FatturaDataTableEditor extends DataTablesEditor
15
+{
16
+  protected $model = Fattura::class;
17
+  // protected $uploadDir = '';
18
+  // protected $disk = 'modelliRicevute';
19
+
20
+  protected $messages = [
21
+    'numero_fattura.required' => 'Il numero della fattura è richiesto',
22
+    'data_fattura.required' => 'La data della fattura è richiesta',
23
+    'stato.required' => 'Lo stato della fattura è richiesto',
24
+    'note.required' => 'La nota della fattura è richiesta',
25
+    'path_pdf.required' => 'Il percorso del PDF della fattura è richiesto',
26
+    'path_xml.required' => 'Il percorso del XML della fattura è richiesto',
27
+    'metadata.required' => 'I metadati della fattura sono richiesti',
28
+    'totale_imponibile.required' => 'Il totale imponibile della fattura è richiesto',
29
+    'totale_netto.required' => 'Il totale netto della fattura è richiesto',
30
+  ];
31
+
32
+  /**
33
+  * Get create action validation rules.
34
+  *
35
+  * @return array
36
+  */
37
+  public function createRules(): array
38
+  {
39
+    return [
40
+      'numero_fattura'  => 'required',
41
+      'data_fattura' => 'required',
42
+      'stato' => 'required',
43
+      'note' => 'nullable',
44
+      // 'path_pdf' => 'required',
45
+      // 'path_xml' => 'required',
46
+      // 'metadata' => 'required',
47
+      // 'totale_imponibile' => 'required',
48
+      // 'totale_netto' => 'required',
49
+    ];
50
+  }
51
+
52
+  public function createMessages(): array{
53
+    return $this->messages;
54
+  }
55
+
56
+  /**
57
+  * Get edit action validation rules.
58
+  *
59
+  * @param Model $model
60
+  * @return array
61
+  */
62
+  public function editRules(Model $model): array
63
+  {
64
+    return [
65
+      'numero_fattura'  => 'required',
66
+      'email' => 'nullable|email',
67
+      'note' => 'nullable',
68
+      // 'path_pdf' => 'required',
69
+      // 'path_xml' => 'required',
70
+      // 'metadata' => 'required',
71
+      // 'totale_imponibile' => 'required',
72
+      // 'totale_netto' => 'required',
73
+    ];
74
+  }
75
+
76
+  public function editMessages(): array{
77
+    return $this->messages;
78
+  }
79
+
80
+  /**
81
+  * Get remove action validation rules.
82
+  *
83
+  * @param Model $model
84
+  * @return array
85
+  */
86
+  public function removeRules(Model $model): array
87
+  {
88
+    return [];
89
+  }
90
+
91
+  public function creating(Model $model, array $data): array
92
+  {
93
+    // $model->roles()->sync([$data['ruolo_id']]);
94
+    return $data;
95
+  }
96
+
97
+  public function updating(Model $model, array $data): array
98
+  {
99
+    // dd($data['ruolo']);
100
+    // $model->roles()->sync([$data['ruolo_id']]);
101
+    return $data;
102
+  }
103
+  public function messages(): array
104
+  {
105
+    return $this->messages;
106
+  }
107
+
108
+}

+ 36
- 12
app/DataTables/ImpostazioniDataTable.php View File

@@ -29,6 +29,23 @@ class ImpostazioniDataTable extends DataTable
29 29
             ->addColumn('action', function($entity){
30 30
                 return view('impostazioni.menu', ['entity' => $entity]);
31 31
             })
32
+            ->editColumn('valore', function($entity){
33
+                if($entity->tipo == 'array'){
34
+                    $giorni = \App\Models\Asporto::getGiorniDisponibilitaAsporto();
35
+                    $items = array_filter(array_map('trim', explode(',', (string) $entity->valore)));
36
+                    $nomi = [];
37
+                    foreach($items as $item){
38
+                        if($giorni->has($item)) {
39
+                            $nomi[] = $giorni->get($item);
40
+                        }
41
+                    }
42
+                    return implode(', ', $nomi);
43
+               
44
+                }else if($entity->tipo == 'boolean'){
45
+                    return $entity->valore == 1 || $entity->valore === 1 || $entity->valore === true || $entity->valore === 'true' ? 'Attivo' : 'Disattivo';
46
+                }
47
+                return $entity->valore;
48
+            })
32 49
             ->setRowId('id');
33 50
     }
34 51
 
@@ -40,9 +57,9 @@ class ImpostazioniDataTable extends DataTable
40 57
     public function query(Impostazioni $model): QueryBuilder
41 58
     {
42 59
         if($this->attivita_id){
43
-            return $model->newQuery()->where('attivita_id', $this->attivita_id);
60
+            return $model->newQuery()->where('attivita_id', $this->attivita_id)->orderBy('gruppo', 'asc');
44 61
         }else{
45
-            return $model->newQuery()->whereRaw('1 = 0');
62
+            return $model->newQuery()->whereRaw('1 = 0')->orderBy('gruppo', 'asc');
46 63
         }
47 64
     }
48 65
 
@@ -62,13 +79,14 @@ class ImpostazioniDataTable extends DataTable
62 79
 
63 80
         $editorFields = [
64 81
             Fields\Hidden::make('attivita_id')->default($this->attivita_id),
65
-            Fields\Text::make('label')->label('Label'),
66
-            Fields\Text::make('valore')->label('Valore'),
67
-            Fields\Text::make('descrizione')->label('Descrizione'),
68
-            Fields\Text::make('opzioni')->label('Opzioni'),
69
-        ];
70
-        
71
-        if(Auth::user()->hasRole('superadmin')){
82
+            Fields\Text::make('valore')->label('Valore')->fieldInfo('Rispetta il formato del tipo di dato'),
83
+            ];
84
+            
85
+            if(Auth::user()->hasRole('superadmin')){
86
+            $editorFields[] =Fields\Text::make('label')->label('Label');
87
+            $editorFields[] =Fields\Text::make('descrizione')->label('Descrizione');
88
+            $editorFields[] =Fields\Text::make('opzioni')->label('Opzioni');
89
+            $editorFields[] =Fields\Text::make('gruppo')->label('Gruppo');
72 90
             $editorFields[] = Fields\Text::make('chiave')->label('Chiave');
73 91
             $editorFields[] = Fields\Select::make('tipo')->label('Tipo')->options(Impostazioni::getTipi());
74 92
         }
@@ -77,6 +95,8 @@ class ImpostazioniDataTable extends DataTable
77 95
                     ->setTableId($this->dataTableVariable)
78 96
                     ->columns($this->getColumns())
79 97
                     ->minifiedAjax()
98
+                    ->pageLength(20)
99
+                    ->responsive(true)
80 100
                     ->orderBy(1)
81 101
                     ->selectStyleSingle()
82 102
                     ->buttons($buttons)
@@ -87,7 +107,10 @@ class ImpostazioniDataTable extends DataTable
87 107
                     )
88 108
                     ->initComplete("function(settings, json){
89 109
                         initComplete_impostazioni();
90
-                    }") ;
110
+                    }")
111
+                    ->drawCallback("function(settings){
112
+                        drawCallback_impostazioni(settings);
113
+                    }");
91 114
     }
92 115
 
93 116
     /**
@@ -97,6 +120,7 @@ class ImpostazioniDataTable extends DataTable
97 120
     {
98 121
         $columns = [
99 122
             Column::make('id')->visible(false),
123
+            Column::make('gruppo')->visible(false),
100 124
         ];
101 125
 
102 126
         if(Auth::user()->hasRole('superadmin')){
@@ -105,8 +129,8 @@ class ImpostazioniDataTable extends DataTable
105 129
         }
106 130
 
107 131
         $columns[] = Column::make('label');
108
-        $columns[] = Column::make('valore')->title('Valore')->visible(false);
109
-        $columns[] = Column::make('descrizione')->title('Descrizione');
132
+        $columns[] = Column::make('valore')->title('Valore')->width('30%');
133
+        $columns[] = Column::make('descrizione')->title('Descrizione')->width('30%');
110 134
         $columns[] = Column::make('opzioni')->title('Opzioni')->visible(false);
111 135
         $columns[] = Column::computed('action')
112 136
                   ->title('')

+ 133
- 0
app/DataTables/ImpostazioniDataTableEditor.php View File

@@ -0,0 +1,133 @@
1
+<?php
2
+
3
+namespace App\DataTables;
4
+
5
+use App\Models\Impostazioni;
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 ImpostazioniDataTableEditor extends DataTablesEditor
15
+{
16
+  protected $model = Impostazioni::class;
17
+  // protected $uploadDir = '';
18
+  // protected $disk = 'modelliRicevute';
19
+
20
+  protected $messages = [
21
+    'valore.required' => 'Il valore è richiesto',
22
+    'valore.formato' => 'Il valore non rispetta il formato del tipo di dato',
23
+    'valore.date_format' => 'Il valore non rispetta il formato della data HH:mm',
24
+    'valore.time_format' => 'Il valore non rispetta il formato dell\'ora HH:mm',
25
+    'valore.email' => 'Il valore non è una email valida',
26
+    'valore.boolean' => 'Il valore non è valido: scrivi true o false',
27
+    'valore.numeric' => 'Il valore non è un numero valido',
28
+    'valore.date' => 'Il valore non è una data valida (YYYY-MM-DD)',
29
+    'valore.time' => 'Il valore non è un\'ora valida (HH:mm)',
30
+    'valore.integer' => 'Il valore non è un intero valido',
31
+    'valore.float' => 'Il valore non è un float valido',
32
+    'valore.array' => 'Il valore non è un array valido.',
33
+    'valore.min' => 'Numeri della settimana da 1 a 7',
34
+    'valore.max' => 'Numeri della settimana da 1 a 7',
35
+  ];
36
+
37
+  /**
38
+  * Get create action validation rules.
39
+  *
40
+  * @return array
41
+  */
42
+  public function createRules(): array
43
+  {
44
+    return [
45
+      'valore'  => 'required',
46
+      'valore.formato' => 'required',
47
+    ];
48
+  }
49
+
50
+  public function createMessages(): array{
51
+    return $this->messages;
52
+  }
53
+
54
+  /**
55
+  * Get edit action validation rules.
56
+  *
57
+  * @param Model $model
58
+  * @return array
59
+  */
60
+  public function editRules(Model $model): array
61
+  {
62
+    $rules = [
63
+      'valore' => ['required'],
64
+    ];
65
+
66
+    switch ($model->tipo ?? null) {
67
+      case 'integer':
68
+        $rules['valore'][] = 'numeric';
69
+        break;
70
+      case 'boolean':
71
+        $rules['valore'][] = 'in:0,1,true,false';
72
+        break;
73
+      case 'date':
74
+        $rules['valore'][] = 'date';
75
+        break;
76
+      case 'email':
77
+        $rules['valore'][] = 'email';
78
+        break;
79
+      case 'time':
80
+        $rules['valore'][] = 'date_format:H:i';
81
+        break;
82
+      case 'float':
83
+        $rules['valore'][] = 'numeric';
84
+        break;
85
+      case 'array':
86
+        // $rules['valore'][] = 'array';
87
+        // $rules['valore'][] = 'min:1';
88
+        // $rules['valore'][] = 'max:7';
89
+        $rules['valore'][] = 'regex:/^[1-7,]+$/';
90
+        break;
91
+      // Aggiungi altri casi per altri tipi personalizzati se necessario
92
+      default:
93
+        // per 'text', 'textarea', ecc., solo required
94
+        break;
95
+    }
96
+
97
+    return $rules;
98
+  }
99
+
100
+  public function editMessages(): array{
101
+    return $this->messages;
102
+  }
103
+
104
+  /**
105
+  * Get remove action validation rules.
106
+  *
107
+  * @param Model $model
108
+  * @return array
109
+  */
110
+  public function removeRules(Model $model): array
111
+  {
112
+    return [];
113
+  }
114
+
115
+  public function creating(Model $model, array $data): array
116
+  {
117
+    // $model->roles()->sync([$data['ruolo_id']]);
118
+    return $data;
119
+  }
120
+
121
+  public function updating(Model $model, array $data): array
122
+  {
123
+    // $data['valore'] = $data['valore'] == 'true' ? true : false;
124
+    // dd($data['ruolo']);
125
+    // $model->roles()->sync([$data['ruolo_id']]);
126
+    return $data;
127
+  }
128
+  public function messages(): array
129
+  {
130
+    return $this->messages;
131
+  }
132
+
133
+}

+ 133
- 0
app/DataTables/ListinoPrezziDataTable.php View File

@@ -0,0 +1,133 @@
1
+<?php
2
+
3
+namespace App\DataTables;
4
+
5
+use App\Models\ListinoPrezzi;
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 ListinoPrezziDataTable extends DataTable
17
+{
18
+    public function __construct()
19
+    {
20
+        $this->dataTableVariable = 'dataTable_listino_prezzi';
21
+    }
22
+    /**
23
+     * Build the DataTable class.
24
+     *
25
+     * @param QueryBuilder<ListinoPrezzi> $query Results from query() method.
26
+     */
27
+    public function dataTable(QueryBuilder $query): EloquentDataTable
28
+    {
29
+        return (new EloquentDataTable($query))
30
+            ->addColumn('action', function($entity){
31
+                return view('listino_prezzi.menu');
32
+            })
33
+            ->addColumn('tipo_dispositivo_display', function($entity){
34
+                return view('listino_prezzi._partials.badge' , ['tipo_dispositivo' => $entity->tipo_dispositivo]);
35
+            })
36
+            ->addColumn('tipo_prezzo_display', function($entity){
37
+                return view('listino_prezzi._partials.badge' , ['tipo_prezzo' => $entity->tipo_prezzo]);
38
+            })
39
+            ->addColumn('prezzo_display', function($entity){
40
+                return '€ '.number_format($entity->prezzo, 2, ',', '.');
41
+            })
42
+            ->setRowId('id');
43
+    }
44
+
45
+    /**
46
+     * Get the query source of dataTable.
47
+     *
48
+     * @return QueryBuilder<ListinoPrezzi>
49
+     */
50
+    public function query(ListinoPrezzi $model): QueryBuilder
51
+    {
52
+        return $model->newQuery();
53
+    }
54
+
55
+    /**
56
+     * Optional method if you want to use the html builder.
57
+     */
58
+    public function html(): HtmlBuilder
59
+    {
60
+
61
+    $buttons = [];
62
+    if(Auth::user()->hasRole('superadmin')){
63
+        array_push($buttons,
64
+         Button::make('create')
65
+         ->text('Crea voce')
66
+         ->className('btn btn-primary')
67
+         ->editor('editor')
68
+         ->formTitle('Crea nuova voce del listino prezzo')
69
+         ->formButtons([
70
+            Button::raw('Annulla')
71
+            ->className('btn btn-secondary ml-2')
72
+            ->actionClose(),
73
+            Button::raw('Salva')
74
+            ->className('btn btn-success ml-2')
75
+            ->actionHandler('create')
76
+         ])
77
+
78
+         );
79
+    }
80
+
81
+        return $this->builder()
82
+                    ->setTableId($this->dataTableVariable)
83
+                    ->columns($this->getColumns())
84
+                    ->minifiedAjax()
85
+                    ->language(asset('assets/Italian.json'))
86
+                    ->orderBy(1)
87
+                    ->dom('Bfrtip')
88
+                    ->editor(Editor::make()
89
+                    ->fields([
90
+                        Fields\Text::make('label')->label('Label'),
91
+                        Fields\Text::make('descrizione')->label('Descrizione'),
92
+                        Fields\Select2::make('tipo_dispositivo')->label('Tipo Dispositivo')->options(\App\Models\Dispositivo::getTipoDispositivo()->pluck('value','label')),
93
+                        Fields\Number::make('prezzo')->label('Prezzo'),
94
+                        Fields\Select2::make('tipo_prezzo')->label('Tipo Prezzo')->options(ListinoPrezzi::getTipiPrezzo()->pluck('value','label')),
95
+                        Fields\Text::make('note')->label('Note'),
96
+                    ]))
97
+                    ->selectStyleSingle()
98
+                    ->responsive(true)
99
+                    ->initComplete('function(){
100
+                        initComplete_listino_prezzi();
101
+                    }')
102
+                    ->buttons($buttons);
103
+    }
104
+
105
+    /**
106
+     * Get the dataTable columns definition.
107
+     */
108
+    public function getColumns(): array
109
+    {
110
+        return [
111
+            Column::make('id'),
112
+            Column::make('label')->title('Nome'),
113
+            Column::make('descrizione'),
114
+            Column::make('tipo_dispositivo')->data('tipo_dispositivo_display'),
115
+            Column::make('prezzo')->data('prezzo_display'),
116
+            Column::make('tipo_prezzo')->data('tipo_prezzo_display'),
117
+            Column::make('note'),
118
+            Column::computed('action')
119
+                  ->exportable(false)
120
+                  ->printable(false)
121
+                  ->width(60)
122
+                  ->addClass('text-center'),
123
+        ];
124
+    }
125
+
126
+    /**
127
+     * Get the filename for export.
128
+     */
129
+    protected function filename(): string
130
+    {
131
+        return 'ListinoPrezzi_' . date('YmdHis');
132
+    }
133
+}

+ 104
- 0
app/DataTables/ListinoPrezziDataTableEditor.php View File

@@ -0,0 +1,104 @@
1
+<?php
2
+
3
+namespace App\DataTables;
4
+
5
+use App\Models\ListinoPrezzi;
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
+
12
+use Illuminate\Support\Facades\Auth;
13
+
14
+class  ListinoPrezziDataTableEditor extends DataTablesEditor
15
+{
16
+  protected $model = ListinoPrezzi::class;
17
+  // protected $uploadDir = '';
18
+  // protected $disk = 'modelliRicevute';
19
+
20
+  protected $messages = [
21
+    'label.required' => 'Il label è richiesto',
22
+    'descrizione.required' => 'La descrizione è richiesta',
23
+    'tipo_dispositivo.required' => 'Il tipo di dispositivo è richiesto',
24
+    'prezzo.required' => 'Il prezzo è richiesto',
25
+    'tipo_prezzo.required' => 'Il tipo di prezzo è richiesto',
26
+    'note.required' => 'La nota è richiesta',
27
+  ];
28
+
29
+  /**
30
+  * Get create action validation rules.
31
+  *
32
+  * @return array
33
+  */
34
+  public function createRules(): array
35
+  {
36
+    if(!Auth::user()->hasRole('superadmin')){
37
+      return 403;
38
+    }
39
+    return [
40
+      'label'  => 'required|string|max:255',
41
+      'descrizione' => 'nullable|string|max:255',
42
+      'tipo_dispositivo' => 'required',
43
+      'prezzo' => 'required|numeric |min:0',
44
+      'tipo_prezzo' => 'required|string|max:255',
45
+      'note' => 'nullable|string|max:255',
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
+    if(!Auth::user()->hasRole('superadmin')){
62
+      return 403;
63
+    }
64
+    return [
65
+      'label'  => 'required|string|max:255',
66
+      'descrizione' => 'nullable|string|max:255',
67
+      'tipo_dispositivo' => 'nullable',
68
+      'prezzo' => 'required|numeric |min:0',
69
+      'tipo_prezzo' => 'required|string|max:255',
70
+      'note' => 'nullable|string|max:255',
71
+    ];
72
+  }
73
+
74
+  public function editMessages(): array{
75
+    return $this->messages;
76
+  }
77
+
78
+  /**
79
+  * Get remove action validation rules.
80
+  *
81
+  * @param Model $model
82
+  * @return array
83
+  */
84
+  public function removeRules(Model $model): array
85
+  {
86
+    return [];
87
+  }
88
+
89
+  public function creating(Model $model, array $data): array
90
+  {
91
+    return $data;
92
+  }
93
+
94
+  public function updating(Model $model, array $data): array
95
+  {
96
+    return $data;
97
+  }
98
+
99
+  public function messages(): array
100
+  {
101
+    return $this->messages;
102
+  }
103
+
104
+}

+ 7
- 3
app/DataTables/OrdineDataTable.php View File

@@ -60,6 +60,9 @@ class OrdineDataTable extends DataTable
60 60
                 $tavolo = $infoArr['tavolo'] ?? 'ND';
61 61
                 return 'Cliente: ' . $cliente . ' - Tavolo: ' . $tavolo;
62 62
             })
63
+            ->addColumn('created_at_display', function($entity){
64
+                return $entity->created_at->format('d/m/Y H:i')." (".$entity->created_at->diffForHumans().")";
65
+            })
63 66
             ->setRowId('id');
64 67
     }
65 68
 
@@ -137,13 +140,14 @@ class OrdineDataTable extends DataTable
137 140
     public function getColumns(): array
138 141
     {
139 142
         return [
140
-            Column::make('id')->title('ID')->addClass('text-center w-10')->responsivePriority(1)->width('10%'),
141
-            Column::make('stato')->data('stato_display')->title('Stato')->responsivePriority(1),
143
+            Column::make('id')->title('ID')->addClass('text-center w-10')->responsivePriority(1)->width('8%'),
144
+            Column::make('stato')->data('stato_display')->title('Stato')->responsivePriority(1)->width('5%'),
142 145
             Column::make('riferimento')->title('Riferimento')->responsivePriority(2),
143
-            Column::make('prenotazione_id')->data('prenotazione_id_display')->title('Prenotazione')->responsivePriority(2),
146
+            // Column::make('prenotazione_id')->data('prenotazione_id_display')->title('Prenotazione')->responsivePriority(2),
144 147
             Column::make('prezzo')->title('Totale')->data('prezzo_display')->responsivePriority(2),
145 148
             Column::make('note')->title('Note')->responsivePriority(2),
146 149
             Column::make('info')->title('Info')->data('info_display')->responsivePriority(2),
150
+            Column::make('created_at_display')->title('Creato il')->responsivePriority(2),
147 151
             Column::computed('action')
148 152
                   ->title('')
149 153
                   ->exportable(false)

+ 138
- 0
app/DataTables/PrenotazioneTavoloDataTable.php View File

@@ -0,0 +1,138 @@
1
+<?php
2
+
3
+namespace App\DataTables;
4
+
5
+use App\Models\PrenotazioneTavolo;
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 PrenotazioneTavoloDataTable extends DataTable
17
+{
18
+    public function __construct()
19
+    {
20
+        $this->dataTableVariable = 'dataTable_prenotazione_tavolo';
21
+    }
22
+    /**
23
+     * Build the DataTable class.
24
+     *
25
+     * @param QueryBuilder<PrenotazioneTavolo> $query Results from query() method.
26
+     */
27
+    public function dataTable(QueryBuilder $query): EloquentDataTable
28
+    {
29
+        return (new EloquentDataTable($query))
30
+            ->addColumn('action', function($entity){
31
+                return view('prenotazione_tavolo.menu', ['entity' => $entity]);
32
+            })
33
+            ->addColumn('stato_display', function($entity){
34
+                return view('prenotazione_tavolo._partials.stato_prenotazione', ['stato' => $entity->stato]);
35
+            })
36
+            ->setRowId('id');
37
+    }
38
+
39
+    /**
40
+     * Get the query source of dataTable.
41
+     *
42
+     * @return QueryBuilder<PrenotazioneTavolo>
43
+     */
44
+    public function query(PrenotazioneTavolo $model): QueryBuilder
45
+    {
46
+        if(isset($this->attivita_id)){
47
+            return $model->newQuery()->where('attivita_id', $this->attivita_id);
48
+
49
+            if(isset($this->oggi) && $this->oggi == true){
50
+                return $query->whereDate('data_prenotazione', Carbon::today());
51
+            }
52
+        }
53
+        return $model->newQuery()->whereRaw('1 = 0');
54
+    }
55
+
56
+    /**
57
+     * Optional method if you want to use the html builder.
58
+     */
59
+    public function html(): HtmlBuilder
60
+    {
61
+        $buttons = [];
62
+        if(Auth::user()->can('create-prenotazione_tavolo')){
63
+            array_push($buttons, Button::make('create')
64
+                ->editor('editor')
65
+                ->formTitle('Crea nuova prenotazione')
66
+                ->className('btn btn-sm btn-primary mb-4')
67
+                ->text('<i class="fas fa-plus"></i> Nuova prenotazione'));
68
+        }
69
+
70
+        if(isset($this->oggi) && $this->oggi == true){
71
+            array_push($buttons, Button::raw('Appello prenotazioni')
72
+                ->className('btn btn-sm btn-primary mb-4')
73
+                ->action('appelloPrenotazioniTavoli()')
74
+                ->text('<i class="bx bx-notification"></i> Appello prenotazioni'));
75
+        }
76
+        return $this->builder()
77
+                    ->setTableId($this->dataTableVariable)
78
+                    ->columns($this->getColumns())
79
+                    ->minifiedAjax()
80
+                    ->ajax(route('prenotazione-tavolo.store', ['attivita_id' => $this->attivita_id]))
81
+                    ->language(asset('assets/Italian.json'))
82
+                    ->dom(count($buttons) == 0 ? 'rtip' : 'Bfrtip')
83
+                    ->buttons($buttons)
84
+                    ->orderBy(1)
85
+                    ->selectStyleSingle()
86
+                    ->responsive(true)
87
+                    ->editor(
88
+                        Editor::make()
89
+                            ->fields([
90
+                                Fields\Hidden::make('attivita_id')->label('Attività')->default($this->attivita_id),
91
+                                Fields\Text::make('nome')->label('Nome'),
92
+                                Fields\Text::make('email')->label('Email'),
93
+                                Fields\Text::make('telefono')->label('Telefono'),
94
+                                Fields\Select2::make('tavolo_id')->label('Tavolo')->options(\App\Models\Tavolo::where('attivita_id', $this->attivita_id)->where('is_attivo', true)->pluck('label', 'id')),
95
+                                Fields\Date::make('data_prenotazione')->label('Data prenotazione')->default(date('Y-m-d')),
96
+                                Fields\Time::make('ora_prenotazione')->label('Ora prenotazione')->default(date('H:i')),
97
+                                Fields\Text::make('note')->label('Note'),
98
+                                Fields\Select2::make('stato')->label('Stato')->options(PrenotazioneTavolo::getStati()->pluck('value', 'label'))->default(PrenotazioneTavolo::STATO_INVIATA),
99
+                            ])
100
+                    )
101
+                    ->initComplete("function(settings, json){
102
+                        initComplete_prenotazione_tavolo();
103
+                    }");
104
+    }
105
+
106
+    /**
107
+     * Get the dataTable columns definition.
108
+     */
109
+    public function getColumns(): array
110
+    {
111
+        return [
112
+            Column::make('id')->visible(false)->responsivePriority(1),
113
+            Column::make('stato')->data('stato_display')->title('Stato')->searchable(true)->filter(true)->responsivePriority(1),
114
+            Column::make('nome')->title('Nome'),
115
+            Column::make('email')->title('Email'),
116
+            Column::make('telefono')->title('Telefono'),
117
+            Column::make('tavolo_id')->title('Tavolo'),
118
+            Column::make('data_prenotazione')->title('Data prenotazione'),
119
+            Column::make('ora_prenotazione')->title('Ora prenotazione'),
120
+            Column::make('note')->title('Note'),
121
+            Column::computed('action')
122
+                  ->title('')
123
+                  ->exportable(false)
124
+                  ->printable(false)
125
+                  ->width(60)
126
+                  ->addClass('text-center')
127
+                  ->responsivePriority(1),
128
+        ];
129
+    }
130
+
131
+    /**
132
+     * Get the filename for export.
133
+     */
134
+    protected function filename(): string
135
+    {
136
+        return 'PrenotazioneTavolo_' . date('YmdHis');
137
+    }
138
+}

+ 108
- 0
app/DataTables/PrenotazioneTavoloDataTableEditor.php View File

@@ -0,0 +1,108 @@
1
+<?php
2
+
3
+namespace App\DataTables;
4
+
5
+use App\Models\PrenotazioneTavolo;
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 PrenotazioneTavoloDataTableEditor extends DataTablesEditor
15
+{
16
+  protected $model = PrenotazioneTavolo::class;
17
+  // protected $uploadDir = '';
18
+  // protected $disk = 'modelliRicevute';
19
+
20
+  protected $messages = [
21
+    'nome.required' => 'Il nome è richiesto',
22
+    'email.required' => 'L\'email è richiesta',
23
+    'telefono.required' => 'Il telefono è richiesto',
24
+    'tavolo_id.required' => 'Il tavolo è richiesto',
25
+    'data_prenotazione.required' => 'La data prenotazione è richiesta',
26
+    'ora_prenotazione.required' => 'L\'ora prenotazione è richiesta',
27
+    'note.required' => 'La note è richiesta',
28
+    'stato.required' => 'Il stato è richiesto',
29
+  ];
30
+
31
+  /**
32
+  * Get create action validation rules.
33
+  *
34
+  * @return array
35
+  */
36
+  public function createRules(): array
37
+  {
38
+    return [
39
+      'attivita_id' => 'required|exists:attivita,id',
40
+      'nome' => 'required',
41
+      'email' => 'required|email',
42
+      'telefono' => 'nullable',
43
+      'tavolo_id' => 'nullable|exists:tavolo,id',
44
+      'data_prenotazione' => 'required|date',
45
+      'ora_prenotazione' => 'required',
46
+      'note' => 'nullable',
47
+      'stato' => 'required|string|in:'.implode(',', PrenotazioneTavolo::getStati()->pluck('value', 'label')->toArray()),
48
+    ];
49
+  }
50
+
51
+  public function createMessages(): array{
52
+    return $this->messages;
53
+  }
54
+
55
+  /**
56
+  * Get edit action validation rules.
57
+  *
58
+  * @param Model $model
59
+  * @return array
60
+  */
61
+  public function editRules(Model $model): array
62
+  {
63
+    return [
64
+      'attivita_id' => 'required|exists:attivita,id',
65
+      'nome' => 'required',
66
+      'email' => 'required|email',
67
+      'telefono' => 'nullable',
68
+      'tavolo_id' => 'nullable|exists:tavolo,id',
69
+      'data_prenotazione' => 'required|date',
70
+      'ora_prenotazione' => 'required',
71
+      'note' => 'nullable',
72
+      'stato' => 'required|string|in:'.implode(',', PrenotazioneTavolo::getStati()->pluck('value', 'label')->toArray()),
73
+    ];
74
+  }
75
+
76
+  public function editMessages(): array{
77
+    return $this->messages;
78
+  }
79
+
80
+  /**
81
+  * Get remove action validation rules.
82
+  *
83
+  * @param Model $model
84
+  * @return array
85
+  */
86
+  public function removeRules(Model $model): array
87
+  {
88
+    return [];
89
+  }
90
+
91
+  public function creating(Model $model, array $data): array
92
+  {
93
+    // $model->roles()->sync([$data['ruolo_id']]);
94
+    return $data;
95
+  }
96
+
97
+  public function updating(Model $model, array $data): array
98
+  {
99
+    // dd($data['ruolo']);
100
+    // $model->roles()->sync([$data['ruolo_id']]);
101
+    return $data;
102
+  }
103
+  public function messages(): array
104
+  {
105
+    return $this->messages;
106
+  }
107
+
108
+}

+ 22
- 12
app/DataTables/PuntoVenditaDataTable.php View File

@@ -85,7 +85,7 @@ class PuntoVenditaDataTable extends DataTable
85 85
     {
86 86
         return $model->newQuery()
87 87
         ->whereIn('tipo', [Dispositivo::KIOSK, Dispositivo::CASSA , Dispositivo::CAMERIERE])
88
-        ->where('attivita_id', session('attivita_attuale'));
88
+        ->where('attivita_id', $this->attivita_id);
89 89
     }
90 90
 
91 91
     /**
@@ -99,6 +99,16 @@ class PuntoVenditaDataTable extends DataTable
99 99
                 ->editor('editor')
100 100
                 ->className('btn btn-sm btn-primary mb-4')
101 101
                 ->formTitle('Crea nuovo punto di vendita')
102
+                ->formButtons([
103
+                    Button::raw('cancel')
104
+                    ->text('Annulla')
105
+                    ->className('btn btn-secondary')
106
+                    ->actionHandler('cancel'),
107
+                    Button::raw('create')
108
+                    ->text('Crea')
109
+                    ->className('btn btn-primary mx-2')
110
+                    ->actionHandler('create'),
111
+                ])
102 112
                 ->text('<i class="fas fa-plus"></i> Nuovo punto di vendita'));
103 113
         }
104 114
         return $this->builder()
@@ -113,19 +123,19 @@ class PuntoVenditaDataTable extends DataTable
113 123
                     ->editor(
114 124
                         Editor::make()
115 125
                             ->fields([
126
+                                Fields\Hidden::make('attivita_id')->label('Attività')->default($this->attivita_id),
116 127
                                 Fields\Text::make('nome')->label('Nome'),
117 128
                                 Fields\Select::make('tipo')->label('Tipo')
118 129
                                 ->options(Dispositivo::getTipoPuntiVendita()),
119
-                                Fields\Select2::make('attivita_id')->label('Attività')->options(Attivita::where('is_attiva', true)->pluck('id', 'nome')),
120
-                                Fields\Text::make('licenza')->label('Licenza'),
121
-                                Fields\Select2::make('url_stampante')->label('URL della stampante')
130
+                                Fields\Boolean::make('is_attivo')->label('Attivo')->default(true),
131
+                                // Fields\Text::make('licenza')->label('Licenza'),
132
+                                Fields\Select2::make('url_stampante')->label('Stampante')
122 133
                                 ->options(Dispositivo::select2_stampante()),
123 134
                                 Fields\Text::make('ubicazione')->label('Ubicazione'),
124 135
                                 Fields\Text::make('note')->label('Note'),
125
-                                Fields\Boolean::make('is_attivo')->label('Attivo')->default(true),
126
-                                Fields\Text::make('pin_sblocco')->label('Pin di sblocco'),
127
-                                Fields\DateTime::make('data_apertura_dispositivo')->label('Data di apertura del dispositivo'),
128
-                                Fields\DateTime::make('data_chiusura_dispositivo')->label('Data di chiusura del dispositivo'),
136
+                                // Fields\Text::make('pin_sblocco')->label('Pin di sblocco'),
137
+                                // Fields\DateTime::make('data_apertura_dispositivo')->label('Data di apertura del dispositivo'),
138
+                                // Fields\DateTime::make('data_chiusura_dispositivo')->label('Data di chiusura del dispositivo'),
129 139
                             ])
130 140
                     )
131 141
                     ->initComplete("function(settings, json){
@@ -143,14 +153,14 @@ class PuntoVenditaDataTable extends DataTable
143 153
             Column::make('id')->visible(false),
144 154
             Column::make('is_attivo')->data('is_attivo_display')->title('Disponibile')->responsivePriority(1)->width('10%'),
145 155
             Column::make('nome')->title('Nome')->responsivePriority(1),
146
-            Column::make('attivita_id')->title('Attività')->responsivePriority(2),
156
+            Column::make('attivita_id')->title('Attività')->responsivePriority(2)->visible(false),
147 157
             Column::make('tipo')->title('Tipo')->visible(true)->responsivePriority(2),
148 158
             // Column::computed('punto_vendita_tipo_display')->title('Tipo'),
149
-            Column::make('licenza')->title('Licenza')->responsivePriority(2),
150
-            Column::make('url_stampante')->title('URL della stampante')->responsivePriority(2),
159
+            // Column::make('licenza')->title('Licenza')->responsivePriority(2),
160
+            Column::make('url_stampante')->title('Stampante')->responsivePriority(2),
151 161
             Column::make('ubicazione')->title('Ubicazione')->responsivePriority(2),
152 162
             // Column::make('note')->title('Note'),
153
-            Column::make('pin_sblocco')->title('Pin di sblocco')->responsivePriority(2),
163
+            // Column::make('pin_sblocco')->title('Pin di sblocco')->responsivePriority(2),
154 164
             Column::computed('abbinato_display')->title('Abbinato')->responsivePriority(2),
155 165
             // Column::make('data_apertura_dispositivo')->title('Data di apertura del dispositivo'),
156 166
             // Column::make('data_chiusura_dispositivo')->title('Data di chiusura del dispositivo'),

+ 187
- 0
app/DataTables/RigaFatturazioneDataTable.php View File

@@ -0,0 +1,187 @@
1
+<?php
2
+
3
+namespace App\DataTables;
4
+
5
+use App\Models\RigaFatturazione;
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 RigaFatturazioneDataTable extends DataTable
17
+{
18
+    public function __construct()
19
+    {
20
+        $this->dataTableVariable = 'dataTable_riga_fatturazione';
21
+    }
22
+    /**
23
+     * Build the DataTable class.
24
+     *
25
+     * @param QueryBuilder<Fatturazione> $query Results from query() method.
26
+     */
27
+    public function dataTable(QueryBuilder $query): EloquentDataTable
28
+    {
29
+        return (new EloquentDataTable($query))
30
+            ->addColumn('action', function ($entity) {
31
+                return view('fatturazione.menu',['entity' => $entity]);
32
+            })
33
+            ->addColumn('attivita_id', function ($entity) {
34
+                return $entity->attivita->nome??'ND';
35
+            })
36
+            ->addColumn('dispositivo_id', function ($entity) {
37
+                return $entity->dispositivo->nome??'ND';
38
+            })
39
+            ->addColumn('prezzo', function ($entity) {
40
+                return number_format($entity->prezzo, 2, ',', '.') . ' €';
41
+            })
42
+            ->addColumn('stato_display', function ($entity) {
43
+                return view('fatturazione.stato',['entity' => $entity]);
44
+            })
45
+            ->addColumn('listino_prezzi_display', function ($entity) {
46
+                return optional($entity->listinoPrezzi)->label;
47
+            })
48
+            ->rawColumns(['note'])
49
+            ->setRowId('id');
50
+    }
51
+
52
+    /**
53
+     * Get the query source of dataTable.
54
+     *
55
+     * @return QueryBuilder<Fatturazione>
56
+     */
57
+    public function query(RigaFatturazione $model): QueryBuilder
58
+    {
59
+        $query = $model->newQuery()->orderBy('created_at', 'desc');
60
+
61
+        if(!$this->tutti_i_consumi){
62
+            $query->whereIn('stato', [RigaFatturazione::GRATUITO, RigaFatturazione::REGISTRATO]);
63
+        }
64
+        if($this->superadmin){
65
+            return $query;
66
+            }
67
+            
68
+            if($this->user_id){
69
+                $query->where('user_id', $this->user_id);
70
+            }
71
+            if($this->attivita_id){
72
+                $query->where('attivita_id', $this->attivita_id);
73
+            }
74
+
75
+        return $query;
76
+    }
77
+
78
+    /**
79
+     * Optional method if you want to use the html builder.
80
+     */
81
+    public function html(): HtmlBuilder
82
+    {
83
+        $buttons = [];
84
+
85
+            if(Auth::user()->hasRole('amministratore')){
86
+                if(!$this->tutti_i_consumi){
87
+                array_push($buttons,
88
+                Button::raw('')
89
+                ->addClass('btn-primary')
90
+                ->text('<i class="bx bx-abacus me-1"></i> Mostra tutti i consumi')
91
+                ->className('btn btn-primary btn-sm')
92
+                ->action('mostra_tutti_i_consumi(true)')
93
+                );
94
+            }else{
95
+                array_push($buttons,
96
+                Button::raw('')
97
+                ->addClass('btn-primary')
98
+                ->text('<i class="bx bx-abacus me-1"></i> Mostra solo consumi non fatturati')
99
+                ->className('btn btn-primary btn-sm')
100
+                ->action('mostra_tutti_i_consumi(false)')
101
+                );
102
+            }
103
+
104
+        }
105
+
106
+        if(Auth::user()->hasRole('superadmin')){
107
+            array_push($buttons,
108
+            Button::make('create')
109
+            ->addClass('btn-primary')
110
+            ->text('<i class="bx bx-plus"></i> Consumo')
111
+            ->className('btn btn-primary')
112
+            ->editor('editor')
113
+            ->formTitle('Aggiungi consumo')
114
+            );
115
+
116
+        }
117
+
118
+        return $this->builder()
119
+                    ->setTableId($this->dataTableVariable)
120
+                    ->columns($this->getColumns())
121
+                    ->dom('PBfrtip')
122
+                    ->language(asset('assets/Italian.json'))
123
+                    ->minifiedAjax()
124
+                    ->responsive(true)
125
+                    ->orderBy(1)
126
+                    ->searchPanes([
127
+                        'layout' => 'columns-3',
128
+                        'cascadePanes' => true,
129
+                    ])
130
+                    ->editor(
131
+                        Editor::make()->fields([
132
+                            Fields\Select2::make('listino_prezzi_id')->label('Listino prezzi')->options(\App\Models\ListinoPrezzi::all()->pluck('id','descrizione')),
133
+                            Fields\Select2::make('attivita_id')->label('Attività')->options(\App\Models\Attivita::all()->pluck('id','nome')),
134
+                            Fields\Select2::make('dispositivo_id')->label('Dispositivo')->options(\App\Models\Dispositivo::all()->pluck('id','nome')),
135
+                            // Fields\Number::make('prezzo')->label('Prezzo'),
136
+                            Fields\Number::make('qta')->label('Qta'),
137
+                            // Fields\Text::make('tipo')->label('Tipo'),
138
+                        ])
139
+                    )
140
+                    ->selectStyleSingle()
141
+                    ->buttons($buttons)
142
+                    ->initComplete('function ($query) {
143
+                        initComplete_riga_fatturazione();
144
+                    }');
145
+    }
146
+
147
+    /**
148
+     * Get the dataTable columns definition.
149
+     */
150
+    public function getColumns(): array
151
+    {
152
+        return [
153
+            Column::make('id'),
154
+            Column::make('fattura_id')->title('Fattura'),
155
+            Column::make('attivita_id')->title('Attività')->searchable(true)->searchPanes(['show' => true]),
156
+            Column::make('dispositivo_id')->title('Dispositivo')->searchPanes(['show' => true]),
157
+            Column::make('tipo')->title('Tipo'),
158
+            Column::make('qta')->title('Qta'),
159
+            Column::make('ordini')->title('Ordini'),
160
+            Column::make('listino_prezzi_id')->title('Listino prezzi')->data('listino_prezzi_display'),
161
+            Column::make('prezzo'),
162
+            Column::make('stato')->data('stato_display'),
163
+            Column::make('note'),
164
+        ];
165
+
166
+        if(Auth::user()->hasRole('superadmin')){
167
+            array_push($columns,
168
+            Column::make('action')
169
+                  ->title('')
170
+                  ->addClass('text-center')
171
+                  ->exportable(false)
172
+                  ->printable(false)
173
+                  ->width(60)
174
+                  ->addClass('text-center'),
175
+            );
176
+        }
177
+        return $columns;
178
+    }
179
+
180
+    /**
181
+     * Get the filename for export.
182
+     */
183
+    protected function filename(): string
184
+    {
185
+        return 'Fatturazione_' . date('YmdHis');
186
+    }
187
+}

+ 100
- 0
app/DataTables/RigaFatturazioneDataTableEditor.php View File

@@ -0,0 +1,100 @@
1
+<?php
2
+
3
+namespace App\DataTables;
4
+
5
+use App\Models\RigaFatturazione;
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 RigaFatturazioneDataTableEditor extends DataTablesEditor
15
+{
16
+  protected $model = RigaFatturazione::class;
17
+  // protected $uploadDir = '';
18
+  // protected $disk = 'modelliRicevute';
19
+
20
+  protected $messages = [
21
+    'attivita_id.required' => 'L\'attività è richiesta',
22
+    'qta.required' => 'La quantità è richiesta',
23
+    'prezzo.required' => 'Il prezzo è richiesto',
24
+    'tipo.required' => 'Il tipo è richiesto',
25
+    'listino_prezzi_id.required' => 'Il listino prezzi è richiesto',
26
+  ];
27
+
28
+  /**
29
+  * Get create action validation rules.
30
+  *
31
+  * @return array
32
+  */
33
+  public function createRules(): array
34
+  {
35
+    return [
36
+      'attivita_id'  => 'required',
37
+      'qta' => 'required',
38
+      // 'prezzo' => 'required',
39
+      // 'tipo' => 'required',
40
+      'listino_prezzi_id' => 'required',
41
+    ];
42
+  }
43
+
44
+  public function createMessages(): array{
45
+    return $this->messages;
46
+  }
47
+
48
+  /**
49
+  * Get edit action validation rules.
50
+  *
51
+  * @param Model $model
52
+  * @return array
53
+  */
54
+  public function editRules(Model $model): array
55
+  {
56
+    return [
57
+      'attivita_id'  => 'required',
58
+      'qta' => 'required',
59
+      // 'prezzo' => 'required',
60
+      // 'tipo' => 'required',
61
+      'listino_prezzi_id' => 'required',
62
+    ];
63
+  }
64
+
65
+  public function editMessages(): array{
66
+    return $this->messages;
67
+  }
68
+
69
+  /**
70
+  * Get remove action validation rules.
71
+  *
72
+  * @param Model $model
73
+  * @return array
74
+  */
75
+  public function removeRules(Model $model): array
76
+  {
77
+    return [];
78
+  }
79
+
80
+  public function creating(Model $model, array $data): array
81
+  {
82
+    // $model->roles()->sync([$data['ruolo_id']]);
83
+   $listinoPrezzi = \App\Models\ListinoPrezzi::find($data['listino_prezzi_id']);
84
+    $data['prezzo'] = $listinoPrezzi->prezzo;
85
+    $data['tipo'] = $listinoPrezzi->categoria;
86
+    return $data;
87
+  }
88
+
89
+  public function updating(Model $model, array $data): array
90
+  {
91
+    // dd($data['ruolo']);
92
+    // $model->roles()->sync([$data['ruolo_id']]);
93
+    return $data;
94
+  }
95
+  public function messages(): array
96
+  {
97
+    return $this->messages;
98
+  }
99
+
100
+}

+ 3
- 17
app/DataTables/RigaOrdineDataTable.php View File

@@ -76,7 +76,7 @@ class RigaOrdineDataTable extends DataTable
76 76
                 return view('punto_vendita.cassa._partials.carrello.item', ['riga_ordine' => $query, 'piatto_id' => $query->piatto_id , 'riga_ordine_id' => $query->id]);
77 77
             })
78 78
             ->setRowId('id')
79
-            ->rawColumns(['carrello_item']);
79
+            ->rawColumns(['carrello_item', 'vista_catalogo']);
80 80
     }
81 81
 
82 82
     /**
@@ -136,12 +136,7 @@ class RigaOrdineDataTable extends DataTable
136 136
      */
137 137
     public function getColumns(): array
138 138
     {
139
-        return [
140
-            Column::make('id')->visible(false),
141
-            Column::make('carrello_item')->title('')->searchable(true)->data('carrello_item')->addClass('text-start col-5 col-sm-5 col-md-5 col-lg-5 col-xl-5 col-xxl-5'),
142
-        ];
143
- 
144
-        if($this->vista == 'catalogo'){
139
+        if ($this->vista === 'catalogo') {
145 140
             return [
146 141
                 Column::make('id')->visible(false),
147 142
                 Column::make('vista_catalogo')->title('')->searchable(true)->data('vista_catalogo')->addClass('text-start col-5 col-sm-5 col-md-5 col-lg-5 col-xl-5 col-xxl-5'),
@@ -150,16 +145,7 @@ class RigaOrdineDataTable extends DataTable
150 145
 
151 146
         return [
152 147
             Column::make('id')->visible(false),
153
-            // Column::computed('riga_ordine')->title('')->searchable(true),
154
-            Column::make('piatto')->title('Piatto')->searchable(true)->data('piatto_display')->addClass('text-start col-5 col-sm-5 col-md-5 col-lg-5 col-xl-5 col-xxl-5'),
155
-            // Column::make('diminuisci')->title('')->addClass('text-center')->width('10%'),
156
-            Column::make('quantita')->title('Quantità')->width('10%')->attributes(['class' => 'text-center w-25 w-50 w-md-25 w-sm-50'])->addClass('text-center')->data('quantita_display'),
157
-            // Column::make('aumenta')->title('')->addClass('text-center')->width('10%'),
158
-            Column::make('prezzo')->title('Prezzo')->data('prezzo_display')->addClass('text-center'),
159
-            // Column::make('note')->title('Note')->addClass('text-center')->width('10%')->visible(false),
160
-            // Column::make('elimina')->title('')->addClass('text-center')->width('10%'),
161
-            // Column::make('colonna_action')->title('')->addClass('text-center')->width('10%')->data('colonna_action'),
162
-            Column::computed('menu')->title('')->addClass('text-center details-control')->width('10%')->data('menu_display'),
148
+            Column::make('carrello_item')->title('')->searchable(true)->data('carrello_item')->addClass('text-start col-5 col-sm-5 col-md-5 col-lg-5 col-xl-5 col-xxl-5'),
163 149
         ];
164 150
     }
165 151
 

+ 33
- 15
app/DataTables/StampanteDataTable.php View File

@@ -68,7 +68,7 @@ class StampanteDataTable extends DataTable
68 68
     public function html(): HtmlBuilder
69 69
     {
70 70
         $buttons = [];
71
-        if(Auth::user()->can('create-stampante')){
71
+        if(Auth::user()->hasRole('superadmin')){
72 72
             array_push($buttons, Button::make('create')
73 73
                 ->editor('editor')
74 74
                 ->className('btn btn-sm btn-primary mb-4')
@@ -76,10 +76,40 @@ class StampanteDataTable extends DataTable
76 76
                 ->text('<i class="fas fa-plus"></i> Nuova stampante'));
77 77
         }
78 78
 
79
-        if(Auth::user()->can('edit-stampante')){
79
+        if(Auth::user()->hasRole('superadmin')){
80 80
             array_push($buttons, Button::raw('<button class="btn btn-sm btn-warning mb-4" onclick="api_updateStampanti()">Aggiorna stampanti (API)</button>'));
81 81
         }
82 82
 
83
+        //editor superadmin
84
+        if(Auth::user()->hasRole('superadmin')){
85
+            $editorFields = [
86
+                Fields\Text::make('nome')->label('Nome'),
87
+                Fields\Hidden::make('tipo')->label('Tipo')->default(Dispositivo::STAMPANTE),
88
+                Fields\Select2::make('attivita_id')->label('Attività')->options(Attivita::where('is_attiva', true)->pluck('id', 'nome')),
89
+                Fields\Text::make('licenza')->label('Licenza'),
90
+                Fields\Text::make('url_stampante')->label('URL della stampante'),
91
+                Fields\Text::make('ubicazione')->label('Ubicazione'),
92
+                Fields\Text::make('note')->label('Note'),
93
+                Fields\Boolean::make('is_attivo')->label('Attivo')->default(true),
94
+                Fields\Text::make('pin_sblocco')->label('Pin di sblocco'),
95
+                Fields\DateTime::make('data_apertura_dispositivo')->label('Data di apertura del dispositivo'),
96
+                Fields\DateTime::make('data_chiusura_dispositivo')->label('Data di chiusura del dispositivo'),
97
+            ];
98
+        }
99
+
100
+        //editor amministratore
101
+        if(Auth::user()->hasRole('amministratore')){
102
+            $editorFields = [
103
+                Fields\Text::make('nome')->label('Nome'),
104
+                Fields\Boolean::make('is_attivo')->label('Attivo')->default(true),
105
+                Fields\Hidden::make('tipo')->label('Tipo')->default(Dispositivo::STAMPANTE),
106
+                Fields\Select2::make('attivita_id')->label('Attività')->default($this->attivita_id),
107
+                Fields\Text::make('ubicazione')->label('Ubicazione'),
108
+                Fields\Text::make('note')->label('Note'),
109
+
110
+            ];
111
+        }
112
+
83 113
         return $this->builder()
84 114
                     ->setTableId($this->dataTableVariable)
85 115
                     ->columns($this->getColumns())
@@ -90,19 +120,7 @@ class StampanteDataTable extends DataTable
90 120
                     ->orderBy(1)
91 121
                     ->editor(
92 122
                         Editor::make()
93
-                            ->fields([
94
-                                Fields\Text::make('nome')->label('Nome'),
95
-                                Fields\Hidden::make('tipo')->label('Tipo')->default(Dispositivo::STAMPANTE),
96
-                                Fields\Select2::make('attivita_id')->label('Attività')->options(Attivita::where('is_attiva', true)->pluck('id', 'nome')),
97
-                                Fields\Text::make('licenza')->label('Licenza'),
98
-                                Fields\Text::make('url_stampante')->label('URL della stampante'),
99
-                                Fields\Text::make('ubicazione')->label('Ubicazione'),
100
-                                Fields\Text::make('note')->label('Note'),
101
-                                Fields\Boolean::make('is_attivo')->label('Attivo')->default(true),
102
-                                Fields\Text::make('pin_sblocco')->label('Pin di sblocco'),
103
-                                Fields\DateTime::make('data_apertura_dispositivo')->label('Data di apertura del dispositivo'),
104
-                                Fields\DateTime::make('data_chiusura_dispositivo')->label('Data di chiusura del dispositivo'),
105
-                            ])
123
+                            ->fields($editorFields)
106 124
                     )
107 125
                     ->initComplete("function(settings, json){
108 126
                         initComplete_stampante();

+ 9
- 4
app/DataTables/TavoloDataTable.php View File

@@ -56,11 +56,16 @@ class TavoloDataTable extends DataTable
56 56
     {
57 57
         $buttons = [];
58 58
         if(Auth::user()->can('create-tavolo')){
59
-            array_push($buttons, Button::make('create')
60
-                ->editor('editor')
61
-                ->formTitle('Crea nuovo 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
+                array_push($buttons, Button::raw('Aggiungi tavoli')
62 66
                 ->className('btn btn-sm btn-primary mb-4')
63
-                ->text('<i class="fas fa-plus"></i> Nuovo tavolo'));
67
+                ->action('aggiungiTavoli()')
68
+                ->text('<i class="fas fa-plus"></i> Aggiungi tavoli'));
64 69
         }
65 70
 
66 71
         return $this->builder()

+ 4
- 3
app/DataTables/VariantePiattoDataTable.php View File

@@ -93,10 +93,11 @@ class VariantePiattoDataTable extends DataTable
93 93
                         ->ajax(route('variante-piatto.store'))
94 94
                         ->fields([
95 95
                             Fields\Hidden::make('attivita_id')->label('Attività')->default($this->attivita_id),
96
-                            Fields\Text::make('prezzo')->label('Prezzo'),
97 96
                             Fields\Text::make('label')->label('Nome'),
98
-                            Fields\Select2::make('piatto_id')->label('Piatto')->options(Piatto::where('attivita_id', $this->attivita_id)->pluck('id', 'nome')),
99
-                            Fields\Select2::make('cucina_id')->label('Cucina')->options(Cucina::where('attivita_id', $this->attivita_id)->pluck('id', 'nome')),
97
+                            Fields\Text::make('prezzo')->label('Prezzo'),
98
+                            Fields\Select2::make('piatto_id')->label('Associa al Piatto')->options(Piatto::where('attivita_id', $this->attivita_id)->pluck('id', 'nome')),
99
+                            Fields\Select2::make('cucina_id')->label('Associa alla Cucina')->options(Cucina::where('attivita_id', $this->attivita_id)->pluck('id', 'nome'))->default($this->cucina_id),
100
+                            Fields\Checkbox::make('is_attivo')->label('Attiva')->default(true),
100 101
                         ])
101 102
                     )
102 103
                     ->language(asset('assets/Italian.json'))

+ 6
- 5
app/DataTables/VariantePiattoDataTableEditor.php View File

@@ -86,16 +86,17 @@ class VariantePiattoDataTableEditor extends DataTablesEditor
86 86
 
87 87
   public function creating(Model $model, array $data): array
88 88
   {
89
-    // $model->roles()->sync([$data['ruolo_id']]);
90
-    $data['is_attivo'] = isset($data['is_attivo']) ? true : false;
89
+    $data['is_attivo'] = array_key_exists('is_attivo', $data)
90
+      ? filter_var($data['is_attivo'], FILTER_VALIDATE_BOOLEAN)
91
+      : true;
91 92
     return $data;
92 93
   }
93 94
 
94 95
   public function updating(Model $model, array $data): array
95 96
   {
96
-    // dd($data['ruolo']);
97
-    // $model->roles()->sync([$data['ruolo_id']]);
98
-    $data['is_attivo'] = isset($data['is_attivo']) ? true : false;
97
+    if (array_key_exists('is_attivo', $data)) {
98
+      $data['is_attivo'] = filter_var($data['is_attivo'], FILTER_VALIDATE_BOOLEAN);
99
+    }
99 100
     return $data;
100 101
   }
101 102
   public function messages(): array

+ 457
- 0
app/Http/Controllers/AsportoController.php View File

@@ -0,0 +1,457 @@
1
+<?php
2
+
3
+namespace App\Http\Controllers;
4
+
5
+use Illuminate\Http\Request;
6
+use App\Models\Asporto;
7
+use Illuminate\Support\Collection;
8
+
9
+class AsportoController extends Controller
10
+{
11
+    public static $permission_group = 'asporto';
12
+
13
+    public static $permissions = [
14
+        'view-asporto' => 'Visualizza asporto',
15
+    ];
16
+
17
+    public function index(Request $request)
18
+    {
19
+
20
+        return view('asporto.index');
21
+    }
22
+
23
+    public function dashboard()
24
+    {
25
+        $asportiPreparazione = $this->preparazioneList();
26
+        $asportiChiamata = $this->chiamataList();
27
+        $asportiRitiro = $this->ritiroList();
28
+        $asportiTimeline = $asportiPreparazione
29
+            ->concat($asportiChiamata)
30
+            ->concat($asportiRitiro)
31
+            ->sortBy(function ($asporto) {
32
+                $ora = $asporto->ora_ritiro;
33
+                if ($ora instanceof \DateTimeInterface) {
34
+                    return $ora->format('H:i:s');
35
+                }
36
+
37
+                return (string) ($ora ?: '99:99:99');
38
+            })
39
+            ->values();
40
+
41
+        return view('asporto.dashboard', compact(
42
+            'asportiPreparazione',
43
+            'asportiChiamata',
44
+            'asportiRitiro',
45
+            'asportiTimeline'
46
+        ));
47
+    }
48
+
49
+    public function dashboard_slot(Request $request)
50
+    {
51
+        $impostazioni = \App\Models\Impostazioni::query()
52
+            ->where('attivita_id', session('attivita_attuale'))
53
+            ->whereIn('chiave', [
54
+                'ora_inizio_asporto',
55
+                'ora_fine_asporto',
56
+                'durata_slot_oraria_consegna_asporto',
57
+                'qta_massima_ordini_per_slot_oraria',
58
+            ])
59
+            ->get()
60
+            ->keyBy('chiave');
61
+
62
+        $startMin = $this->timeToMinutes($impostazioni->get('ora_inizio_asporto')?->valore ?? '19:00');
63
+        $endMin = $this->timeToMinutes($impostazioni->get('ora_fine_asporto')?->valore ?? '23:00');
64
+        $durata = max(1, (int) ($impostazioni->get('durata_slot_oraria_consegna_asporto')?->valore ?? 15));
65
+        $maxOrdini = max(1, (int) ($impostazioni->get('qta_massima_ordini_per_slot_oraria')?->valore ?? 1));
66
+
67
+        $asportiDiOggi = Asporto::query()
68
+            ->select('asporto.*')
69
+            ->join('ordine', 'ordine.id', '=', 'asporto.ordine_id')
70
+            ->where('ordine.attivita_id', session('attivita_attuale'))
71
+            ->whereDate('asporto.ora_ritiro', today())
72
+            ->orderBy('asporto.ora_ritiro')
73
+            ->with(['ordine'])
74
+            ->get();
75
+
76
+        $slots = [];
77
+        if ($startMin !== null && $endMin !== null && $endMin > $startMin) {
78
+            for ($t = $startMin; $t + $durata <= $endMin; $t += $durata) {
79
+                $slots[] = ['start' => $t, 'end' => $t + $durata];
80
+            }
81
+        }
82
+
83
+        $activeKeys = [];
84
+        foreach ($slots as $slot) {
85
+            $activeKeys[$slot['start']] = true;
86
+        }
87
+
88
+        $hourStart = $startMin !== null ? intdiv($startMin, 60) : 0;
89
+        $hourEnd = $endMin !== null
90
+            ? max($hourStart, (int) ceil($endMin / 60) - ($endMin % 60 === 0 ? 1 : 0))
91
+            : $hourStart;
92
+        $hours = range($hourStart, $hourEnd);
93
+
94
+        $minuteSlots = [];
95
+        for ($m = 0; $m + $durata <= 60; $m += $durata) {
96
+            $minuteSlots[] = [
97
+                'start' => $m,
98
+                'end' => $m + $durata,
99
+                'label' => sprintf('%02d–%02d', $m, ($m + $durata) >= 60 ? 59 : ($m + $durata)),
100
+            ];
101
+        }
102
+
103
+        $occupancy = [];
104
+        foreach ($asportiDiOggi as $asporto) {
105
+            if (!$asporto->ora_ritiro) {
106
+                continue;
107
+            }
108
+            $ora = \Illuminate\Support\Carbon::parse($asporto->ora_ritiro);
109
+            $oraMin = ((int) $ora->format('H')) * 60 + (int) $ora->format('i');
110
+            $slotStart = null;
111
+            foreach ($slots as $slot) {
112
+                if ($oraMin >= $slot['start'] && $oraMin < $slot['end']) {
113
+                    $slotStart = $slot['start'];
114
+                    break;
115
+                }
116
+            }
117
+            if ($slotStart === null) {
118
+                continue;
119
+            }
120
+            $occupancy[$slotStart] = $occupancy[$slotStart] ?? [];
121
+            $occupancy[$slotStart][] = $asporto;
122
+        }
123
+
124
+        $capacitaOra = count($minuteSlots) * $maxOrdini;
125
+        $capacitaTotale = count($slots) * $maxOrdini;
126
+        $occupati = $asportiDiOggi->count();
127
+        $liberi = max(0, $capacitaTotale - $occupati);
128
+
129
+        return view('asporto.dashboard_slot', compact(
130
+            'asportiDiOggi',
131
+            'hours',
132
+            'minuteSlots',
133
+            'maxOrdini',
134
+            'durata',
135
+            'activeKeys',
136
+            'occupancy',
137
+            'slots',
138
+            'startMin',
139
+            'endMin',
140
+            'capacitaOra',
141
+            'capacitaTotale',
142
+            'occupati',
143
+            'liberi'
144
+        ));
145
+    }
146
+
147
+    protected function timeToMinutes(?string $value): ?int
148
+    {
149
+        if (!$value || !preg_match('/^(\d{1,2}):(\d{2})/', trim($value), $m)) {
150
+            return null;
151
+        }
152
+        $h = (int) $m[1];
153
+        $min = (int) $m[2];
154
+        if ($h > 23 || $min > 59) {
155
+            return null;
156
+        }
157
+
158
+        return $h * 60 + $min;
159
+    }
160
+
161
+    public function preparazioneList()
162
+    {
163
+        return $this->asportiPerStato(Asporto::PREPARAZIONE);
164
+    }
165
+
166
+    public function preparazione()
167
+    {
168
+        $data = $this->boardData('preparazione');
169
+
170
+        return view('asporto.postazione.preparazione', $data['view'] + [
171
+            'boardSignature' => $data['signature'],
172
+        ]);
173
+    }
174
+
175
+    public function completaPreparazione(Request $request, Asporto $asporto)
176
+    {
177
+        $ordine = $asporto->ordine;
178
+        if (!$ordine || (int) $ordine->attivita_id !== (int) session('attivita_attuale')) {
179
+            return response()->json([
180
+                'success' => false,
181
+                'message' => 'Asporto non trovato per questa attività.',
182
+            ], 404);
183
+        }
184
+
185
+        if ($asporto->stato !== Asporto::PREPARAZIONE) {
186
+            return response()->json([
187
+                'success' => false,
188
+                'message' => 'L\'ordine non è più in preparazione.',
189
+            ], 422);
190
+        }
191
+
192
+        try {
193
+            app(\App\Services\Asporto\AsportoService::class)->avanzaAChiamata($asporto);
194
+        } catch (\Throwable $e) {
195
+            return response()->json([
196
+                'success' => false,
197
+                'message' => $e->getMessage(),
198
+            ], 422);
199
+        }
200
+
201
+        return response()->json([
202
+            'success' => true,
203
+            'message' => 'Ordine passato a chiamata.',
204
+            'data' => [
205
+                'id' => $asporto->id,
206
+                'stato' => $asporto->stato,
207
+            ],
208
+        ]);
209
+    }
210
+
211
+    public function chiamataList()
212
+    {
213
+        return $this->asportiPerStato(Asporto::CHIAMATA);
214
+    }
215
+
216
+    public function chiamata()
217
+    {
218
+        $data = $this->boardData('chiamata');
219
+
220
+        return view('asporto.postazione.chiamata', $data['view'] + [
221
+            'boardSignature' => $data['signature'],
222
+        ]);
223
+    }
224
+
225
+    public function completaChiamata(Request $request, Asporto $asporto)
226
+    {
227
+        $ordine = $asporto->ordine;
228
+        if (!$ordine || (int) $ordine->attivita_id !== (int) session('attivita_attuale')) {
229
+            return response()->json([
230
+                'success' => false,
231
+                'message' => 'Asporto non trovato per questa attività.',
232
+            ], 404);
233
+        }
234
+
235
+        if ($asporto->stato !== Asporto::CHIAMATA) {
236
+            return response()->json([
237
+                'success' => false,
238
+                'message' => 'L\'ordine non è più in chiamata.',
239
+            ], 422);
240
+        }
241
+
242
+        try {
243
+            app(\App\Services\Asporto\AsportoService::class)->avanzaARitiro($asporto);
244
+        } catch (\Throwable $e) {
245
+            return response()->json([
246
+                'success' => false,
247
+                'message' => $e->getMessage(),
248
+            ], 422);
249
+        }
250
+
251
+        return response()->json([
252
+            'success' => true,
253
+            'message' => 'Ordine passato a ritiro.',
254
+            'data' => [
255
+                'id' => $asporto->id,
256
+                'stato' => $asporto->stato,
257
+            ],
258
+        ]);
259
+    }
260
+
261
+    public function ritiroList()
262
+    {
263
+        return $this->asportiPerStato(Asporto::RITIRO);
264
+    }
265
+
266
+    public function ritiro()
267
+    {
268
+        $data = $this->boardData('ritiro');
269
+
270
+        return view('asporto.postazione.ritiro', $data['view'] + [
271
+            'boardSignature' => $data['signature'],
272
+        ]);
273
+    }
274
+
275
+    public function completaRitiro(Request $request, Asporto $asporto)
276
+    {
277
+        $ordine = $asporto->ordine;
278
+        if (!$ordine || (int) $ordine->attivita_id !== (int) session('attivita_attuale')) {
279
+            return response()->json([
280
+                'success' => false,
281
+                'message' => 'Asporto non trovato per questa attività.',
282
+            ], 404);
283
+        }
284
+
285
+        if ($asporto->stato !== Asporto::RITIRO) {
286
+            return response()->json([
287
+                'success' => false,
288
+                'message' => 'L\'ordine non è più in ritiro.',
289
+            ], 422);
290
+        }
291
+
292
+        try {
293
+            app(\App\Services\Asporto\AsportoService::class)->avanzaAConsegnato($asporto);
294
+        } catch (\Throwable $e) {
295
+            return response()->json([
296
+                'success' => false,
297
+                'message' => $e->getMessage(),
298
+            ], 422);
299
+        }
300
+
301
+        $asporto = $asporto->fresh('ordine');
302
+        $ordine = $asporto?->ordine;
303
+        $info = is_array($ordine?->info) ? $ordine->info : [];
304
+
305
+        return response()->json([
306
+            'success' => true,
307
+            'message' => 'Ordine consegnato.',
308
+            'data' => [
309
+                'id' => $asporto->id,
310
+                'stato' => $asporto->stato,
311
+                'codice' => $ordine?->codice ?? ('A-'.$asporto->ordine_id),
312
+                'cliente' => $info['cliente'] ?? ($ordine?->nome ?? '—'),
313
+                'orario' => $asporto->ora_ritiro
314
+                    ? \Illuminate\Support\Carbon::parse($asporto->ora_ritiro)->format('H:i')
315
+                    : '',
316
+                'consegnato_at' => $asporto->updated_at
317
+                    ? \Illuminate\Support\Carbon::parse($asporto->updated_at)->format('H:i')
318
+                    : '',
319
+                'diff_min' => ($asporto->ora_ritiro && $asporto->updated_at)
320
+                    ? (int) round((
321
+                        \Illuminate\Support\Carbon::parse($asporto->updated_at)->getTimestamp()
322
+                        - \Illuminate\Support\Carbon::parse($asporto->ora_ritiro)->getTimestamp()
323
+                    ) / 60)
324
+                    : null,
325
+            ],
326
+        ]);
327
+    }
328
+
329
+    /**
330
+     * Sync fluido postazione: HTML board + signature (no poll se invariato).
331
+     */
332
+    public function sync(Request $request, string $stazione)
333
+    {
334
+        if (! in_array($stazione, ['preparazione', 'chiamata', 'ritiro'], true)) {
335
+            abort(404);
336
+        }
337
+
338
+        $data = $this->boardData($stazione);
339
+        $signature = $data['signature'];
340
+
341
+        if ((string) $request->query('signature') === $signature) {
342
+            return response()->json([
343
+                'changed' => false,
344
+                'signature' => $signature,
345
+            ]);
346
+        }
347
+
348
+        return response()->json([
349
+            'changed' => true,
350
+            'signature' => $signature,
351
+            'html' => view('asporto.postazione._boards.'.$stazione, $data['view'])->render(),
352
+        ]);
353
+    }
354
+
355
+    /**
356
+     * @return array{signature: string, view: array<string, mixed>}
357
+     */
358
+    protected function boardData(string $stazione): array
359
+    {
360
+        if ($stazione === 'preparazione') {
361
+            $asporti = $this->preparazioneList();
362
+            $view = [
363
+                'asporti' => $asporti,
364
+                'asportiTimeline' => $asporti->values(),
365
+            ];
366
+
367
+            return [
368
+                'signature' => $this->signatureFrom($asporti),
369
+                'view' => $view,
370
+            ];
371
+        }
372
+
373
+        if ($stazione === 'chiamata') {
374
+            $asporti = $this->chiamataList();
375
+            $view = [
376
+                'asporti' => $asporti,
377
+                'asportiTimeline' => $asporti->values(),
378
+            ];
379
+
380
+            return [
381
+                'signature' => $this->signatureFrom($asporti),
382
+                'view' => $view,
383
+            ];
384
+        }
385
+
386
+        $asporti = $this->ritiroList();
387
+        $asportiConsegnati = $this->consegnatiList();
388
+        $asportiTimeline = $this->preparazioneList()
389
+            ->concat($this->chiamataList())
390
+            ->concat($asporti)
391
+            ->concat($asportiConsegnati->take(15))
392
+            ->unique('id')
393
+            ->sortBy(function ($asporto) {
394
+                $ora = $asporto->ora_ritiro;
395
+                if ($ora instanceof \DateTimeInterface) {
396
+                    return $ora->format('H:i:s');
397
+                }
398
+
399
+                return (string) ($ora ?: '99:99:99');
400
+            })
401
+            ->values();
402
+
403
+        $view = [
404
+            'asporti' => $asporti,
405
+            'asportiConsegnati' => $asportiConsegnati,
406
+            'asportiTimeline' => $asportiTimeline,
407
+        ];
408
+
409
+        return [
410
+            'signature' => $this->signatureFrom($asporti->concat($asportiConsegnati)),
411
+            'view' => $view,
412
+        ];
413
+    }
414
+
415
+    protected function consegnatiList(): Collection
416
+    {
417
+        return Asporto::query()
418
+            ->with(['ordine'])
419
+            ->where('stato', Asporto::CONSEGNATO)
420
+            ->whereHas('ordine', function ($q) {
421
+                $q->where('attivita_id', session('attivita_attuale'));
422
+            })
423
+            ->orderByDesc('updated_at')
424
+            ->limit(40)
425
+            ->get();
426
+    }
427
+
428
+    protected function signatureFrom(Collection $asporti): string
429
+    {
430
+        $parts = $asporti
431
+            ->sortBy('id')
432
+            ->values()
433
+            ->map(function ($asporto) {
434
+                $updated = $asporto->updated_at;
435
+                $ts = $updated instanceof \DateTimeInterface
436
+                    ? $updated->getTimestamp()
437
+                    : (string) $updated;
438
+
439
+                return $asporto->id.'|'.$asporto->stato.'|'.$ts;
440
+            })
441
+            ->implode(';');
442
+
443
+        return hash('sha256', $parts);
444
+    }
445
+
446
+    protected function asportiPerStato(string $stato)
447
+    {
448
+        return Asporto::query()
449
+            ->with(['ordine.righe_ordine.piatto', 'ordine.righe_ordine.has_variante.variante_piatto'])
450
+            ->where('stato', $stato)
451
+            ->whereHas('ordine', function ($q) {
452
+                $q->where('attivita_id', session('attivita_attuale'));
453
+            })
454
+            ->orderBy('ora_ritiro')
455
+            ->get();
456
+    }
457
+}

+ 9
- 18
app/Http/Controllers/BilancioController.php View File

@@ -59,9 +59,9 @@ class BilancioController extends Controller
59 59
         ]);
60 60
     }
61 61
 
62
-    public function index(Request $request)
62
+    public function index(Request $request, BilancioServizioService $bilancioServizio)
63 63
     {
64
-        $attivitaId = session()->get('attivita_attuale');
64
+        $attivitaId = (int) session()->get('attivita_attuale');
65 65
         $attivita = Attivita::find($attivitaId);
66 66
 
67 67
         $year = (int) $request->get('anno', now()->year);
@@ -258,25 +258,18 @@ class BilancioController extends Controller
258 258
             })
259 259
             ->values();
260 260
 
261
-        $righeOrdineAnnuali = RigaOrdine::query()
262
-            ->with(['piatto.cucina', 'ordine'])
263
-            ->whereHas('ordine', function ($query) use ($attivitaId, $yearStart, $yearEnd) {
264
-                $query
265
-                    ->where('attivita_id', $attivitaId)
266
-                    ->whereBetween('created_at', [$yearStart, $yearEnd]);
267
-            })
268
-            ->get();
261
+        $righeOrdineAnnuali = $attivitaId > 0
262
+            ? $bilancioServizio->righeOrdineIncassate($attivitaId, $yearStart, $yearEnd)
263
+            : collect();
269 264
 
270 265
         $piattiPerformance = $righeOrdineAnnuali
271 266
             ->groupBy('piatto_id')
272
-            ->map(function ($items) {
267
+            ->map(function ($items) use ($bilancioServizio) {
273 268
                 $first = $items->first();
274 269
                 $quantita = (int) $items->sum(function ($r) {
275 270
                     return (int) ($r->quantita ?? 0);
276 271
                 });
277
-                $totale = (float) $items->sum(function ($r) {
278
-                    return ((float) ($r->quantita ?? 0)) * ((float) ($r->prezzo ?? 0));
279
-                });
272
+                $totale = (float) $items->sum(fn ($r) => $bilancioServizio->totaleRigaOrdine($r));
280 273
                 return [
281 274
                     'nome' => $first?->piatto?->nome ?? 'Piatto',
282 275
                     'quantita' => $quantita,
@@ -290,14 +283,12 @@ class BilancioController extends Controller
290 283
             ->groupBy(function ($riga) {
291 284
                 return (int) ($riga->piatto?->cucina_id ?? 0);
292 285
             })
293
-            ->map(function ($items) {
286
+            ->map(function ($items) use ($bilancioServizio) {
294 287
                 $first = $items->first();
295 288
                 $quantita = (int) $items->sum(function ($r) {
296 289
                     return (int) ($r->quantita ?? 0);
297 290
                 });
298
-                $totale = (float) $items->sum(function ($r) {
299
-                    return ((float) ($r->quantita ?? 0)) * ((float) ($r->prezzo ?? 0));
300
-                });
291
+                $totale = (float) $items->sum(fn ($r) => $bilancioServizio->totaleRigaOrdine($r));
301 292
                 return [
302 293
                     'nome' => $first?->piatto?->cucina?->nome ?? 'Cucina N/D',
303 294
                     'quantita' => $quantita,

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

@@ -194,17 +194,24 @@ class CarrelloController extends Controller
194 194
             'tipo' => 'required|string|in:singolo,tutti',
195 195
             'piatto_id' => 'required|integer|exists:piatto,id',
196 196
             'riga_ordine_id' => 'required|integer|exists:riga_ordine,id',
197
-            'variante_id' => 'nullable|integer|exists:variante_piatto,id',
197
+            'variante_ids' => 'nullable|array',
198
+            'variante_ids.*' => 'integer|exists:variante_piatto,id',
198 199
             'nota' => 'nullable|string',
199 200
         ]);
200 201
         // dd($request->all());
201
-        $request->nota = null;
202
+// dd($request->input('variante_ids' , []));
203
+        // $request->nota = null;
202 204
         $result = null;
203 205
         if($request->tipo == 'singolo'){
204
-            $result = $this->carrelloService->aggiungiVarianteSingoloPiatto($request->piatto_id, $request->riga_ordine_id, $request->variante_id, $request->nota);
206
+            $result = $this->carrelloService->aggiungiVarianteSingoloPiatto($request->piatto_id, $request->riga_ordine_id, $request->input('variante_ids' , []), $request->nota);
205 207
             
206 208
         }elseif($request->tipo == 'tutti'){
207
-            $result = $this->carrelloService->aggiungiVarianteTuttiPiatto($request->piatto_id, $request->riga_ordine_id, $request->variante_id, $request->nota);
209
+            return response()->json([
210
+                'success' => false,
211
+                'message' => 'Tipo di variante tutti non ancora disponibile.',
212
+                'data' => null,
213
+            ]);
214
+            // $result = $this->carrelloService->aggiungiVarianteTuttiPiatto($request->piatto_id, $request->riga_ordine_id, $request->input('variante_ids' , []), $request->nota);
208 215
         }else{
209 216
 
210 217
             return response()->json([
@@ -350,11 +357,11 @@ class CarrelloController extends Controller
350 357
         $ordine->tipo = $request->tipo;
351 358
         $ordine->save();
352 359
         
353
-    return view('punto_vendita.cassa._partials.carrello.pagamento.checkout', ['ordine' => $ordine, 'dispositivo' => $ordine->dispositivo, 'attivita' => $ordine->attivita]);
360
+    return view('punto_vendita.cassa._partials.carrello.pagamento.checkout', ['ordine' => $ordine, 'dispositivo' => $ordine->dispositivo, 'attivita' => $ordine->attivita, 'vista' => $request->vista]);
354 361
     }
355 362
 
356 363
     public function paga_ordine(Request $request){
357
-
364
+        
358 365
         if(!$request->filled('tag_id') && MetodoPagamento::find($request->metodo_pagamento_id)->tipo == MetodoPagamento::SEGRESTA_WALLET){
359 366
             return back()->with('error', 'Tag ID obbligatorio per il metodo di pagamento Segresta Wallet');
360 367
         }
@@ -378,6 +385,12 @@ class CarrelloController extends Controller
378 385
         ]);
379 386
         $ordine->pagamento_id = $pagamento->id;
380 387
         $ordine->stato = Ordine::IN_ATTESA_PAGAMENTO;
388
+
389
+        $ordine->info = array_merge(
390
+            is_array($ordine->info) ? $ordine->info : [],
391
+            is_array($request->info) ? $request->info : []
392
+        );
393
+        
381 394
         $ordine->save();
382 395
         
383 396
         switch($pagamento->metodo_pagamento->tipo){
@@ -391,7 +404,7 @@ class CarrelloController extends Controller
391 404
                 
392 405
                 if($pagamentoResult['result'] === true){
393 406
                     $ordine->update(['stato' => Ordine::PAGATO]);
394
-                    return redirect()->route('punto-vendita.show', ['punto_vendita_id' => $ordine->dispositivo_id])->with('success', 'Pagamento con contanti riuscito. ->'.$pagamento->metodo_pagamento->tipo);
407
+                    return redirect()->route('punto-vendita.show', ['punto_vendita_id' => $ordine->dispositivo_id , 'vista' => $request->vista])->with('success', 'Pagamento con contanti riuscito. ->'.$pagamento->metodo_pagamento->tipo);
395 408
                 }else{
396 409
                     $ordine->stato = Ordine::CARRELLO;
397 410
                     $ordine->save();

+ 182
- 29
app/Http/Controllers/EndpointController.php View File

@@ -9,10 +9,13 @@ use App\Models\Endpoint;
9 9
 use App\Models\Dispositivo;
10 10
 use App\Models\PrintJob;
11 11
 use App\DataTables\EndpointDataTable;
12
+use App\DataTables\EndpointDataTableEditor;
12 13
 use Illuminate\Support\Facades\Validator;
13 14
 use App\Models\Attivita;
14 15
 use Illuminate\Support\Str;
15
-
16
+use Illuminate\Support\Facades\Session;
17
+use Illuminate\Support\Facades\Auth;
18
+use Illuminate\Support\Facades\Storage;
16 19
 
17 20
 class EndpointController extends Controller
18 21
 {
@@ -34,6 +37,11 @@ class EndpointController extends Controller
34 37
         ];
35 38
     }
36 39
 
40
+    private function creaProductKey()
41
+    {
42
+        return 'FEST-'.strtoupper(Str::random(20));
43
+    }
44
+
37 45
     public function show(Request $request)
38 46
     {
39 47
         $endpoint = Endpoint::query()
@@ -55,6 +63,107 @@ class EndpointController extends Controller
55 63
     }
56 64
     
57 65
     //API
66
+
67
+    //deeplink
68
+    public function collega_questo_pc(Request $request)
69
+    {
70
+        $endpoint = Endpoint::where('attivita_id', Session::get('attivita_attuale'))
71
+        ->where('user_id', $request->user()->id)
72
+        ->where('status', Endpoint::NON_REGISTRATO)
73
+        ->first();
74
+
75
+        if($endpoint){
76
+            $endpoint->deeplink_token = ['token' => Str::random(32) , 'timestamp' => now()->timestamp , 'status' => 'pending'];
77
+            $endpoint->save();
78
+        }else{
79
+
80
+        $endpoint = new Endpoint();
81
+        $endpoint->attivita_id = $request->attivita_id;
82
+        $endpoint->user_id = $request->user()->id;
83
+        $endpoint->deeplink_token = ['token' => Str::random(32) , 'timestamp' => now()->timestamp , 'status' => 'pending'];
84
+        $endpoint->product_key = $this->creaProductKey();
85
+        $endpoint->status = Endpoint::NON_REGISTRATO;
86
+        $endpoint->info = [
87
+            'stampanti' => [],
88
+        ];
89
+        $endpoint->save();
90
+        };
91
+
92
+        //debug
93
+        $server = config('app.url');
94
+        return response()->json([
95
+            'message' => 'Endpoint collegato con successo',
96
+            'status' => 'success',
97
+            'success' => true,
98
+            'endpoint' => $endpoint,
99
+            'url' => url('fest-agent://pair?token=' . $endpoint->deeplink_token['token'] . '&server=' . $server),
100
+        ]);
101
+    }
102
+
103
+    public function api_endpoint_check(Request $request)
104
+    {
105
+        $endpoint = Endpoint::where('token', $request->header('token-endpoint'))->first();
106
+        if(!$endpoint){
107
+            return response()->json([
108
+                'message' => 'Endpoint non trovato',
109
+                'status' => 'error',
110
+                'success' => false,
111
+                'exists' => false,
112
+            ], 404);
113
+        }
114
+
115
+        return response()->json([
116
+            'message' => 'Endpoint verificato con successo',
117
+            'status' => 'success',
118
+            'success' => true,
119
+            'exists' => true,
120
+        ]);
121
+    }
122
+
123
+    public function api_endpoint_disattiva(Request $request)
124
+    {
125
+        $endpoint = Endpoint::where('token', $request->header('token-endpoint'))->first();
126
+        if(!$endpoint){
127
+            return response()->json([
128
+                'message' => 'Endpoint non trovato',
129
+                'status' => 'error',
130
+                'success' => false,
131
+            ], 404);
132
+        }
133
+        $endpoint->update([
134
+            'status' => Endpoint::DISABILITATO,
135
+        ]);
136
+        return response()->json([
137
+            'message' => 'Endpoint disattivato con successo',
138
+            'status' => 'success',
139
+            'success' => true,
140
+        ]);
141
+        }
142
+
143
+    public function token_pairing(Request $request){
144
+        $request->validate([
145
+            'token' => 'required|string|max:255',
146
+        ]);
147
+        $endpoint = Endpoint::where('deeplink_token->token', $request->token)->first();
148
+        if(!$endpoint){
149
+            return response()->json(['message' => 'Endpoint non trovato', 'status' => 'error', 'success' => false], 404);
150
+        }
151
+        $deeplink_token = $endpoint->deeplink_token;
152
+        $deeplink_token['status'] = 'paired';
153
+        $endpoint->update([
154
+            'deeplink_token' => $deeplink_token,
155
+            'status' => Endpoint::REGISTRATO,
156
+            'token' => Str::random(32),
157
+        ]);
158
+        return response()->json([
159
+            'message' => 'Endpoint registrato con successo',
160
+            'status' => 'success',
161
+            'success' => true,
162
+            'endpoint' => $endpoint,
163
+            'token' => $endpoint->token,
164
+        ]);
165
+    }
166
+
58 167
     public function register(Request $request)
59 168
     {
60 169
         $request->validate([
@@ -113,6 +222,10 @@ class EndpointController extends Controller
113 222
                     ]);
114 223
             // }
115 224
         }
225
+        $endpoint->update([
226
+            'status' => Endpoint::ATTIVO,
227
+            'last_heartbeat' => now(),
228
+        ]);
116 229
 
117 230
         return response()->json([
118 231
             'message' => 'Stampanti registrate con successo',
@@ -130,6 +243,20 @@ class EndpointController extends Controller
130 243
         $has_lavoro = false;
131 244
         $endpoint = $request->endpoint;
132 245
 
246
+        if(!$endpoint){
247
+            return response()->json([
248
+                'message' => 'Endpoint non trovato',
249
+                'status' => 'error',
250
+                'success' => false,
251
+            ], 404);
252
+        }
253
+        // Heartbeat dell'endpoint
254
+        if($endpoint->last_heartbeat?->diffInMinutes(now()) > 3){
255
+            $endpoint->update([
256
+                'last_heartbeat' => now(),
257
+            ]);
258
+        };
259
+
133 260
         $stampanti = $endpoint->stampanti;
134 261
         $stampanti_ids = $endpoint->stampanti->pluck('id')->toArray();
135 262
         $query = PrintJob::whereIn('stampante_id', $stampanti_ids)
@@ -158,42 +285,66 @@ class EndpointController extends Controller
158 285
     }
159 286
 
160 287
     public function esito_stampa(Request $request)
161
-    {
162
-        $request->validate([
163
-            'printJob_id' => 'required|integer|exists:print_job,id',
164
-            'esito' => 'required|string|in:success,error',
165
-            // 'endpoint_id' => 'required|integer|exists:endpoint,id',
166
-        ]);
288
+{
289
+    $request->validate([
290
+        'printJob_id' => 'required|integer|exists:print_job,id',
291
+        'esito' => 'required|string|in:success,error',
292
+    ]);
167 293
 
168
-        $printJob = PrintJob::find($request->printJob_id)->where('stato', PrintJob::STATO_PRESO);
169
-        if($printJob->count() == 0){
294
+    $printJob = PrintJob::query()
295
+        ->whereKey($request->printJob_id)
296
+        ->first();
297
+
298
+    if (! $printJob) {
299
+        return response()->json([
300
+            'message' => 'Print job non trovato',
301
+            'status' => 'error',
302
+            'success' => false,
303
+        ], 404);
304
+    }
305
+
306
+    // opzionale ma utile: evita update di job non "preso"
307
+    if ($printJob->stato !== PrintJob::STATO_PRESO) {
308
+        // idempotenza: se già completato e arriva ancora success, non fallire
309
+        if ($printJob->stato === PrintJob::STATO_COMPLETATO && $request->esito === 'success') {
170 310
             return response()->json([
171
-                'message' => 'Print job non trovato o non in stato preso',
172
-                'status' => 'error',
173
-                'success' => false,
311
+                'message' => 'Esito già registrato',
312
+                'status' => 'success',
313
+                'success' => true,
174 314
             ]);
175
-        };
176
-
177
-        switch($request->esito){
178
-            case 'success':
179
-                $printJob->update([
180
-                    'stato' => PrintJob::STATO_COMPLETATO,
181
-                ]);
182
-                break;
183
-            case 'error':
184
-                $printJob->update([
185
-                    'stato' => PrintJob::STATO_FALLITO,
186
-                ]);
187
-                break;
188 315
         }
316
+
189 317
         return response()->json([
190
-            'message' => 'Esito stampa aggiornato con successo',
318
+            'message' => 'Print job non in stato preso',
319
+            'status' => 'error',
320
+            'success' => false,
321
+        ], 409);
322
+    }
323
+
324
+    if ($request->esito === 'success') {
325
+        $printJob->update([
326
+            'stato' => PrintJob::STATO_COMPLETATO,
327
+            'last_error' => null,
328
+        ]);
329
+    } else {
330
+        $printJob->update([
331
+            'stato' => PrintJob::STATO_FALLITO,
332
+            'failed_at' => now(),
333
+            'last_error' => 'Errore endpoint/agent',
191 334
         ]);
192 335
     }
193 336
 
337
+    return response()->json([
338
+        'message' => 'Esito stampa aggiornato con successo',
339
+        'status' => 'success',
340
+        'success' => true,
341
+    ]);
342
+}
343
+
194 344
     //WEB
195 345
     public function index(EndpointDataTable $dataTable)
196 346
     {
347
+        $dataTable->attivita_id = Session::get('attivita_attuale');
197 348
         return $dataTable->render('endpoint.index');
198 349
     }
199 350
 
@@ -386,7 +537,7 @@ class EndpointController extends Controller
386 537
             'url' => 'non assegnato',
387 538
             'user_id' => $request->user()->id,
388 539
             'status' => Endpoint::NON_REGISTRATO,
389
-            'product_key' => 'FEST-'.strtoupper(Str::random(20)),
540
+            'product_key' => $this->creaProductKey(), //'FEST-'.strtoupper(Str::random(20)),
390 541
             'info' => [
391 542
                 'stampanti' => [],
392 543
             ],
@@ -396,8 +547,10 @@ class EndpointController extends Controller
396 547
             'success' => true,
397 548
             'endpoint' => $endpoint,
398 549
         ]);
550
+    }
399 551
 
400
-
401
-    
552
+    public function download_festagent(Request $request)
553
+    {
554
+        return Storage::disk('festAgent')->download('001_fest.exe', '001_fest.exe');   
402 555
     }
403 556
 }

+ 82
- 0
app/Http/Controllers/FatturaController.php View File

@@ -0,0 +1,82 @@
1
+<?php
2
+
3
+namespace App\Http\Controllers;
4
+
5
+use Illuminate\Http\Request;
6
+use App\Models\Fattura;
7
+use App\DataTables\FatturaDataTable;
8
+use App\DataTables\FatturaDataTableEditor;
9
+use Illuminate\Support\Facades\Auth;
10
+
11
+class FatturaController extends Controller
12
+{
13
+    
14
+public static $permission_group = "Fatture";
15
+public static $permissions = [
16
+    'view-fattura' => 'Vedi',
17
+    'create-fattura' => 'Crea',
18
+    'edit-fattura' => 'Modifica',
19
+    'delete-fattura' => 'Elimina',
20
+];
21
+
22
+public static function middleware(): array
23
+{
24
+    return [
25
+        new Middleware('permission:view-fattura', only: ['index', 'show']),
26
+        new Middleware('permission:create-fattura|edit-fattura|delete-fattura', only: ['store', 'update', 'destroy']),
27
+    ];
28
+}
29
+
30
+public function index(FatturaDataTable $dataTable, Request $request)
31
+{
32
+    $dataTable->attivita_id = session()->get('attivita_attuale');
33
+
34
+    if($request->has('user_tutteLeMieFatture')){
35
+        $dataTable->query = $dataTable->query->where('user_id', Auth::user()->id);
36
+    }
37
+    return $dataTable->render('fattura.index');
38
+}
39
+
40
+public function store(FatturaDataTableEditor $editor)
41
+{
42
+    $request = request();
43
+    $input = $request->all();
44
+    if($request->has('action')){
45
+        switch($input['action']){
46
+            case 'create':
47
+                if(!Auth::user()->can('create-fattura')) return response()->json(['error' => 'Non hai i permessi per creare una fattura'],403);
48
+                break;
49
+            case 'edit':
50
+                if(!Auth::user()->can('edit-fattura')) return response()->json(['error' => 'Non hai i permessi per modificare una fattura'],403);
51
+                break;
52
+            case 'remove':
53
+                if(!Auth::user()->can('delete-fattura')) return response()->json(['error' => 'Non hai i permessi per eliminare una fattura'],403);
54
+                break;
55
+        }
56
+    }
57
+    return $editor->process($request);
58
+}
59
+
60
+public function show(Request $request)
61
+{
62
+    if(!Auth::user()->hasAnyRole(['superadmin','amministratore'])) return response()->json(['error' => 'Non hai i permessi per vedere una fattura'],403);
63
+
64
+    $fattura = Fattura::findOrFail($request->fattura_id);
65
+    return view('fattura.show', compact('fattura'));
66
+}
67
+
68
+public function need_fatturazione_mensile()
69
+{
70
+    $generaFatturaMensile = app('App\Services\Fattura\GeneraFatturaMensile')->needFatturazione();
71
+    return response()->json(['success' => 'Fattura mensile generata']);
72
+}
73
+
74
+public function registra_pagamento(Request $request)
75
+{
76
+    $fattura = Fattura::findOrFail($request->fattura_id);
77
+    $fattura->stato = Fattura::PAGATA;
78
+    $fattura->info= json_encode(['pagamento_registrato_il' => now()]);
79
+    $fattura->save();
80
+    return response()->json(['success' => 'Pagamento registrato']);
81
+}
82
+}

+ 19
- 3
app/Http/Controllers/HomePageController.php View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
4 4
 
5 5
 use App\Http\Controllers\Controller;
6 6
 use App\Models\Attivita;
7
+use App\Models\User;
7 8
 use Illuminate\Http\Request;
8 9
 use App\Models\Segnalazione;
9 10
 use App\Models\Reparto;
@@ -21,12 +22,27 @@ class HomePageController extends Controller
21 22
   public function welcome()
22 23
   {
23 24
     $pageConfigs = ['myLayout' => 'front'];
24
-    $attivitaAttive = Attivita::query()
25
-      ->where('is_attiva', true)
25
+
26
+    $organizzazioni = User::query()
27
+      ->role('amministratore')
28
+      ->whereHas('attivita', function ($query) {
29
+        $query->where('is_attiva', true);
30
+      })
31
+      ->with([
32
+        'attivita' => function ($query) {
33
+          $query
34
+            ->where('is_attiva', true)
35
+            ->with('saltacoda')
36
+            ->orderBy('nome');
37
+        },
38
+      ])
39
+      ->orderBy('cognome')
26 40
       ->orderBy('nome')
27 41
       ->get();
28 42
 
29
-    return view('welcome', compact('pageConfigs', 'attivitaAttive'));
43
+    $totaleAttivita = $organizzazioni->sum(fn (User $user) => $user->attivita->count());
44
+
45
+    return view('welcome', compact('pageConfigs', 'organizzazioni', 'totaleAttivita'));
30 46
   }
31 47
 
32 48
   public function admin_dashboard()

+ 23
- 2
app/Http/Controllers/ImpostazioniController.php View File

@@ -2,10 +2,13 @@
2 2
 
3 3
 namespace App\Http\Controllers;
4 4
 
5
+use App\Models\Attivita;
5 6
 use Illuminate\Http\Request;
6
-use App\DataTables\ImpostazioniDataTable;
7 7
 use App\Models\Impostazioni;
8
+use Illuminate\Support\Facades\Auth;
8 9
 use Illuminate\Support\Facades\Session;
10
+use App\DataTables\ImpostazioniDataTable;
11
+use App\DataTables\ImpostazioniDataTableEditor;
9 12
 
10 13
 class ImpostazioniController extends Controller
11 14
 {
@@ -27,8 +30,19 @@ class ImpostazioniController extends Controller
27 30
     
28 31
     public function index(ImpostazioniDataTable $dataTable)
29 32
     {
33
+        $attivita = Attivita::find(Session::get('attivita_attuale'));
34
+        if(!$attivita) return redirect()->route('attivita.index')->with('error', 'Attività non trovata');
35
+
36
+        if(Auth::user()->id != $attivita->user_id){
37
+            if(!Auth::user()->hasRole('superadmin')){
38
+                return redirect()->route('attivita.index')->with('error', 'Non hai accesso a questa attività');
39
+            }
40
+        }
41
+
42
+        $gruppoImpostazioni = Impostazioni::where('attivita_id', $attivita->id)
43
+        ->get()->groupBy('gruppo');
30 44
         $dataTable->attivita_id = Session::get('attivita_attuale');
31
-        return $dataTable->render('impostazioni.index');
45
+        return $dataTable->render('impostazioni.index', compact('gruppoImpostazioni'));
32 46
     }
33 47
 
34 48
     public function store(ImpostazioniDataTableEditor $editor)
@@ -50,4 +64,11 @@ class ImpostazioniController extends Controller
50 64
         }
51 65
         return $editor->process($request);
52 66
     }
67
+
68
+    public function edit($impostazione_id)
69
+    {
70
+        $impostazione = Impostazioni::find($impostazione_id);
71
+        if(!$impostazione) return redirect()->route('impostazioni.index')->with('error', 'Impostazione non trovata');
72
+        return view('impostazioni.edit', compact('impostazione'));
73
+    }
53 74
 }

+ 90
- 0
app/Http/Controllers/ListinoPrezziController.php View File

@@ -0,0 +1,90 @@
1
+<?php
2
+
3
+namespace App\Http\Controllers;
4
+
5
+use Illuminate\Http\Request;
6
+use App\Models\ListinoPrezzi;
7
+use Illuminate\Support\Facades\Auth;
8
+use Illuminate\Support\Facades\Validator;
9
+use App\DataTables\ListinoPrezziDataTable;
10
+use App\DataTables\ListinoPrezziDataTableEditor;
11
+
12
+
13
+class ListinoPrezziController extends Controller
14
+{
15
+    public static $permission_group = "Listino Prezzi";
16
+    public static $permissions = [
17
+        'view-listino_prezzi' => 'Vedi',
18
+        'create-listino-prezzi' => 'Crea',
19
+        'edit-listino-prezzi' => 'Modifica',
20
+        'delete-listino-prezzi' => 'Elimina',
21
+    ];
22
+    
23
+    public static function middleware(): array
24
+    {
25
+        return  [
26
+                    new Middleware('role:superadmin', ['only' => ['index', 'store']]),
27
+                    new Middleware('role:amministratore', ['only' => ['show_listino_cliente']]),
28
+                ];
29
+    }
30
+
31
+    public function index(ListinoPrezziDataTable $dataTable)
32
+    {
33
+        return $dataTable->render('listino_prezzi.index');
34
+    }
35
+
36
+    public function store(ListinoPrezziDataTableEditor $editor)
37
+    {
38
+        if(!Auth::user()->hasRole('superadmin')){
39
+            return 403;
40
+        }
41
+
42
+            $request = request();
43
+            $input = $request->all();
44
+            if($request->has('action')){
45
+                switch($input['action']){
46
+                    case 'create':
47
+                        if(!Auth::user()->can('create-listino-prezzi')) return;
48
+                        break;
49
+                    case 'edit':
50
+                        if(!Auth::user()->can('edit-listino-prezzi')) return;
51
+                        break;
52
+                    case 'remove':
53
+                        if(!Auth::user()->can('delete-listino-prezzi')) return;
54
+                        break;
55
+                }
56
+            }
57
+            return $editor->process($request);
58
+    }
59
+
60
+    public function show_listino_cliente()
61
+    {
62
+        $listino_prezzi = ListinoPrezzi::all();
63
+        return view('listino_prezzi.cliente.show', compact('listino_prezzi'));
64
+    }
65
+
66
+    public function show_listino_pubblico()
67
+    {
68
+        $listino_prezzi = ListinoPrezzi::all();
69
+        return view('listino_prezzi.pubblico.show', compact('listino_prezzi'));
70
+    }
71
+
72
+    public function export_listino_pubblico_pdf(\App\Services\ListinoPrezzi\ListinoPubblicoPdfExporter $exporter)
73
+    {
74
+        try {
75
+            $html = $exporter->renderPageHtml(ListinoPrezzi::all());
76
+            $pdfPath = $exporter->exportFromHtml($html);
77
+        } catch (\Throwable $e) {
78
+            report($e);
79
+            return redirect()
80
+                ->route('listino-prezzi.show')
81
+                ->with('error', 'Impossibile generare il PDF: '.$e->getMessage());
82
+        }
83
+
84
+        return response()
85
+            ->download($pdfPath, 'listino-segrestafest-local-cloud.pdf', [
86
+                'Content-Type' => 'application/pdf',
87
+            ])
88
+            ->deleteFileAfterSend(true);
89
+    }
90
+}

+ 281
- 51
app/Http/Controllers/MappaDispositiviController.php View File

@@ -6,6 +6,9 @@ use Illuminate\Http\Request;
6 6
 use App\Models\Attivita;
7 7
 use App\Models\Dispositivo;
8 8
 use App\Models\Cucina;
9
+use App\Models\Endpoint;
10
+use App\Models\Piatto;
11
+use App\Models\VariantePiatto;
9 12
 use App\DataTables\DispositivoDataTable;
10 13
 use App\DataTables\DispositivoDataTableEditor;
11 14
 use Illuminate\Support\Facades\Auth;
@@ -36,9 +39,30 @@ class MappaDispositiviController extends Controller
36 39
             return $dataTable->ajax();
37 40
         }
38 41
 
42
+        $attivitaCorrenteId = (int) Session::get('attivita_attuale');
43
+        $attivitaCorrente = null;
44
+        if ($attivitaCorrenteId > 0) {
45
+            $attivitaCorrente = Attivita::query()
46
+                ->where('id', $attivitaCorrenteId)
47
+                ->where('user_id', Auth::id())
48
+                ->where('is_attiva', true)
49
+                ->first(['id', 'nome']);
50
+        }
51
+
52
+        $topologia = self::buildTopologyByAttivita($attivitaCorrente?->id, (int) Auth::id());
53
+        $dispositiviByTipo = self::buildDispositiviByTipo();
54
+        $dispositiviFlat = collect($dispositiviByTipo)->flatten(1);
55
+
39 56
         return view('mappa_dispositivi.index', [
40
-            'dispositiviByTipo' => self::buildDispositiviByTipo(),
57
+            'topologia' => $topologia,
58
+            'attivitaCorrente' => $attivitaCorrente,
59
+            'dispositiviByTipo' => $dispositiviByTipo,
60
+            'dispositiviAttivitaCorrente' => self::buildDispositiviAttivitaCorrente($attivitaCorrente?->id),
41 61
             'tipiDispositivo' => Dispositivo::getTipoDispositivo(),
62
+            'totaleDispositivi' => $dispositiviFlat->count(),
63
+            'totaleAttivi' => $dispositiviFlat->where('is_attivo', true)->count(),
64
+            'puoModificarePv' => Auth::user()->can('edit-punto_vendita'),
65
+            'guidaStampanti' => self::buildGuidaStampanti($attivitaCorrente?->id, (int) Auth::id()),
42 66
             'attivitaList' => Attivita::query()
43 67
                 ->where('is_attiva', true)
44 68
                 ->orderBy('nome')
@@ -92,7 +116,7 @@ class MappaDispositiviController extends Controller
92 116
                     ->orWhereNull('attivita_id');
93 117
             })
94 118
             ->orderBy('nome')
95
-            ->get(['id', 'nome', 'ubicazione', 'tipo', 'is_attivo', 'occupato', 'attivita_id']);
119
+            ->get(['id', 'nome', 'ubicazione', 'tipo', 'is_attivo', 'occupato', 'attivita_id', 'binding_token']);
96 120
 
97 121
         foreach ($devices as $device) {
98 122
             $tipo = $device->tipo ?? '_altro';
@@ -103,6 +127,44 @@ class MappaDispositiviController extends Controller
103 127
         return $grouped;
104 128
     }
105 129
 
130
+    /**
131
+     * Dispositivi dell'attività in sessione, raggruppati per tipologia.
132
+     */
133
+    public static function buildDispositiviAttivitaCorrente(?int $attivitaId): array
134
+    {
135
+        $tipi = Dispositivo::getTipoDispositivo()->keys()->all();
136
+        $grouped = array_fill_keys($tipi, []);
137
+        $grouped['_altro'] = [];
138
+
139
+        if ($attivitaId === null || $attivitaId <= 0) {
140
+            return [
141
+                'by_tipo' => $grouped,
142
+                'totale' => 0,
143
+                'totale_attivi' => 0,
144
+            ];
145
+        }
146
+
147
+        $devices = Dispositivo::query()
148
+            ->with('attivita:id,nome')
149
+            ->where('attivita_id', $attivitaId)
150
+            ->orderBy('nome')
151
+            ->get(['id', 'nome', 'ubicazione', 'tipo', 'is_attivo', 'occupato', 'attivita_id', 'binding_token']);
152
+
153
+        foreach ($devices as $device) {
154
+            $tipo = $device->tipo ?? '_altro';
155
+            $bucket = isset($grouped[$tipo]) ? $tipo : '_altro';
156
+            $grouped[$bucket][] = self::serializeDispositivoMappa($device);
157
+        }
158
+
159
+        $flat = collect($grouped)->flatten(1);
160
+
161
+        return [
162
+            'by_tipo' => $grouped,
163
+            'totale' => $flat->count(),
164
+            'totale_attivi' => $flat->where('is_attivo', true)->count(),
165
+        ];
166
+    }
167
+
106 168
     private static function serializeDispositivoMappa(Dispositivo $device): array
107 169
     {
108 170
         return [
@@ -112,6 +174,7 @@ class MappaDispositiviController extends Controller
112 174
             'ubicazione' => $device->ubicazione !== null ? (string) $device->ubicazione : null,
113 175
             'is_attivo' => (bool) $device->is_attivo,
114 176
             'occupato' => (bool) $device->occupato,
177
+            'has_binding' => ! empty($device->binding_token),
115 178
             'attivita_id' => $device->attivita_id !== null ? (int) $device->attivita_id : null,
116 179
             'attivita_nome' => $device->attivita?->nome,
117 180
             'is_punto_vendita' => in_array($device->tipo, [
@@ -125,22 +188,32 @@ class MappaDispositiviController extends Controller
125 188
     /**
126 189
      * Bozza mappa: contenitore = attività → punto vendita (cassa/kiosk/cameriere) + cucine → stampante.
127 190
      */
128
-    public static function buildTopologyByAttivita(): array
191
+    public static function buildTopologyByAttivita(?int $attivitaId = null, ?int $userId = null): array
129 192
     {
130 193
         $pvTipi = [Dispositivo::CASSA, Dispositivo::KIOSK, Dispositivo::CAMERIERE];
131 194
 
132
-        $attivitaRows = Attivita::query()
195
+        $attivitaQuery = Attivita::query()
133 196
             ->where('is_attiva', true)
134
-            ->orderBy('nome')
135
-            ->get(['id', 'nome']);
197
+            ->orderBy('nome');
198
+        if ($userId !== null && $userId > 0) {
199
+            $attivitaQuery->where('user_id', $userId);
200
+        }
201
+        if ($attivitaId !== null && $attivitaId > 0) {
202
+            $attivitaQuery->where('id', $attivitaId);
203
+        }
204
+        $attivitaRows = $attivitaQuery->get(['id', 'nome']);
136 205
 
137 206
         $attivitaIds = $attivitaRows->pluck('id')->all();
207
+        if (empty($attivitaIds)) {
208
+            return [];
209
+        }
138 210
 
139
-        $devicesGrouped = Dispositivo::query()
211
+        $devices = Dispositivo::query()
140 212
             ->whereIn('attivita_id', $attivitaIds)
141 213
             ->orderBy('nome')
142
-            ->get(['id', 'nome', 'ubicazione', 'tipo', 'is_attivo', 'attivita_id'])
143
-            ->groupBy(fn (Dispositivo $d) => (int) $d->attivita_id);
214
+            ->get(['id', 'nome', 'ubicazione', 'tipo', 'is_attivo', 'attivita_id', 'url_stampante', 'endpoint_id']);
215
+
216
+        $devicesGrouped = $devices->groupBy(fn (Dispositivo $d) => (int) $d->attivita_id);
144 217
 
145 218
         $cucineByAttivita = Cucina::query()
146 219
             ->whereIn('attivita_id', $attivitaIds)
@@ -148,6 +221,35 @@ class MappaDispositiviController extends Controller
148 221
             ->get(['id', 'nome', 'stampante_id', 'attivita_id'])
149 222
             ->groupBy(fn (Cucina $c) => (int) $c->attivita_id);
150 223
 
224
+        $cucinaIds = $cucineByAttivita
225
+            ->flatten(1)
226
+            ->pluck('id')
227
+            ->map(fn ($id) => (int) $id)
228
+            ->filter()
229
+            ->unique()
230
+            ->values()
231
+            ->all();
232
+
233
+        $piattiCountByCucina = [];
234
+        $variantiCountByCucina = [];
235
+        if (!empty($cucinaIds)) {
236
+            $piattiCountByCucina = Piatto::query()
237
+                ->selectRaw('cucina_id, COUNT(*) as aggregate')
238
+                ->whereIn('cucina_id', $cucinaIds)
239
+                ->groupBy('cucina_id')
240
+                ->pluck('aggregate', 'cucina_id')
241
+                ->map(fn ($count) => (int) $count)
242
+                ->all();
243
+
244
+            $variantiCountByCucina = VariantePiatto::query()
245
+                ->selectRaw('cucina_id, COUNT(*) as aggregate')
246
+                ->whereIn('cucina_id', $cucinaIds)
247
+                ->groupBy('cucina_id')
248
+                ->pluck('aggregate', 'cucina_id')
249
+                ->map(fn ($count) => (int) $count)
250
+                ->all();
251
+        }
252
+
151 253
         $stampanteIds = Cucina::query()
152 254
             ->whereIn('attivita_id', $attivitaIds)
153 255
             ->whereNotNull('stampante_id')
@@ -158,7 +260,8 @@ class MappaDispositiviController extends Controller
158 260
 
159 261
         $stampanti = Dispositivo::query()
160 262
             ->whereIn('id', $stampanteIds)
161
-            ->get(['id', 'nome', 'ubicazione', 'tipo', 'is_attivo'])
263
+            ->with('endpoint:id,label')
264
+            ->get(['id', 'nome', 'ubicazione', 'tipo', 'is_attivo', 'endpoint_id'])
162 265
             ->keyBy('id');
163 266
 
164 267
         $serialize = static function (Dispositivo $d): array {
@@ -174,6 +277,8 @@ class MappaDispositiviController extends Controller
174 277
                 'ubicazione' => $d->ubicazione !== null ? (string) $d->ubicazione : null,
175 278
                 'tipo' => $d->tipo !== null ? (string) $d->tipo : null,
176 279
                 'is_attivo' => (bool) $d->is_attivo,
280
+                'endpoint_id' => $d->endpoint_id ? (int) $d->endpoint_id : null,
281
+                'endpoint_label' => $d->relationLoaded('endpoint') ? $d->endpoint?->label : null,
177 282
                 'ip' => $ip,
178 283
             ];
179 284
         };
@@ -183,7 +288,58 @@ class MappaDispositiviController extends Controller
183 288
         foreach ($attivitaRows as $att) {
184 289
             $aid = (int) $att->id;
185 290
             $devices = $devicesGrouped->get($aid, collect());
186
-            $puntiVendita = $devices->whereIn('tipo', $pvTipi)->values()->map($serialize)->all();
291
+            $pvRows = $devices->whereIn('tipo', $pvTipi)->values();
292
+            $pvIds = $pvRows->pluck('id')->all();
293
+            $pvWithDeps = Dispositivo::query()
294
+                ->whereIn('id', $pvIds)
295
+                ->with([
296
+                    'endpoint:id,label',
297
+                    'hasCucine:id,nome,stampante_id',
298
+                ])
299
+                ->get()
300
+                ->keyBy('id');
301
+
302
+            $puntiVendita = $pvRows->map(function (Dispositivo $pv) use ($serialize, $pvWithDeps, $stampanti) {
303
+                $pvData = $serialize($pv);
304
+                $fullPv = $pvWithDeps->get($pv->id);
305
+                $pvData['endpoint_id'] = $fullPv?->endpoint_id ? (int) $fullPv->endpoint_id : null;
306
+                $pvData['endpoint_label'] = $fullPv?->endpoint?->label;
307
+
308
+                $stampanteCassa = null;
309
+                $stampanteRef = $fullPv?->url_stampante;
310
+                if (is_numeric($stampanteRef)) {
311
+                    $stampanteModel = Dispositivo::query()->find((int) $stampanteRef);
312
+                    if ($stampanteModel) {
313
+                        $stampanteCassa = $serialize($stampanteModel);
314
+                    }
315
+                }
316
+                $pvData['stampante_cassa'] = $stampanteCassa;
317
+
318
+                $pvData['cucine_collegate'] = $fullPv
319
+                    ? $fullPv->hasCucine->map(function ($c) use ($serialize, $stampanti) {
320
+                        $stampante = null;
321
+                        if ($c->stampante_id && $stampanti->has((int) $c->stampante_id)) {
322
+                            $stampante = $serialize($stampanti->get((int) $c->stampante_id));
323
+                        } elseif ($c->stampante_id) {
324
+                            $fallback = Dispositivo::query()
325
+                                ->with('endpoint:id,label')
326
+                                ->find((int) $c->stampante_id, ['id', 'nome', 'ubicazione', 'tipo', 'is_attivo', 'endpoint_id']);
327
+                            if ($fallback) {
328
+                                $stampante = $serialize($fallback);
329
+                            }
330
+                        }
331
+
332
+                        return [
333
+                            'id' => (int) $c->id,
334
+                            'nome' => (string) $c->nome,
335
+                            'stampante_id' => $c->stampante_id ? (int) $c->stampante_id : null,
336
+                            'stampante' => $stampante,
337
+                        ];
338
+                    })->values()->all()
339
+                    : [];
340
+
341
+                return $pvData;
342
+            })->all();
187 343
             $altri = $devices->whereNotIn('tipo', $pvTipi)->values()->map($serialize)->all();
188 344
 
189 345
             $cucinePayload = [];
@@ -192,15 +348,20 @@ class MappaDispositiviController extends Controller
192 348
                 if ($cucina->stampante_id && $stampanti->has((int) $cucina->stampante_id)) {
193 349
                     $st = $serialize($stampanti->get((int) $cucina->stampante_id));
194 350
                 } elseif ($cucina->stampante_id) {
195
-                    $fallback = Dispositivo::query()->find((int) $cucina->stampante_id);
351
+                    $fallback = Dispositivo::query()
352
+                        ->with('endpoint:id,label')
353
+                        ->find((int) $cucina->stampante_id, ['id', 'nome', 'ubicazione', 'tipo', 'is_attivo', 'endpoint_id']);
196 354
                     if ($fallback) {
197 355
                         $st = $serialize($fallback);
198 356
                     }
199 357
                 }
358
+                $cucinaId = (int) $cucina->id;
200 359
                 $cucinePayload[] = [
201
-                    'id' => (int) $cucina->id,
360
+                    'id' => $cucinaId,
202 361
                     'nome' => (string) $cucina->nome,
203 362
                     'stampante' => $st,
363
+                    'piatti_count' => $piattiCountByCucina[$cucinaId] ?? 0,
364
+                    'varianti_count' => $variantiCountByCucina[$cucinaId] ?? 0,
204 365
                 ];
205 366
             }
206 367
 
@@ -213,50 +374,119 @@ class MappaDispositiviController extends Controller
213 374
             ];
214 375
         }
215 376
 
216
-        $orphanDevices = Dispositivo::query()
217
-            ->whereNull('attivita_id')
218
-            ->orderBy('nome')
219
-            ->get(['id', 'nome', 'ubicazione', 'tipo', 'is_attivo', 'attivita_id']);
377
+        return $out;
378
+    }
220 379
 
221
-        $orphanCucine = Cucina::query()
222
-            ->whereNull('attivita_id')
223
-            ->orderBy('nome')
224
-            ->get(['id', 'nome', 'stampante_id', 'attivita_id']);
380
+    /**
381
+     * Dati per la guida setup stampanti (tab mappa dispositivi).
382
+     */
383
+    public static function buildGuidaStampanti(?int $attivitaId, ?int $userId = null): array
384
+    {
385
+        if ($attivitaId === null || $attivitaId <= 0) {
386
+            return ['ready' => false];
387
+        }
225 388
 
226
-        if ($orphanDevices->isNotEmpty() || $orphanCucine->isNotEmpty()) {
227
-            $orphanStampanteIds = $orphanCucine->pluck('stampante_id')->filter()->unique()->values();
228
-            $orphanStampanti = Dispositivo::query()
229
-                ->whereIn('id', $orphanStampanteIds)
230
-                ->get(['id', 'nome', 'ubicazione', 'tipo', 'is_attivo'])
231
-                ->keyBy('id');
389
+        $topologia = self::buildTopologyByAttivita($attivitaId, $userId);
390
+        $att = $topologia[0] ?? null;
232 391
 
233
-            $cucineOrfane = [];
234
-            foreach ($orphanCucine as $cucina) {
235
-                $st = null;
236
-                if ($cucina->stampante_id && $orphanStampanti->has((int) $cucina->stampante_id)) {
237
-                    $st = $serialize($orphanStampanti->get((int) $cucina->stampante_id));
238
-                } elseif ($cucina->stampante_id) {
239
-                    $fb = Dispositivo::query()->find((int) $cucina->stampante_id);
240
-                    if ($fb) {
241
-                        $st = $serialize($fb);
242
-                    }
243
-                }
244
-                $cucineOrfane[] = [
245
-                    'id' => (int) $cucina->id,
246
-                    'nome' => (string) $cucina->nome,
247
-                    'stampante' => $st,
248
-                ];
249
-            }
392
+        $endpoints = Endpoint::query()
393
+            ->where('attivita_id', $attivitaId)
394
+            ->when($userId !== null && $userId > 0, fn ($q) => $q->where('user_id', $userId))
395
+            ->withCount('stampanti')
396
+            ->orderBy('label')
397
+            ->get(['id', 'label', 'status', 'last_heartbeat', 'deeplink_token', 'product_key']);
250 398
 
251
-            $out[] = [
252
-                'id' => 0,
253
-                'nome' => 'Senza attività',
254
-                'punti_vendita' => $orphanDevices->whereIn('tipo', $pvTipi)->values()->map($serialize)->all(),
255
-                'cucine' => $cucineOrfane,
256
-                'altri' => $orphanDevices->whereNotIn('tipo', $pvTipi)->values()->map($serialize)->all(),
399
+        $endpointRows = $endpoints->map(function (Endpoint $ep) {
400
+            $meta = Endpoint::getStati()->get($ep->status);
401
+            $deeplink = is_array($ep->deeplink_token) ? $ep->deeplink_token : [];
402
+            $deeplinkStatus = $deeplink['status'] ?? null;
403
+            $deeplinkLabel = match ($deeplinkStatus) {
404
+                'paired' => 'Computer collegato',
405
+                'pending' => 'In attesa su FestAgent',
406
+                default => null,
407
+            };
408
+            $deeplinkClass = match ($deeplinkStatus) {
409
+                'paired' => 'bg-label-success',
410
+                'pending' => 'bg-label-warning',
411
+                default => 'bg-label-secondary',
412
+            };
413
+
414
+            return [
415
+                'id' => (int) $ep->id,
416
+                'label' => $ep->label ?: ('Endpoint #' . $ep->id),
417
+                'status' => (string) $ep->status,
418
+                'status_label' => $meta['label'] ?? $ep->status,
419
+                'status_class' => $meta['class'] ?? 'bg-label-secondary',
420
+                'stampanti_count' => (int) $ep->stampanti_count,
421
+                'last_heartbeat' => $ep->last_heartbeat?->diffForHumans(),
422
+                'deeplink_status' => $deeplinkStatus,
423
+                'deeplink_label' => $deeplinkLabel,
424
+                'deeplink_class' => $deeplinkClass,
425
+                'has_product_key' => ! empty($ep->product_key),
257 426
             ];
427
+        })->values()->all();
428
+
429
+        $puntiVendita = collect($att['punti_vendita'] ?? []);
430
+        $cucine = collect($att['cucine'] ?? []);
431
+
432
+        $pvSenzaEndpoint = $puntiVendita
433
+            ->filter(fn (array $pv) => empty($pv['endpoint_id']))
434
+            ->map(fn (array $pv) => ['id' => (int) $pv['id'], 'nome' => (string) $pv['nome']])
435
+            ->values()
436
+            ->all();
437
+
438
+        $pvSenzaStampante = $puntiVendita
439
+            ->filter(fn (array $pv) => empty($pv['stampante_cassa']))
440
+            ->map(fn (array $pv) => ['id' => (int) $pv['id'], 'nome' => (string) $pv['nome']])
441
+            ->values()
442
+            ->all();
443
+
444
+        $cucineSenzaStampante = $cucine
445
+            ->filter(fn (array $c) => empty($c['stampante']))
446
+            ->map(fn (array $c) => ['id' => (int) $c['id'], 'nome' => (string) $c['nome']])
447
+            ->values()
448
+            ->all();
449
+
450
+        $totStampanti = Dispositivo::query()
451
+            ->where('tipo', Dispositivo::STAMPANTE)
452
+            ->where('attivita_id', $attivitaId)
453
+            ->count();
454
+
455
+        $hasEndpoint = $endpoints->isNotEmpty();
456
+        $hasEndpointAttivo = $endpoints->contains(fn (Endpoint $e) => $e->status === Endpoint::ATTIVO);
457
+        $hasStampanti = $totStampanti > 0 || $endpoints->sum('stampanti_count') > 0;
458
+        $cucineOk = $cucine->isEmpty() || empty($cucineSenzaStampante);
459
+        $casseOk = $puntiVendita->isEmpty() || (empty($pvSenzaStampante) && empty($pvSenzaEndpoint));
460
+
461
+        $problemi = count($pvSenzaEndpoint) + count($pvSenzaStampante) + count($cucineSenzaStampante);
462
+        if (! $hasEndpoint) {
463
+            $problemi += 1;
464
+        } elseif (! $hasEndpointAttivo) {
465
+            $problemi += 1;
466
+        }
467
+        if (! $hasStampanti) {
468
+            $problemi += 1;
258 469
         }
259 470
 
260
-        return $out;
471
+        return [
472
+            'ready' => true,
473
+            'endpoints' => $endpointRows,
474
+            'tot_stampanti' => $totStampanti,
475
+            'tot_endpoint' => count($endpointRows),
476
+            'pv_senza_endpoint' => $pvSenzaEndpoint,
477
+            'pv_senza_stampante' => $pvSenzaStampante,
478
+            'cucine_senza_stampante' => $cucineSenzaStampante,
479
+            'tot_cucine' => $cucine->count(),
480
+            'tot_pv' => $puntiVendita->count(),
481
+            'checks' => [
482
+                'endpoint' => $hasEndpoint,
483
+                'endpoint_attivo' => $hasEndpointAttivo,
484
+                'stampanti' => $hasStampanti,
485
+                'cucine' => $cucineOk,
486
+                'casse' => $casseOk,
487
+            ],
488
+            'completato' => $hasEndpointAttivo && $hasStampanti && $cucineOk && $casseOk,
489
+            'problemi' => $problemi,
490
+        ];
261 491
     }
262 492
 }

+ 119
- 0
app/Http/Controllers/PrenotazioneTavoloController.php View File

@@ -0,0 +1,119 @@
1
+<?php
2
+
3
+namespace App\Http\Controllers;
4
+
5
+use Illuminate\Http\Request;
6
+use App\Models\PrenotazioneTavolo;
7
+use App\DataTables\PrenotazioneTavoloDataTable;
8
+use App\DataTables\PrenotazioneTavoloDataTableEditor;
9
+use Illuminate\Support\Facades\Auth;
10
+use App\Models\Attivita;
11
+use Illuminate\Support\Facades\Validator;
12
+use Illuminate\Support\Str;
13
+use Illuminate\Support\Facades\Session;
14
+
15
+class PrenotazioneTavoloController extends Controller
16
+{
17
+    public static $permission_group = "Prenotazioni";
18
+    public static $permissions = [
19
+        'view-prenotazione_tavolo' => 'Vedi',
20
+        'create-prenotazione_tavolo' => 'Crea',
21
+        'edit-prenotazione_tavolo' => 'Modifica',
22
+        'delete-prenotazione_tavolo' => 'Elimina',
23
+        'report-prenotazione_tavolo' => 'Report',
24
+    ];
25
+
26
+    public static function middleware(): array
27
+    {
28
+        return [
29
+            new Middleware('permission:view-prenotazione_tavolo', only: ['index']),
30
+            new Middleware('permission:create-prenotazione_tavolo|edit-prenotazione_tavolo|delete-prenotazione_tavolo', only: ['store', 'update', 'destroy']),
31
+        ];
32
+    }
33
+
34
+    public function index(PrenotazioneTavoloDataTable $dataTable)
35
+    {
36
+        $dataTable->attivita_id = Session::get('attivita_attuale');
37
+        return $dataTable->render('prenotazione_tavolo.index');
38
+    }
39
+    
40
+    public function store(PrenotazioneTavoloDataTableEditor $editor)
41
+    {
42
+        $request = request();
43
+        $input = $request->all();
44
+        if($request->has('action')){
45
+            switch($input['action']){
46
+                case 'create':
47
+                    if(!Auth::user()->can('create-prenotazione_tavolo')) return;
48
+                    break;
49
+                case 'edit':
50
+                    if(!Auth::user()->can('edit-prenotazione_tavolo')) return;
51
+                    break;
52
+                case 'delete':
53
+                    if(!Auth::user()->can('delete-prenotazione_tavolo')) return;
54
+                    break;
55
+            }
56
+        }
57
+        return $editor->process($request);
58
+    }
59
+
60
+    public function oggi(PrenotazioneTavoloDataTable $dataTable)
61
+    {
62
+        $dataTable->oggi = true;
63
+        $dataTable->attivita_id = Session::get('attivita_attuale');
64
+        return $dataTable->render('prenotazione_tavolo.oggi');
65
+    }
66
+
67
+    public function appelloPrenotazioniTavoli()
68
+    {
69
+        $prenotazioni = PrenotazioneTavolo::where('attivita_id', Session::get('attivita_attuale'))->where('data_prenotazione', Carbon::today())->get();
70
+        return view('prenotazione_tavolo.appello_prenotazioni_tavoli')->with(['prenotazioni' => $prenotazioni]);
71
+    }
72
+
73
+    public function cliente_index(Request $request){
74
+        $attivita = Attivita::find($request->attivita_id);
75
+        if(!$attivita) return back()->with('error' , 'Attività non trovata');
76
+        
77
+        return view('prenotazione_tavolo.cliente.index')->with('attivita' , $attivita);
78
+    }
79
+
80
+    public function cliente_check_disponibilita(Request $request){
81
+        $attivita = Attivita::find($request->attivita_id);
82
+        if(!$attivita) return back()->with('error' , 'Attività non trovata');
83
+        
84
+        $data_prenotazione = $request->data_prenotazione;
85
+        $ora_prenotazione = $request->ora_prenotazione;
86
+        $tavolo_id = $request->tavolo_id;
87
+
88
+        $prenotazione = PrenotazioneTavolo::where('attivita_id', $attivita->id)->where('data_prenotazione', $data_prenotazione)->where('ora_prenotazione', $ora_prenotazione)->where('tavolo_id', $tavolo_id)->first();
89
+        if($prenotazione) return back()->with('error' , 'Tavolo non disponibile');
90
+        
91
+        return back()->with('success' , 'Tavolo disponibile');
92
+    }
93
+    public function cliente_prenota(Request $request){
94
+        $attivita = Attivita::find($request->attivita_id);
95
+        if(!$attivita) return redirect()->route('attivita.index')->with('error', 'Prenotazine fallita: Attività non trovata');
96
+
97
+        $request->validate([
98
+            'nome' => 'required',
99
+            'email' => 'required|email',
100
+            'telefono' => 'required',
101
+            'tavolo_id' => 'required|exists:tavolo,id',
102
+            'data_prenotazione' => 'required|date',
103
+            'ora_prenotazione' => 'required',
104
+        ]);
105
+        $prenotazione = new PrenotazioneTavolo();
106
+        $prenotazione->fill($request->all());
107
+        $prenotazione->stato = PrenotazioneTavolo::STATO_INVIATA;
108
+        // dd($prenotazione);
109
+        $prenotazione->save();
110
+
111
+        return redirect()->route('cliente.prenotazione-tavolo.show')->with(['success' => 'Prenotazione del tavolo inviata con successo' , 'prenotazione_tavolo_id' => $prenotazione->id]);
112
+    }
113
+
114
+    public function cliente_show(Request $request, $prenotazione_tavoli_id){
115
+        $prenotazione = PrenotazioneTavolo::find($request->get('prenotazione_tavolo_id')|| $prenotazione_tavoli_id);
116
+        if(!$prenotazione) return redirect()->route('cliente.attivita.show')->with('error', 'Prenotazione non trovata');
117
+        return view('prenotazione_tavolo.cliente.show')->with(['prenotazione' => $prenotazione]);
118
+    }
119
+}

+ 169
- 45
app/Http/Controllers/PuntoVenditaController.php View File

@@ -5,14 +5,23 @@ namespace App\Http\Controllers;
5 5
 use App\DataTables\PuntoVenditaDataTable;
6 6
 use App\DataTables\PuntoVenditaDataTableEditor;
7 7
 use App\DataTables\RigaOrdineDataTable;
8
+
8 9
 use App\Models\Dispositivo;
9 10
 use App\Models\Ordine;
10 11
 use App\Models\Pagamento;
12
+use App\Models\RigaOrdine;
13
+use App\Models\RigaOrdineHasVariante;
14
+use App\Models\SaltacodaOrdine;
15
+use App\Models\SaltacodaRigaOrdine;
16
+use App\Models\SaltacodaRigaOrdineHasVariante;
17
+
18
+use Illuminate\Support\Str;
11 19
 use Illuminate\Http\Request;
20
+use Illuminate\Support\Facades\DB;
12 21
 use Illuminate\Support\Facades\Auth;
13 22
 use Illuminate\Support\Facades\Cookie;
14 23
 use Illuminate\Support\Facades\Session;
15
-use Illuminate\Support\Str;
24
+
16 25
 
17 26
 class PuntoVenditaController extends Controller
18 27
 {
@@ -35,6 +44,7 @@ class PuntoVenditaController extends Controller
35 44
 
36 45
     public function index(PuntoVenditaDataTable $dataTable)
37 46
     {
47
+        $dataTable->attivita_id = session('attivita_attuale');
38 48
         return $dataTable->render('punto_vendita.index');
39 49
     }
40 50
 
@@ -201,6 +211,17 @@ class PuntoVenditaController extends Controller
201 211
                 ->with('error', 'Punto di vendita non trovato');
202 212
         }
203 213
 
214
+        if($puntoVendita->attivita->user_id != Auth::user()->id){
215
+            session(['attivita_attuale' => null]);
216
+            Cookie::queue(Cookie::forget('binding_token'));
217
+            $puntoVendita->occupato = false;
218
+            $puntoVendita->binding_token = null;
219
+            $puntoVendita->save();
220
+
221
+            return redirect()->route('punto-vendita.index')
222
+                ->with('error', 'Non hai il permesso di accedere a questo punto di vendita');
223
+        }
224
+
204 225
         // se non esiste il binding_token nel DB, lo crea
205 226
         if ($puntoVendita->binding_token == '' || $puntoVendita->binding_token == null) {
206 227
             // $puntoVendita->binding_token = Str::random(32);
@@ -217,7 +238,7 @@ class PuntoVenditaController extends Controller
217 238
         switch (true) {
218 239
             // nessun cookie e il dispositivo non è occupato ACCEDI
219 240
             case ! $CookieBindingToken && $occupato == false:
220
-                Cookie::queue('binding_token', $puntoVendita->binding_token);
241
+                Cookie::queue('binding_token', $puntoVendita->binding_token, 60*12);
221 242
 
222 243
                 $puntoVendita->occupato = true;
223 244
                 $puntoVendita->save();
@@ -247,7 +268,7 @@ class PuntoVenditaController extends Controller
247 268
                         ->where('binding_token', $CookieBindingToken)
248 269
                         ->update(['occupato' => false]);
249 270
 
250
-                    Cookie::queue('binding_token', $puntoVendita->binding_token);
271
+                    Cookie::queue('binding_token', $puntoVendita->binding_token, 60*12);
251 272
                     $puntoVendita->occupato = true;
252 273
                     $puntoVendita->save();
253 274
                 }
@@ -418,7 +439,8 @@ class PuntoVenditaController extends Controller
418 439
     {
419 440
         $codice = $request->get('codice');
420 441
         // $pre_ordine = \App\Models\SaltacodaOrdine::where('codice' , $codice)->first();
421
-        $pre_ordine = \App\Models\SaltacodaOrdine::where('codice', 'like', '%PRE_'.strtoupper($codice).'%')->first();
442
+        // $pre_ordine = \App\Models\SaltacodaOrdine::where('codice', 'like', '%PRE_'.strtoupper($codice).'%')->first();
443
+        $pre_ordine = \App\Models\SaltacodaOrdine::where('codice', 'like', '%'.strtoupper($codice).'%')->first();
422 444
 
423 445
         // Se non trovato, prova con "PRE_" come prefisso se non già presente
424 446
         if (! $pre_ordine && stripos($codice, 'PRE_') !== 0) {
@@ -426,65 +448,167 @@ class PuntoVenditaController extends Controller
426 448
         }
427 449
 
428 450
         if (! $pre_ordine) {
429
-            return response()->json(['success' => false, 'message' => 'Pre-ordine non trovato']);
451
+            return view('punto_vendita.cassa._partials.pre_ordine', ['success' => false, 'error' => 'Pre-ordine non trovato']);
452
+
453
+            // return response()->json(['success' => false, 'message' => 'Pre-ordine non trovato']);
430 454
         }
431 455
 
432 456
         if (! (Session::has('attivita_attuale') && Session::get('attivita_attuale') != null && (int) Session::get('attivita_attuale') == (int) $pre_ordine->attivita_id)) {
433
-            return response()->json(['success' => false, 'message' => 'Pre-ordine non trovato']);
457
+            return view('punto_vendita.cassa._partials.pre_ordine', ['success' => false, 'error' => 'Attività non trovata']);
458
+    
459
+        // return response()->json(['success' => false, 'message' => 'Pre-ordine non trovato']);
434 460
         }
435 461
 
436 462
         return view('punto_vendita.cassa._partials.pre_ordine', ['pre_ordine' => $pre_ordine]);
437 463
 
438 464
     }
439 465
 
466
+
440 467
     public function importa_pre_ordine(Request $request)
441
-    {
468
+{
469
+    $codice = (string) $request->get('codice');
442 470
 
443
-        // dd($request->all());
444
-        $codice = $request->get('codice');
445
-        $pre_ordine = \App\Models\SaltacodaOrdine::where('codice', $codice)->first();
446
-        if (! $pre_ordine) {
447
-            return response()->json(['success' => false, 'message' => 'Pre-ordine non trovato']);
448
-        }
471
+    if (! Session::has('attivita_attuale') || Session::get('attivita_attuale') === null) {
472
+        return response()->json(['success' => false, 'message' => 'Attività non trovata']);
473
+    }
449 474
 
450
-        if (! (Session::has('attivita_attuale') && Session::get('attivita_attuale') != null && (int) Session::get('attivita_attuale') == (int) $pre_ordine->attivita_id)) {
451
-            return response()->json(['success' => false, 'message' => 'Attività non trovata']);
452
-        }
475
+    try {
476
+        return DB::transaction(function () use ($request, $codice) {
477
+            $pre_ordine = SaltacodaOrdine::where('codice', $codice)
478
+                ->with('saltacoda_riga_ordine')
479
+                ->lockForUpdate()
480
+                ->first();
453 481
 
454
-        // $ordine = Ordine::where('id', $request->ordine_id)
455
-        // ->where('stato', Ordine::CARRELLO)
456
-        // ->where('dispositivo_id', $request->dispositivo_id)
457
-        // ->where('attivita_id', $request->attivita_id)
458
-        // ->first();
482
+            if (! $pre_ordine) {
483
+                return response()->json(['success' => false, 'message' => 'Pre-ordine non trovato']);
484
+            }
459 485
 
460
-        $ordine = Ordine::find($request->ordine_id);
486
+            if ((int) Session::get('attivita_attuale') !== (int) $pre_ordine->attivita_id) {
487
+                return response()->json(['success' => false, 'message' => 'Attività non trovata']);
488
+            }
461 489
 
462
-        if (! $ordine) {
463
-            return response()->json(['success' => false, 'message' => 'Ordine non trovato']);
464
-        }
490
+            $ordine = Ordine::lockForUpdate()->find($request->ordine_id);
465 491
 
466
-        if ($ordine->stato != Ordine::CARRELLO) {
467
-            return response()->json(['success' => false, 'message' => 'Ordine non è in carrello']);
468
-        }
492
+            if (! $ordine || $ordine->stato !== Ordine::CARRELLO) {
493
+                return response()->json(['success' => false, 'message' => 'Ordine non valido']);
494
+            }
469 495
 
470
-        // compila ordine con i dati del pre-ordine
471
-        $ordine->info = $pre_ordine->info;
472
-        $ordine->save();
473
-
474
-        foreach ($pre_ordine->riga_ordine as $riga_ordine) {
475
-            \App\Models\RigaOrdine::create([
476
-                'ordine_id' => $ordine->id,
477
-                'piatto_id' => $riga_ordine->piatto_id,
478
-                'quantita' => $riga_ordine->quantita,
479
-                'prezzo' => $riga_ordine->prezzo,
480
-                'note' => $riga_ordine->note,
481
-            ]);
482
-        }
496
+            $isPrimoImport = $pre_ordine->stato === SaltacodaOrdine::PRE_ORDINE;
497
+            $isRipristino = $pre_ordine->stato === SaltacodaOrdine::PRE_CARRELLO
498
+                && (int) data_get($ordine->info, 'pre_ordine_id') === (int) $pre_ordine->id;
499
+
500
+            if (! $isPrimoImport && ! $isRipristino) {
501
+                if ($pre_ordine->stato === SaltacodaOrdine::IMPORTATO) {
502
+                    return response()->json(['success' => false, 'message' => 'Pre-ordine già chiuso']);
503
+                }
504
+
505
+                return response()->json([
506
+                    'success' => false,
507
+                    'message' => 'Pre-ordine già associato ad un altro carrello',
508
+                ]);
509
+            }
483 510
 
484
-        // elimina pre-ordine
485
-        $pre_ordine->stato = \App\Models\SaltacodaOrdine::IMPORTATO;
486
-        $pre_ordine->save();
511
+            if ($isPrimoImport) {
512
+                $pre_ordine->stato = SaltacodaOrdine::PRE_CARRELLO;
513
+                $pre_ordine->save();
514
+            }
515
+
516
+            if ($isRipristino) {
517
+                RigaOrdine::where('ordine_id', $ordine->id)->delete();
518
+            }
487 519
 
488
-        return response()->json(['success' => true, 'message' => 'Pre-ordine importato', 'data' => $pre_ordine]);
520
+            $info = is_array($ordine->info) ? $ordine->info : [];
521
+            $infoPreOrdine = is_array($pre_ordine->info) ? $pre_ordine->info : [];
522
+
523
+            $ordine->info = array_merge($infoPreOrdine, $info, [
524
+                'pre_ordine_codice' => $pre_ordine->codice,
525
+                'pre_ordine_id'     => $pre_ordine->id,
526
+            ]);
527
+            $ordine->save();
528
+
529
+            foreach ($pre_ordine->saltacoda_riga_ordine as $pre_riga_ordine) {
530
+                $riga_ordine = RigaOrdine::create([
531
+                                    'ordine_id' => $ordine->id,
532
+                                    'piatto_id' => $pre_riga_ordine->piatto_id,
533
+                                    'quantita' => $pre_riga_ordine->quantita,
534
+                                    'prezzo' => $pre_riga_ordine->prezzo,
535
+                                    'note' => $pre_riga_ordine->note,
536
+                                ]);
537
+
538
+                //Conotrollo se ha varianti
539
+                if ($pre_riga_ordine->saltacoda_has_variante->isNotEmpty()) {
540
+                    foreach ($pre_riga_ordine->saltacoda_has_variante as $variante) {
541
+                        RigaOrdineHasVariante::create([
542
+                            'riga_ordine_id' => $riga_ordine->id,
543
+                            'variante_id' => $variante->variante_id,
544
+                            'nota' => $variante->nota,
545
+                        ]);
546
+                    }
547
+                }
548
+            }
549
+
550
+            return response()->json([
551
+                'success' => true,
552
+                'message' => $isRipristino ? 'Pre-ordine ripristinato' : 'Pre-ordine importato',
553
+                'data' => $pre_ordine->fresh(),
554
+            ]);
555
+        });
556
+    } catch (\Throwable $e) {
557
+        return response()->json([
558
+            'success' => false,
559
+            'message' => 'Errore durante l\'importazione del pre-ordine',
560
+            'error' => $e->getMessage(),
561
+        ], 500);
489 562
     }
563
+}
564
+    // public function importa_pre_ordine(Request $request)
565
+    // {
566
+
567
+    //     // dd($request->all());
568
+    //     $codice = $request->get('codice');
569
+    //     $pre_ordine = \App\Models\SaltacodaOrdine::where('codice', $codice)->first();
570
+    //     if (! $pre_ordine) {
571
+    //         return response()->json(['success' => false, 'message' => 'Pre-ordine non trovato']);
572
+    //     }
573
+
574
+    //     if (! (Session::has('attivita_attuale') && Session::get('attivita_attuale') != null && (int) Session::get('attivita_attuale') == (int) $pre_ordine->attivita_id)) {
575
+    //         return response()->json(['success' => false, 'message' => 'Attività non trovata']);
576
+    //     }
577
+
578
+    //     // $ordine = Ordine::where('id', $request->ordine_id)
579
+    //     // ->where('stato', Ordine::CARRELLO)
580
+    //     // ->where('dispositivo_id', $request->dispositivo_id)
581
+    //     // ->where('attivita_id', $request->attivita_id)
582
+    //     // ->first();
583
+
584
+    //     $ordine = Ordine::find($request->ordine_id);
585
+
586
+    //     if (! $ordine) {
587
+    //         return response()->json(['success' => false, 'message' => 'Ordine non trovato']);
588
+    //     }
589
+
590
+    //     if ($ordine->stato != Ordine::CARRELLO) {
591
+    //         return response()->json(['success' => false, 'message' => 'Ordine non è in carrello']);
592
+    //     }
593
+
594
+    //     // compila ordine con i dati del pre-ordine
595
+    //     $ordine->info = merge_array($ordine->info, $pre_ordine->info); 
596
+    //     $ordine->save();
597
+
598
+    //     foreach ($pre_ordine->riga_ordine as $riga_ordine) {
599
+    //         \App\Models\RigaOrdine::create([
600
+    //             'ordine_id' => $ordine->id,
601
+    //             'piatto_id' => $riga_ordine->piatto_id,
602
+    //             'quantita' => $riga_ordine->quantita,
603
+    //             'prezzo' => $riga_ordine->prezzo,
604
+    //             'note' => $riga_ordine->note,
605
+    //         ]);
606
+    //     }
607
+
608
+    //     // elimina pre-ordine
609
+    //     $pre_ordine->stato = \App\Models\SaltacodaOrdine::PRE_CARRELLO;
610
+    //     $pre_ordine->save();
611
+
612
+    //     return response()->json(['success' => true, 'message' => 'Pre-ordine importato', 'data' => $pre_ordine]);
613
+    // }
490 614
 }

+ 81
- 0
app/Http/Controllers/RigaFatturazioneController.php View File

@@ -0,0 +1,81 @@
1
+<?php
2
+
3
+namespace App\Http\Controllers;
4
+
5
+use Illuminate\Http\Request;
6
+use Illuminate\Support\Facades\Storage;
7
+use Illuminate\Support\Facades\Validator;
8
+use Illuminate\Support\Facades\DB;
9
+use Illuminate\Support\Facades\Auth;
10
+use App\Models\RigaFatturazione;
11
+use App\Models\Attivita;
12
+use App\Models\Dispositivo;
13
+use App\DataTables\RigaFatturazioneDataTable;
14
+use App\DataTables\RigaFatturazioneDataTableEditor;
15
+use Illuminate\Support\Facades\Session;
16
+
17
+class RigaFatturazioneController extends Controller
18
+{
19
+    public static $permission_group = 'riga_fatturazione';
20
+    Public static $permissions = [
21
+        'view-riga_fatturazione' => 'Vedi',
22
+        'create-riga_fatturazione' => 'Crea',
23
+        'edit-riga_fatturazione' => 'Modifica',
24
+        'delete-riga_fatturazione' => 'Elimina',
25
+    ];
26
+
27
+    public static function middleware(): array
28
+    {
29
+        return [
30
+            new Middleware('permission:view-riga_fatturazione', only: ['index']),
31
+            new Middleware('permission:create-riga_fatturazione|edit-riga_fatturazione|delete-riga_fatturazione', only: ['store', 'update', 'destroy']),
32
+        ];
33
+    }
34
+
35
+    public function index(RigaFatturazioneDataTable $dataTable, Request $request)
36
+    {
37
+        $attivita_user_id = null;
38
+        if(Session::has('attivita_attuale')){
39
+            $dataTable->attivita_id = Session::get('attivita_attuale');
40
+            $attivita_user_id = Attivita::find(Session::get('attivita_attuale'))->user_id;
41
+        }
42
+
43
+        if(Auth::user()->hasRole('superadmin')){
44
+            $dataTable->superadmin = true;
45
+        }
46
+
47
+        if($request->has('tutti_i_consumi')){
48
+            $dataTable->tutti_i_consumi = true;
49
+        }
50
+        $fattureEmesse = \App\Models\Fattura::where('user_id', $attivita_user_id)->where('stato', \App\Models\Fattura::EMESSA)->get();
51
+
52
+        return $dataTable->render('fatturazione.index',['fattureEmesse' => $fattureEmesse]);
53
+    }
54
+
55
+    public function store(RigaFatturazioneDataTableEditor $editor)
56
+    {
57
+        // $editor->user_id = Auth::user()->id;
58
+        // if($request->has('attivita_id')){
59
+        //     $editor->attivita_id = $request->attivita_id;
60
+        // }
61
+
62
+        $request = request();
63
+        $input = $request->all();
64
+        if($request->has('action')){
65
+            switch($input['action']){
66
+                case 'create':
67
+                    if(!Auth::user()->can('create-riga_fatturazione')) return response()->json(['error' => 'Non hai i permessi per creare una riga fatturazione'],403);
68
+                    break;
69
+                case 'edit':
70
+                    if(!Auth::user()->can('edit-riga_fatturazione')) return response()->json(['error' => 'Non hai i permessi per modificare una riga fatturazione'],403);
71
+                    break;
72
+                case 'remove':
73
+                    if(!Auth::user()->can('delete-riga_fatturazione')) return response()->json(['error' => 'Non hai i permessi per eliminare una riga fatturazione'],403);
74
+                    break;
75
+            }
76
+        }
77
+        return $editor->process($request);
78
+    }
79
+    
80
+    
81
+}

+ 154
- 52
app/Http/Controllers/SaltacodaController.php View File

@@ -9,9 +9,9 @@ use App\Models\Piatto;
9 9
 use App\Models\Saltacoda;
10 10
 use App\Models\SaltacodaOrdine;
11 11
 use App\Models\SaltacodaRigaordine;
12
-use Illuminate\Support\Str;
13
-
14
-use Illuminate\Support\Facades\Auth;
12
+use App\Models\VariantePiatto;
13
+use App\Models\SaltacodaRigaOrdineHasVariante;
14
+use Illuminate\Support\Facades\DB;
15 15
 
16 16
 
17 17
 class SaltacodaController extends Controller
@@ -73,27 +73,18 @@ class SaltacodaController extends Controller
73 73
             'is_attivo' => 'required|boolean',
74 74
             'max_ordini' => 'nullable|integer|min:0',
75 75
             'info' => 'nullable|json',
76
-            'note' => 'nullable|array',
77
-            'note.*.label' => 'required_with:note|string|max:255',
78
-            'note.*.value' => 'nullable|string|max:5000',
76
+            'istruzioni' => 'nullable|json',
79 77
         ]);
80 78
 
81
-        $noteJson = collect($request->input('note', []))
82
-            ->map(fn (array $row) => [
83
-                'label' => trim((string) ($row['label'] ?? '')),
84
-                'value' => trim((string) ($row['value'] ?? '')),
85
-            ])
86
-            ->filter(fn (array $row) => $row['label'] !== '')
87
-            ->values()
88
-            ->all();
89
-
90 79
         $saltacoda = Saltacoda::updateOrCreate(
91 80
             ['attivita_id' => $request->attivita_id],
92 81
             [
93 82
                 'is_attivo' => $request->is_attivo,
94 83
                 'max_ordini' => $request->max_ordini,
95 84
                 'info' => $request->info,
96
-                'note' => $noteJson !== [] ? json_encode($noteJson, JSON_UNESCAPED_UNICODE) : null,
85
+                'istruzioni' => $request->filled('istruzioni')
86
+                    ? Saltacoda::normalizeIstruzioni($request->istruzioni)
87
+                    : Saltacoda::defaultIstruzioni(),
97 88
             ]
98 89
         );
99 90
         return redirect()->route('saltacoda.index')->with('success', 'Saltacoda salvato');
@@ -122,10 +113,36 @@ class SaltacodaController extends Controller
122 113
                 ->where('is_attiva', true)
123 114
                 ->with(['piatti' => function ($q) use ($attivita) {
124 115
                     $q->where('attivita_id', $attivita->id)
125
-                    ->where('is_attivo', true);
116
+                    ->where('is_attivo', true)
117
+                    ->orderBy('nome');
126 118
                 }])
127 119
                 ->get();
128 120
 
121
+        $variantiPerPiatto = [];
122
+        $variantiPerCucina = [];
123
+        $varianti = VariantePiatto::query()
124
+            ->where('attivita_id', $attivita->id)
125
+            ->where('is_attivo', true)
126
+            ->orderBy('label')
127
+            ->get(['id', 'piatto_id', 'cucina_id', 'label', 'prezzo']);
128
+
129
+        foreach ($varianti as $variante) {
130
+            $entry = [
131
+                'id' => $variante->id,
132
+                'label' => $variante->label,
133
+                'prezzo' => (float) $variante->prezzo,
134
+            ];
135
+
136
+            if ($variante->piatto_id) {
137
+                $variantiPerPiatto[$variante->piatto_id][] = $entry;
138
+                continue;
139
+            }
140
+
141
+            if ($variante->cucina_id) {
142
+                $variantiPerCucina[$variante->cucina_id][] = $entry;
143
+            }
144
+        }
145
+
129 146
         $campiInfo = [];
130 147
         $rawInfo = $saltacoda->info;
131 148
         if (is_string($rawInfo) && $rawInfo !== '') {
@@ -164,6 +181,8 @@ class SaltacodaController extends Controller
164 181
             'cucine' => $cucine,
165 182
             'campi_info' => $campiInfo,
166 183
             'saltacoda_id' => $saltacoda->id,
184
+            'varianti_per_piatto' => $variantiPerPiatto,
185
+            'varianti_per_cucina' => $variantiPerCucina,
167 186
         ]);
168 187
     }
169 188
 
@@ -176,6 +195,8 @@ class SaltacodaController extends Controller
176 195
             'riga_ordine.*.piatto_id' => 'required|exists:piatto,id',
177 196
             'riga_ordine.*.qta' => 'required|integer|min:1',
178 197
             'riga_ordine.*.note' => 'nullable|string|max:500',
198
+            'riga_ordine.*.varianti' => 'nullable|array',
199
+            'riga_ordine.*.varianti.*' => 'integer|exists:variante_piatto,id',
179 200
             'info_cliente' => 'nullable|array',
180 201
             'idempotency_key' => 'nullable|string|max:120',
181 202
         ]);
@@ -190,58 +211,139 @@ class SaltacodaController extends Controller
190 211
             ], 422);
191 212
         }
192 213
 
193
-// dd($request->all());
214
+        try {
215
+            $saltacoda_ordine = DB::transaction(function () use ($request) {
216
+                $saltacoda_ordine = SaltacodaOrdine::firstOrNew([
217
+                    'riferimento' => $request->idempotency_key,
218
+                ]);
194 219
 
195
-        //Ordine Saltacoda
196
-        $saltacoda_ordine = SaltacodaOrdine::updateOrCreate(
197
-            ['riferimento' => $request->idempotency_key],
198
-            [
199
-                'riferimento' => $request->idempotency_key,
200
-                'attivita_id' => $request->attivita_id,
201
-                'info' => $request->info_cliente,
202
-                'prenotazione_id' => null,
203
-                'stato' => SaltacodaOrdine::PRE_ORDINE,
204
-                'pagamento_id' => null,
205
-                'attivita_id' => $request->attivita_id,
206
-                'note' => null,
207
-                'codice' => "PRE_".strtoupper(Str::random(5)),
208
-                'tipo' => null,
209
-                'prezzo' => 0,
210
-                'metodo_pagamento_id' => null,
211
-                'info' => $request->info_cliente,
212
-            ]);
220
+                if (!$saltacoda_ordine->exists) {
221
+                    $saltacoda_ordine->codice = $this->genera_codice_saltacoda();
222
+                }
213 223
 
214
-            //Riga Ordine Saltacoda
215
-            foreach ($request->riga_ordine as $riga) {
216
-                SaltacodaRigaordine::create([
217
-                    'saltacoda_ordine_id' => $saltacoda_ordine->id,
218
-                    'piatto_id' => $riga['piatto_id'],
219
-                    'quantita' => $riga['qta'],
220
-                    'note' => $riga['note'],
221
-                    'prezzo' => (Piatto::find($riga['piatto_id'])->prezzo*$riga['qta']),
224
+                $saltacoda_ordine->fill([
225
+                    'riferimento' => $request->idempotency_key,
226
+                    'attivita_id' => $request->attivita_id,
227
+                    'info' => $request->info_cliente,
228
+                    'prenotazione_id' => null,
229
+                    'stato' => SaltacodaOrdine::PRE_ORDINE,
230
+                    'pagamento_id' => null,
231
+                    'note' => null,
232
+                    'tipo' => null,
233
+                    'prezzo' => 0,
234
+                    'metodo_pagamento_id' => null,
222 235
                 ]);
223
-            }
236
+                $saltacoda_ordine->save();
237
+
238
+                $saltacoda_ordine->saltacoda_riga_ordine()->delete();
239
+
240
+                foreach ($request->riga_ordine as $riga) {
241
+                    $piatto = Piatto::query()
242
+                        ->where('id', $riga['piatto_id'])
243
+                        ->where('attivita_id', $request->attivita_id)
244
+                        ->first();
245
+
246
+                    if (!$piatto) {
247
+                        throw new \InvalidArgumentException('Piatto non valido per questa attività.');
248
+                    }
249
+
250
+                    $qta = (int) $riga['qta'];
251
+                    $varianteIds = collect($riga['varianti'] ?? [])->unique()->values();
252
+                    $varianti = $varianteIds->isEmpty()
253
+                        ? collect()
254
+                        : VariantePiatto::query()
255
+                            ->where('attivita_id', $request->attivita_id)
256
+                            ->where('is_attivo', true)
257
+                            ->whereIn('id', $varianteIds)
258
+                            ->get();
259
+
260
+                    if ($varianteIds->count() !== $varianti->count()) {
261
+                        throw new \InvalidArgumentException('Una o più varianti non sono valide per questa attività.');
262
+                    }
263
+
264
+                    $prezzoRiga = $this->calcolaPrezzoRigaSaltacoda($piatto, $qta, $varianti);
265
+
266
+                    $saltacoda_riga = SaltacodaRigaordine::create([
267
+                        'saltacoda_ordine_id' => $saltacoda_ordine->id,
268
+                        'piatto_id' => $piatto->id,
269
+                        'quantita' => $qta,
270
+                        'note' => $riga['note'] ?? null,
271
+                        'prezzo' => $prezzoRiga,
272
+                    ]);
273
+
274
+                    foreach ($varianti as $variante) {
275
+                        SaltacodaRigaOrdineHasVariante::create([
276
+                            'saltacoda_riga_ordine_id' => $saltacoda_riga->id,
277
+                            'variante_id' => $variante->id,
278
+                            'nota' => null,
279
+                        ]);
280
+                    }
281
+                }
224 282
 
225
-            //Aggiorna prezzo totale ordine
226
-            $saltacoda_ordine->prezzo = $saltacoda_ordine->riga_ordine->sum('prezzo');
227
-            $saltacoda_ordine->save();
283
+                $saltacoda_ordine->prezzo = $saltacoda_ordine->saltacoda_riga_ordine()->sum('prezzo');
284
+                $saltacoda_ordine->save();
285
+
286
+                return $saltacoda_ordine->load([
287
+                    'saltacoda_riga_ordine.piatto',
288
+                    'saltacoda_riga_ordine.saltacoda_has_variante.variante_piatto',
289
+                ]);
290
+            });
228 291
 
229
-            // dd($saltacoda_ordine);
230 292
             return response()->json([
231 293
                 'success' => true,
232 294
                 'message' => 'Pre-ordine salvato',
233 295
                 'data' => $saltacoda_ordine,
234 296
             ]);
235
-
236
-
297
+        } catch (\InvalidArgumentException $exception) {
298
+            return response()->json([
299
+                'success' => false,
300
+                'message' => $exception->getMessage(),
301
+            ], 422);
302
+        }
237 303
     }
238 304
 
239 305
     public function pre_ordine_show(Request $request)
240 306
     {
241
-        $saltacoda_ordine = SaltacodaOrdine::where('codice', $request->get('codice'))->first();
307
+        $saltacoda_ordine = SaltacodaOrdine::query()
308
+            ->with([
309
+                'saltacoda_riga_ordine.piatto',
310
+                'saltacoda_riga_ordine.saltacoda_has_variante.variante_piatto',
311
+                'attivita',
312
+            ])
313
+            ->where('codice', $request->get('codice'))
314
+            ->first();
242 315
         if(!$saltacoda_ordine) {
243 316
             return abort(404);
244 317
         }
245 318
         return view('saltacoda.pre_ordine', ['saltacoda_ordine' => $saltacoda_ordine]);
246 319
     }
320
+
321
+    private function calcolaPrezzoRigaSaltacoda(Piatto $piatto, int $qta, $varianti): float
322
+    {
323
+        $prezzo = (float) $piatto->prezzo * $qta;
324
+
325
+        foreach ($varianti as $variante) {
326
+            $extra = (float) ($variante->prezzo ?? 0);
327
+            if ($extra !== 0.0) {
328
+                $prezzo += $extra * $qta;
329
+            }
330
+        }
331
+
332
+        return round($prezzo, 2);
333
+    }
334
+
335
+    private function genera_codice_saltacoda()
336
+    {
337
+        $lettereSicure = ['A', 'C', 'E', 'F', 'G', 'L', 'M', 'N', 'R', 'S', 'U', 'Z'];
338
+        $parteLettera = $lettereSicure[rand(0, count($lettereSicure) - 1)];
339
+        $parteCifre2 = str_pad(strval(rand(0, 99)), 2, '0', STR_PAD_LEFT);
340
+        $parteCifre3 = str_pad(strval(rand(0, 999)), 3, '0', STR_PAD_LEFT);
341
+        $codice = strtoupper($parteLettera . $parteCifre2 . '-' . $parteCifre3);
342
+
343
+        if (SaltacodaOrdine::where('codice', $codice)->exists()) {
344
+            return $this->genera_codice_saltacoda();
345
+        }
346
+
347
+        return $codice;
348
+    }
247 349
 }

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

@@ -50,4 +50,5 @@ class TavoloController extends Controller
50 50
         }
51 51
         return $editor->process($request);
52 52
     }
53
+
53 54
 }

+ 54
- 1
app/Http/Controllers/UserController.php View File

@@ -13,6 +13,8 @@ use Illuminate\Support\Facades\Auth;
13 13
 use Notification;
14 14
 use Session;
15 15
 use PDF;
16
+use Illuminate\Auth\SessionGuard;
17
+use Illuminate\Support\Facades\Cookie;
16 18
 use Illuminate\Support\Facades\Storage;
17 19
 use Illuminate\Support\Str;
18 20
 use Carbon\Carbon;
@@ -50,8 +52,59 @@ class UserController extends Controller implements HasMiddleware
50 52
     return [
51 53
       new Middleware('permission:view-user', only: ['index']),
52 54
       new Middleware('permission:create-user|edit-user|remove-user', only: ['store', 'admin_aggiorna']),
55
+      new Middleware('role:superadmin', only: ['superadmin_impersonate']),
53 56
     ];
54 57
   }
58
+  public function superadmin_impersonate(Request $request){
59
+      abort_unless(Auth::user()->hasRole('superadmin'), 403);
60
+  
61
+      $user = User::findOrFail($request->user_id);
62
+      $user->refresh();
63
+  
64
+      $impersonatorId = Auth::id();
65
+  
66
+      /** @var SessionGuard $guard */
67
+      $guard = Auth::guard('web');
68
+      $guard->login($user);
69
+      Auth::shouldUse('web');
70
+  
71
+      $request->session()->put([
72
+        'segresta_impersonating' => true,
73
+        'segresta_impersonator_id' => $impersonatorId,
74
+        'segresta_impersonated_id' => $user->id,
75
+      ]);
76
+      $request->session()->forget('login.id');
77
+  
78
+      // Allinea password_hash_* come storePasswordHashInSession di Jetstream (hashPasswordForCookie se disponibile)
79
+      // per web e sanctum (dopo auth:sanctum il default guard è sanctum → password_hash_sanctum).
80
+      $plain = $user->getAuthPassword();
81
+      $stored = $plain;
82
+      if ($plain) {
83
+        try {
84
+          $stored = $guard->hashPasswordForCookie($plain);
85
+        } catch (BadMethodCallException) {
86
+        }
87
+      }
88
+      $guardNames = array_unique(array_merge(
89
+        ['web', 'sanctum'],
90
+        (array) config('sanctum.guard', ['web']),
91
+        array_keys(config('auth.guards', []))
92
+      ));
93
+      foreach ($guardNames as $guardName) {
94
+        $key = 'password_hash_'.$guardName;
95
+        if ($plain) {
96
+          $request->session()->put($key, $stored);
97
+        } else {
98
+          $request->session()->forget($key);
99
+        }
100
+      }
101
+  
102
+      $request->session()->save();
103
+  
104
+      // Cookie "ricordami" dell'admin: AuthenticateSession lo confronta con la password dell'utente impersonato → logout.
105
+      // Prima GET dopo impersonate: /admin/dashboard (senza middleware "verified"); da lì eventuale redirect a /dashboard.
106
+      return redirect()->route('dashboard')->withCookie(Cookie::forget($guard->getRecallerName()));
107
+    }
55 108
 
56 109
   public function index(UserDataTable $dataTable)
57 110
   {
@@ -167,4 +220,4 @@ class UserController extends Controller implements HasMiddleware
167 220
           }
168 221
 
169 222
 
170
-        }
223
+}

+ 68
- 0
app/Models/AbstractModels/AbstractAsporto.php View File

@@ -0,0 +1,68 @@
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 AbstractAsporto extends \Illuminate\Foundation\Auth\User
10
+{
11
+    /**  
12
+     * The table associated with the model.
13
+     * 
14
+     * @var string
15
+     */
16
+    protected $table = 'asporto';
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
+        'ordine_id' => 'integer',
40
+        'stato' => 'string',
41
+        'ora_ritiro' => 'datetime',
42
+        'info' => 'json',
43
+        'created_at' => 'datetime',
44
+        'updated_at' => 'datetime'
45
+    ];
46
+    
47
+    /**  
48
+     * The attributes that are mass assignable.
49
+     * 
50
+     * @var array
51
+     */
52
+    protected $fillable = [
53
+        'id',
54
+        'ordine_id',
55
+        'stato',
56
+        'ora_ritiro',
57
+        'info',
58
+        'posti',
59
+        'created_at', 
60
+        'updated_at'
61
+    ];
62
+
63
+
64
+    public function ordine()
65
+    {
66
+        return $this->belongsTo('\App\Models\Ordine', 'ordine_id', 'id');
67
+    }
68
+}

+ 2
- 0
app/Models/AbstractModels/AbstractEndpoint.php View File

@@ -50,6 +50,7 @@ abstract class AbstractEndpoint extends \Illuminate\Foundation\Auth\User
50 50
         'version' => 'string',
51 51
         'licenza_id' => 'integer',
52 52
         'info' => 'json',
53
+        'deeplink_token' => 'json',
53 54
         'last_heartbeat' => 'datetime',
54 55
         'last_activity' => 'datetime',
55 56
         'created_at' => 'datetime',
@@ -77,6 +78,7 @@ abstract class AbstractEndpoint extends \Illuminate\Foundation\Auth\User
77 78
         'version',
78 79
         'licenza_id',
79 80
         'info',
81
+        'deeplink_token',
80 82
         'last_heartbeat',
81 83
         'last_activity',
82 84
         'created_at',

+ 98
- 0
app/Models/AbstractModels/AbstractFattura.php View File

@@ -0,0 +1,98 @@
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 AbstractFattura extends \Illuminate\Foundation\Auth\User
10
+{
11
+    /**  
12
+     * The table associated with the model.
13
+     * 
14
+     * @var string
15
+     */
16
+    protected $table = 'fattura';
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
+        'user_id' => 'integer',
40
+        'attivita_id' => 'integer',
41
+        'numero_fattura' => 'string',
42
+        'data_fattura' => 'date',
43
+        'stato' => 'string',
44
+        'note' => 'string',
45
+        'path_pdf' => 'string',
46
+        'path_xml' => 'string',
47
+        'metadata' => 'array',
48
+        'totale_imponibile' => 'decimal:2',
49
+        'totale_netto' => 'decimal:2',
50
+        'totale_iva' => 'decimal:2',
51
+        'totale_lordo' => 'decimal:2',
52
+        'sconto' => 'decimal:2',
53
+        'info' => 'array',
54
+        'created_at' => 'datetime',
55
+        'updated_at' => 'datetime',
56
+        ];
57
+    
58
+    /**  
59
+     * The attributes that are mass assignable.
60
+     * 
61
+     * @var array
62
+     */
63
+    protected $fillable = [
64
+        'id',
65
+        'user_id',
66
+        'attivita_id',
67
+        'numero_fattura',
68
+        'data_fattura',
69
+        'stato',
70
+        'note',
71
+        'path_pdf',
72
+        'path_xml',
73
+        'metadata',
74
+        'totale_imponibile',
75
+        'totale_netto',
76
+        'totale_iva',
77
+        'totale_lordo',
78
+        'sconto',
79
+        'info',
80
+        'created_at',
81
+        'updated_at'
82
+    ];
83
+    
84
+    public function attivita()
85
+    {
86
+        return $this->belongsTo('\App\Models\Attivita', 'attivita_id', 'id');
87
+    }
88
+
89
+    public function user()
90
+    {
91
+        return $this->belongsTo('\App\Models\User', 'user_id', 'id');
92
+    }
93
+    public function righeFatturazione()
94
+    {
95
+        return $this->hasMany('\App\Models\RigaFatturazione', 'fattura_id', 'id');
96
+    }
97
+    
98
+}

+ 74
- 0
app/Models/AbstractModels/AbstractListinoPrezzi.php View File

@@ -0,0 +1,74 @@
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 AbstractListinoPrezzi extends \Illuminate\Foundation\Auth\User
10
+{
11
+    /**  
12
+     * The table associated with the model.
13
+     * 
14
+     * @var string
15
+     */
16
+    protected $table = 'listino_prezzi';
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
+        'categoria' => 'string',
40
+        'label' => 'string',
41
+        'descrizione' => 'string',
42
+        'tipo_dispositivo' => 'string',
43
+        'prezzo' => 'decimal:2',
44
+        'tipo_prezzo' => 'string',
45
+        'note' => 'string',
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
+        'categoria',
58
+        'label',
59
+        'descrizione',
60
+        'tipo_dispositivo',
61
+        'prezzo',
62
+        'tipo_prezzo',
63
+        'note',
64
+        'created_at',
65
+        'updated_at'
66
+    ];
67
+    
68
+
69
+    public function riga_fatturazione()
70
+    {
71
+        return $this->hasMany('\App\Models\RigaFatturazione', 'listino_prezzi_id');
72
+    }
73
+
74
+}

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

@@ -129,4 +129,9 @@ abstract class AbstractOrdine extends \Illuminate\Foundation\Auth\User
129 129
     {
130 130
         return $this->hasMany('\App\Models\PrintJob', 'ordine_id', 'id');
131 131
     }
132
+
133
+    public function asporto()
134
+    {
135
+        return $this->hasOne('\App\Models\Asporto', 'ordine_id', 'id');
136
+    }
132 137
 }

+ 83
- 0
app/Models/AbstractModels/AbstractPrenotazioneTavolo.php View File

@@ -0,0 +1,83 @@
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 AbstractPrenotazioneTavolo extends \Illuminate\Foundation\Auth\User
10
+{
11
+    /**  
12
+     * The table associated with the model.
13
+     * 
14
+     * @var string
15
+     */
16
+    protected $table = 'prenotazione_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
+        'tavolo_id' => 'integer',
40
+        'nome' => 'string',
41
+        'email' => 'string',
42
+        'telefono' => 'string',
43
+        'note' => 'string',
44
+        'stato' => 'string',
45
+        'info' => 'json',
46
+        'attivita_id' => 'integer',
47
+        'created_at' => 'datetime',
48
+        'updated_at' => 'datetime'
49
+    ];
50
+    
51
+    /**  
52
+     * The attributes that are mass assignable.
53
+     * 
54
+     * @var array
55
+     */
56
+    protected $fillable = [
57
+        'id',   
58
+        'tavolo_id',
59
+        'nome',
60
+        'email',
61
+        'telefono',
62
+        'note',
63
+        'stato',
64
+        'data_prenotazione',
65
+        'ora_prenotazione',
66
+        'coperti',
67
+        'trattamento_dati',
68
+        'info',
69
+        'attivita_id',
70
+        'created_at', 
71
+        'updated_at'
72
+    ];
73
+
74
+    public function tavolo()
75
+    {
76
+        return $this->belongsTo('\App\Models\Tavolo', 'tavolo_id', 'id');
77
+    }
78
+
79
+    public function attivita()
80
+    {
81
+        return $this->belongsTo('\App\Models\Attivita', 'attivita_id', 'id');
82
+    }
83
+}

+ 91
- 0
app/Models/AbstractModels/AbstractRigaFatturazione.php View File

@@ -0,0 +1,91 @@
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 AbstractRigaFatturazione extends \Illuminate\Foundation\Auth\User
10
+{
11
+    /**  
12
+     * The table associated with the model.
13
+     * 
14
+     * @var string
15
+     */
16
+    protected $table = 'riga_fatturazione';
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
+        'fattura_id' => 'integer',
40
+        'attivita_id' => 'integer',
41
+        'dispositivo_id' => 'integer',
42
+        'prezzo' => 'decimal:2',
43
+        'qta' => 'integer',
44
+        'tipo'=>'string',
45
+        'listino_prezzi_id' => 'integer',
46
+        'stato'=>'string',
47
+        'created_at' => 'datetime',
48
+        'updated_at' => 'datetime',
49
+    ];
50
+    
51
+    /**  
52
+     * The attributes that are mass assignable.
53
+     * 
54
+     * @var array
55
+     */
56
+    protected $fillable = [
57
+        'id',
58
+        'fattura_id',
59
+        'attivita_id',
60
+        'dispositivo_id',
61
+        'tipo',
62
+        'listino_prezzi_id',
63
+        'qta',
64
+        'prezzo',
65
+        'stato',
66
+        'created_at',
67
+        'updated_at',
68
+    ];
69
+
70
+
71
+    public function fattura()
72
+    {
73
+        return $this->belongsTo('\App\Models\Fattura', 'fattura_id');
74
+    }
75
+
76
+    public function attivita()
77
+    {
78
+        return $this->belongsTo('\App\Models\Attivita', 'attivita_id');
79
+    }
80
+    
81
+    public function dispositivo()
82
+    {
83
+        return $this->belongsTo('\App\Models\Dispositivo', 'dispositivo_id');
84
+    }
85
+
86
+    public function listinoPrezzi()
87
+    {
88
+        return $this->belongsTo('\App\Models\ListinoPrezzi', 'listino_prezzi_id', 'id');
89
+    }
90
+
91
+}

+ 6
- 0
app/Models/AbstractModels/AbstractSaltacoda.php View File

@@ -41,6 +41,7 @@ abstract class AbstractSaltacoda extends \Illuminate\Foundation\Auth\User
41 41
         'max_ordini' => 'integer',
42 42
         'info' => 'string',
43 43
         'note' => 'string',
44
+        'istruzioni' => 'json',
44 45
         'created_at' => 'datetime',
45 46
         'updated_at' => 'datetime'
46 47
     ];
@@ -57,6 +58,7 @@ abstract class AbstractSaltacoda extends \Illuminate\Foundation\Auth\User
57 58
         'max_ordini',
58 59
         'info',
59 60
         'note',
61
+        'istruzioni',
60 62
         'created_at',
61 63
         'updated_at'
62 64
     ];
@@ -66,5 +68,9 @@ abstract class AbstractSaltacoda extends \Illuminate\Foundation\Auth\User
66 68
         return $this->belongsTo('\App\Models\Attivita', 'attivita_id', 'id');
67 69
     }
68 70
 
71
+    public function saltacoda_riga_ordine(): HasMany
72
+    {
73
+        return $this->hasMany('\App\Models\SaltacodaRigaordine', 'saltacoda_ordine_id', 'id');
74
+    }
69 75
 
70 76
 }

+ 2
- 2
app/Models/AbstractModels/AbstractSaltacodaOrdine.php View File

@@ -80,9 +80,9 @@ abstract class AbstractSaltacodaOrdine extends \Illuminate\Foundation\Auth\User
80 80
         return $this->belongsTo('\App\Models\Attivita', 'attivita_id', 'id');
81 81
     }
82 82
 
83
-    public function riga_ordine()
83
+    public function saltacoda_riga_ordine()
84 84
     {
85
-        return $this->hasMany('\App\Models\SaltacodaRigaordine', 'saltacoda_ordine_id', 'id');
85
+        return $this->hasMany('\App\Models\SaltacodaRigaOrdine', 'saltacoda_ordine_id', 'id');
86 86
     }
87 87
 
88 88
 

+ 69
- 0
app/Models/AbstractModels/AbstractSaltacodaRigaOrdineHasVariante.php View File

@@ -0,0 +1,69 @@
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 AbstractSaltacodaRigaOrdineHasVariante extends \Illuminate\Foundation\Auth\User
10
+{
11
+    /**  
12
+     * The table associated with the model.
13
+     * 
14
+     * @var string
15
+     */
16
+    protected $table = 'saltacoda_riga_ordine_has_variante';
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
+        'saltacoda_riga_ordine_id' => 'integer',
40
+        'variante_id' => 'integer',
41
+        'nota' => 'string',
42
+        'created_at' => 'datetime',
43
+        'updated_at' => 'datetime'
44
+    ];
45
+
46
+    /**  
47
+     * The attributes that are mass assignable.
48
+     * 
49
+     * @var array
50
+     */
51
+    protected $fillable = [
52
+        'id',
53
+        'saltacoda_riga_ordine_id',
54
+        'variante_id',
55
+        'nota',
56
+        'created_at',
57
+        'updated_at'
58
+    ];
59
+    
60
+    public function saltacoda_riga_ordine()
61
+    {
62
+        return $this->belongsTo('\App\Models\SaltacodaRigaOrdine', 'saltacoda_riga_ordine_id', 'id');
63
+    }
64
+
65
+    public function variante_piatto()
66
+    {
67
+        return $this->belongsTo('\App\Models\VariantePiatto', 'variante_id', 'id');
68
+    }
69
+}

+ 4
- 0
app/Models/AbstractModels/AbstractSaltacodaRigaordine.php View File

@@ -72,4 +72,8 @@ abstract class AbstractSaltacodaRigaordine extends \Illuminate\Foundation\Auth\U
72 72
     {
73 73
         return $this->belongsTo('\App\Models\Piatto', 'piatto_id', 'id');
74 74
     }
75
+    public function saltacoda_has_variante()
76
+    {
77
+        return $this->hasMany('\App\Models\SaltacodaRigaOrdineHasVariante', 'saltacoda_riga_ordine_id', 'id');
78
+    }
75 79
 }

+ 77
- 0
app/Models/AbstractModels/AbstractSlotTemporaliPrenotazioneTavolo.php View File

@@ -0,0 +1,77 @@
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 AbstractSlotTemporaliPrenotazioneTavolo extends \Illuminate\Foundation\Auth\User
10
+{
11
+    /**  
12
+     * The table associated with the model.
13
+     * 
14
+     * @var string
15
+     */
16
+    protected $table = 'slot_temporali_prenotazione_tavoli';
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
+        'attivita_id' => 'integer',
40
+        'data_prenotazione' => 'string',
41
+        'ora_inizio' => 'time',
42
+        'ora_fine' => 'time',
43
+        'tavolo_id' => 'integer',
44
+        'tipo' => 'string',
45
+        'posti_disponibili' => '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
+        'attivita_id',
58
+        'data_prenotazione',
59
+        'ora_inizio',
60
+        'ora_fine',
61
+        'tavolo_id',
62
+        'tipo',
63
+        'posti_disponibili',
64
+        'created_at',
65
+        'updated_at'
66
+    ];
67
+    
68
+    public function attivita()
69
+    {
70
+        return $this->belongsTo('\App\Models\Attivita', 'attivita_id', 'id');
71
+    }
72
+
73
+    public function tavolo()
74
+    {
75
+        return $this->belongsTo('\App\Models\Tavolo', 'tavolo_id', 'id');
76
+    }
77
+}

+ 4
- 0
app/Models/AbstractModels/AbstractUser.php View File

@@ -85,5 +85,9 @@ abstract class AbstractUser extends \Illuminate\Foundation\Auth\User
85 85
     {
86 86
         return $this->hasMany('\App\Models\Attivita', 'user_id', 'id');
87 87
     }
88
+    public function fatturazione()
89
+    {
90
+        return $this->hasMany('\App\Models\Fatturazione', 'user_id', 'id');
91
+    }
88 92
     
89 93
 }

+ 151
- 0
app/Models/Asporto.php View File

@@ -0,0 +1,151 @@
1
+<?php
2
+
3
+namespace App\Models;
4
+
5
+use Illuminate\Database\Eloquent\Model;
6
+
7
+class Asporto extends \App\Models\AbstractModels\AbstractAsporto
8
+{
9
+    const PREPARAZIONE = 'preparazione';
10
+    const CHIAMATA = 'chiamata';
11
+    const RITIRO = 'ritiro';
12
+    const CONSEGNATO = 'consegnato';
13
+
14
+    //stati dell'asporto
15
+    public static function statiAsporto(){
16
+        return collect([
17
+            'preparazione' => [
18
+                'label' => 'Preparazione',
19
+                'descrizione' => 'Asporto in preparazione',
20
+                'colore' => 'primary',
21
+                'value' => self::PREPARAZIONE,
22
+            ],
23
+            'chiamata' => [
24
+                'label' => 'Chiamata',
25
+                'descrizione' => 'Asporto in chiamata',
26
+                'colore' => 'warning',
27
+                'value' => self::CHIAMATA,
28
+            ],
29
+            'ritiro' => [
30
+                'label' => 'Ritiro',
31
+                'descrizione' => 'Asporto in ritiro',
32
+                'colore' => 'success',
33
+                'value' => self::RITIRO,
34
+            ],
35
+            'consegnato' => [
36
+                'label' => 'Consegnato',
37
+                'descrizione' => 'Asporto consegnato al cliente',
38
+                'colore' => 'secondary',
39
+                'value' => self::CONSEGNATO,
40
+            ],
41
+        ]);
42
+    }
43
+
44
+    public static function standard(){
45
+        return collect([
46
+            [
47
+                'label' => 'Ora inizio servizio asporto',
48
+                'descrizione' => 'Ora inizio servizio asporto',
49
+                'chiave' => 'ora_inizio_asporto',
50
+                'valore' => '19:00',
51
+                'tipo' => 'time',
52
+                'opzioni' => null,
53
+                'gruppo' => 'asporto',
54
+            ],
55
+            [
56
+                'label' => 'Ora fine servizio asporto',
57
+                'descrizione' => 'Ora fine servizio asporto',
58
+                'chiave' => 'ora_fine_asporto',
59
+                'valore' => '23:00',
60
+                'tipo' => 'time',
61
+                'opzioni' => null,
62
+                'gruppo' => 'asporto',
63
+            ],
64
+            [
65
+                'label' => 'Ora inizio prenotazione asporto',
66
+                'descrizione' => 'Ora inizio prenotazione asporto',
67
+                'chiave' => 'ora_inizio_prenotazione_asporto',
68
+                'valore' => '18:00',
69
+                'tipo' => 'time',
70
+                'opzioni' => null,
71
+                'gruppo' => 'asporto',
72
+            ],
73
+            [
74
+                'label' => 'Ora fine prenotazione asporto',
75
+                'descrizione' => 'Ora fine prenotazione asporto',
76
+                'chiave' => 'ora_fine_prenotazione_asporto',
77
+                'valore' => '19:00',
78
+                'tipo' => 'time',
79
+                'opzioni' => null,
80
+                'gruppo' => 'asporto',
81
+            ],
82
+            [
83
+                'label' => 'Giorni in cui è disponibile il servizio asporto',
84
+                'descrizione' => 'Giorni in cui è disponibile il servizio asporto',
85
+                'chiave' => 'giorni_disponibilita_asporto',
86
+                'valore' => '1,2,3,4,5,6,7',
87
+                'tipo' => 'array',
88
+                'opzioni' => [1,2,3,4,5,6,7],
89
+                'gruppo' => 'asporto',
90
+            ],
91
+            [
92
+                'label' => 'Durata della slot oraria consegna asporto',   
93
+                'descrizione' => 'Durata della slot oraria consegna asporto',
94
+                'chiave' => 'durata_slot_oraria_consegna_asporto',
95
+                'valore' => '15',
96
+                'tipo' => 'integer',
97
+                'opzioni' => null,
98
+                'gruppo' => 'asporto',
99
+            ],
100
+            [
101
+                'label' => 'Q.tà massima di ordini per slot oraria',
102
+                'descrizione' => 'Q.tà massima di ordini per slot oraria',
103
+                'chiave' => 'qta_massima_ordini_per_slot_oraria',
104
+                'valore' => '10',
105
+                'tipo' => 'integer',
106
+                'opzioni' => null,
107
+                'gruppo' => 'asporto',
108
+            ],
109
+            [
110
+                'label' => 'Q.tà massima piatti per slot oraria',
111
+                'descrizione' => 'Q.tà massima piatti per slot oraria',
112
+                'chiave' => 'qta_massima_piatti_per_slot_oraria',
113
+                'valore' => '20',
114
+                'tipo' => 'integer',
115
+                'opzioni' => null,
116
+                'gruppo' => 'asporto',
117
+            ],
118
+            [
119
+                'label' => 'Tolleranza nella q.tà di piatti per slot oraria',
120
+                'descrizione' => 'Se osono disponibili ordini e piatti per il slot orario, questo è il numero di piatti in più che può essere consegnato per l\'ultimo ordine della slot aggiunto.',
121
+                'chiave' => 'tolleranza_qta_piatti_per_slot_oraria',
122
+                'valore' => '5',
123
+                'tipo' => 'integer',
124
+                'opzioni' => null,
125
+                'gruppo' => 'asporto',
126
+            ],
127
+            [
128
+                'label' => 'Costo fisso per ogni ordine',
129
+                'descrizione' => 'Costo fisso per ogni ordine',
130
+                'chiave' => 'costo_fisso_per_ordine',
131
+                'valore' => '10',
132
+                'tipo' => 'float',
133
+                'opzioni' => null,
134
+                'gruppo' => 'asporto',
135
+            ]
136
+        ]);
137
+    }
138
+
139
+    public static function getGiorniDisponibilitaAsporto()
140
+    {
141
+        return  collect([
142
+            1 => 'Lunedi',
143
+            2 => 'Martedi',
144
+            3 => 'Mercoledi',
145
+            4 => 'Giovedi',
146
+            5 => 'Venerdi',
147
+            6 => 'Sabato',
148
+            7 => 'Domenica',
149
+        ]);
150
+    }
151
+}

+ 40
- 3
app/Models/Attivita.php View File

@@ -4,9 +4,15 @@ namespace App\Models;
4 4
 
5 5
 use Illuminate\Database\Eloquent\Model;
6 6
 use Illuminate\Support\Facades\Auth;
7
+use Illuminate\Support\Facades\Storage;
7 8
 
8 9
 class Attivita extends \App\Models\AbstractModels\AbstractAttivita
9 10
 {
11
+    public function saltacoda()
12
+    {
13
+        return $this->hasOne(Saltacoda::class, 'attivita_id', 'id');
14
+    }
15
+
10 16
     public function logoUrl(): ?string
11 17
     {
12 18
         if (empty($this->path_logo)) {
@@ -18,15 +24,46 @@ class Attivita extends \App\Models\AbstractModels\AbstractAttivita
18 24
             : asset($this->path_logo);
19 25
     }
20 26
 
27
+    public static function defaultLogoUrl(): string
28
+    {
29
+        return asset('assets/img/logo_fest_S.png');
30
+    }
31
+
32
+    public static function defaultCoverUrl(): string
33
+    {
34
+        return asset('assets/img/cover_login.jpeg');
35
+    }
36
+
37
+    public function logoDisplayUrl(): string
38
+    {
39
+        return $this->logoUrl() ?: self::defaultLogoUrl();
40
+    }
41
+
42
+    public function coverDisplayUrl(): string
43
+    {
44
+        return $this->coverUrl() ?: self::defaultCoverUrl();
45
+    }
46
+
47
+    public function hasCustomCover(): bool
48
+    {
49
+        return filled($this->path_image);
50
+    }
51
+
21 52
     public function coverUrl(): ?string
22 53
     {
23 54
         if (empty($this->path_image)) {
24 55
             return null;
25 56
         }
26 57
 
27
-        return str_starts_with($this->path_image, 'http')
28
-            ? $this->path_image
29
-            : asset($this->path_image);
58
+        if (str_starts_with($this->path_image, 'http')) {
59
+            return $this->path_image;
60
+        }
61
+
62
+        if (str_starts_with($this->path_image, 'assets/')) {
63
+            return asset($this->path_image);
64
+        }
65
+
66
+        return Storage::disk('copertinaAttivita')->url($this->path_image);
30 67
     }
31 68
 
32 69
     public function iniziali(): string

+ 32
- 0
app/Models/Categoriacontabile.php View File

@@ -7,6 +7,38 @@ use Illuminate\Support\Facades\Session;
7 7
 
8 8
 class Categoriacontabile extends \App\Models\AbstractModels\AbstractCategoriacontabile
9 9
 {
10
+
11
+    public static function standard(){
12
+        return collect([
13
+            [
14
+                'nome' => 'Vendita',
15
+                'descrizione' => 'Vendita di prodotti e servizi',
16
+                'colore' => '#61db0f',
17
+            ],
18
+            [
19
+                'nome' => 'Acquisto',
20
+                'descrizione' => 'Acquisto di prodotti e servizi',
21
+                'colore' => '#dc3545',
22
+            ],
23
+            [
24
+                'nome' => 'Spese generali',
25
+                'descrizione' => 'Spese generali',
26
+                'colore' => '#dc3545',
27
+            ],
28
+            [
29
+                'nome' => 'Utenze',
30
+                'descrizione' => 'Spese per utenze (Luce, Acqua, Gas, Telefono)',
31
+                'colore' => '#dc3545',
32
+            ],
33
+            [
34
+                'nome' => 'Staff',
35
+                'descrizione' => 'Spese per staff',
36
+                'colore' => '#dc3545',
37
+            ]
38
+        ]);
39
+   
40
+    }
41
+    
10 42
     public static function vendita(){
11 43
         return self::where('attivita_id', session('attivita_attuale'))->where('is_attiva', true)->where('nome', 'Vendita')->first();
12 44
     }

+ 51
- 0
app/Models/Dispositivo.php View File

@@ -4,9 +4,20 @@ namespace App\Models;
4 4
 
5 5
 use Illuminate\Database\Eloquent\Model;
6 6
 use Illuminate\Support\Facades\Hash;
7
+use Illuminate\Validation\ValidationException;
7 8
 
8 9
 class Dispositivo extends \App\Models\AbstractModels\AbstractDispositivo
9 10
 {
11
+    protected static function booted(): void
12
+    {
13
+        static::deleting(function (self $dispositivo) {
14
+            if ($dispositivo->haStorico()) {
15
+                throw ValidationException::withMessages([
16
+                    'id' => 'Dispositivo con storico: disattivalo (is_attivo) invece di eliminarlo.',
17
+                ]);
18
+            }
19
+        });
20
+    }
10 21
     const STAMPANTE = 'stampante';  // stampante DI CASSA O DI CUCINA
11 22
     const CASSA = 'cassa';  // cassa di vendita
12 23
     const KIOSK = 'kiosk';  // kiosk di vendita
@@ -141,4 +152,44 @@ public function cucine()
141 152
 {
142 153
     return $this->belongsToMany('\App\Models\Cucina', 'dispositivo_has_cucina', 'dispositivo_id', 'cucina_id');
143 154
 }
155
+
156
+public function ordini()
157
+{
158
+    return $this->hasMany(Ordine::class, 'dispositivo_id');
159
+}
160
+
161
+public function haStorico(): bool
162
+{
163
+    if ($this->ordini()->exists()) {
164
+        return true;
165
+    }
166
+
167
+    if (PrintJob::query()
168
+        ->where(function ($query) {
169
+            $query->where('dispositivo_id', $this->id)
170
+                ->orWhere('stampante_id', $this->id);
171
+        })
172
+        ->exists()) {
173
+        return true;
174
+    }
175
+
176
+    if (self::query()->where('url_stampante', (string) $this->id)->exists()) {
177
+        return true;
178
+    }
179
+
180
+    if (Cucina::query()->where('stampante_id', $this->id)->exists()) {
181
+        return true;
182
+    }
183
+
184
+    return false;
185
+}
186
+
187
+public function scontrini_di_oggi(){
188
+    return $this->ordini()->where('created_at', '>=', now()->startOfDay());
189
+}
190
+
191
+public function utilizzo(){
192
+    return $this->scontrini_di_oggi()->count();
193
+}
194
+
144 195
 };

+ 19
- 0
app/Models/Fattura.php View File

@@ -0,0 +1,19 @@
1
+<?php
2
+
3
+namespace App\Models;
4
+
5
+use Illuminate\Database\Eloquent\Model;
6
+
7
+class Fattura extends \App\Models\AbstractModels\AbstractFattura
8
+{
9
+    public const IN_ELABORAZIONE = 'in_elaborazione';
10
+    public const EMESSA = 'emessa';
11
+    public const PAGATA = 'pagata';
12
+    public const ANNULLATA = 'annullata';
13
+    public const SCADUTA = 'scaduta';
14
+    public const PENDENTE = 'pendente';
15
+    public const REGISTRATO = 'registrato';
16
+    public const CANCELLATA = 'cancellata';
17
+    public const INVIATA = 'inviata';
18
+    public const RICEVUTA = 'ricevuta';
19
+}

+ 22
- 0
app/Models/Impostazioni.php View File

@@ -6,6 +6,28 @@ use Illuminate\Database\Eloquent\Model;
6 6
 
7 7
 class Impostazioni extends \App\Models\AbstractModels\AbstractImpostazioni
8 8
 {
9
+
10
+    public static function standard(){
11
+        return collect([
12
+            [
13
+                'label' => 'Stampa QR code scontrino',
14
+                'chiave' => 'stampa_qr_code_chiamata',
15
+                'valore' => 'true',
16
+                'tipo' => 'boolean',
17
+                'opzioni' => null,
18
+                'gruppo' => 'Scontrino',
19
+            ],
20
+            [
21
+                'label' => 'Larghezza della stampante in caratteri',
22
+                'chiave' => 'stampante_width_default',
23
+                'valore' => '42',
24
+                'tipo' => 'integer',
25
+                'opzioni' => null,
26
+                'gruppo' => 'Scontrino',
27
+            ]
28
+        ]);
29
+    }
30
+
9 31
     public static function getImpostazione($chiave)
10 32
     {
11 33
         return self::where('chiave', $chiave)->first();

+ 77
- 0
app/Models/ListinoPrezzi.php View File

@@ -0,0 +1,77 @@
1
+<?php
2
+
3
+namespace App\Models;
4
+
5
+use Illuminate\Database\Eloquent\Model;
6
+use Illuminate\Support\Collection;
7
+class ListinoPrezzi extends \App\Models\AbstractModels\AbstractListinoPrezzi
8
+{
9
+    // Tipi di dispositivo
10
+    const CATEGORIA_HARDWARE = 'hardware';
11
+    const CATEGORIA_SOFTWARE = 'software';
12
+    const CATEGORIA_SERVIZIO = 'servizio';
13
+    const CATEGORIA_ALTRO = 'altro';
14
+
15
+    // Tipi di prezzo
16
+    const TIPI_PREZZO_GIORNALIERO = 'giornaliero';
17
+    const TIPI_PREZZO_SETTIMANALE = 'settimanale';
18
+    const TIPI_PREZZO_MENSILE = 'mensile';
19
+    const TIPI_PREZZO_ANNUALE = 'annuale';
20
+    const TIPI_PREZZO_UNA_TANTUM = 'una-tantum';
21
+
22
+    public static function getCategorieDispositivo(): Collection
23
+    {
24
+        return collect([
25
+            self::CATEGORIA_HARDWARE => [
26
+                'label' => 'Hardware',
27
+                'value' => self::CATEGORIA_HARDWARE,
28
+                'icon' => 'fas fa-microchip',
29
+            ],
30
+            self::CATEGORIA_SOFTWARE => [
31
+                'label' => 'Software',
32
+                'value' => self::CATEGORIA_SOFTWARE,
33
+                'icon' => 'fas fa-laptop',
34
+            ],
35
+            self::CATEGORIA_SERVIZIO => [
36
+                'label' => 'Servizio',
37
+                'value' => self::CATEGORIA_SERVIZIO,
38
+                'icon' => 'fas fa-cogs',
39
+            ],
40
+            self::CATEGORIA_ALTRO => [
41
+                'label' => 'Altro',
42
+                'value' => self::CATEGORIA_ALTRO,
43
+                'icon' => 'fas fa-question',
44
+            ],
45
+        ]);
46
+    }
47
+    public static function getTipiPrezzo(): Collection
48
+    {
49
+        return collect([
50
+            self::TIPI_PREZZO_UNA_TANTUM => [
51
+                'label' => 'Una tantum',
52
+                'value' => self::TIPI_PREZZO_UNA_TANTUM,
53
+                'icon' => 'fas fa-times',
54
+            ],
55
+            self::TIPI_PREZZO_GIORNALIERO => [
56
+                'label' => 'Giornaliero',
57
+                'value' => self::TIPI_PREZZO_GIORNALIERO,
58
+                'icon' => 'fas fa-calendar-day',
59
+            ],
60
+            self::TIPI_PREZZO_SETTIMANALE => [
61
+                'label' => 'Settimanale',
62
+                'value' => self::TIPI_PREZZO_SETTIMANALE,
63
+                'icon' => 'fas fa-calendar-week',
64
+            ],
65
+            self::TIPI_PREZZO_MENSILE => [
66
+                'label' => 'Mensile',
67
+                'value' => self::TIPI_PREZZO_MENSILE,
68
+                'icon' => 'fas fa-calendar-month',
69
+            ],
70
+            self::TIPI_PREZZO_ANNUALE => [
71
+                'label' => 'Annuale',
72
+                'value' => self::TIPI_PREZZO_ANNUALE,
73
+                'icon' => 'fas fa-calendar-year',
74
+            ],
75
+        ]);
76
+    }
77
+}

+ 40
- 0
app/Models/PrenotazioneTavolo.php View File

@@ -0,0 +1,40 @@
1
+<?php
2
+
3
+namespace App\Models;
4
+
5
+use Illuminate\Database\Eloquent\Model;
6
+
7
+class PrenotazioneTavolo extends \App\Models\AbstractModels\AbstractPrenotazioneTavolo
8
+{
9
+    // Stati della prenotazione
10
+    const STATO_INVIATA = 'inviata';
11
+    const STATO_CONFERMATA = 'confermata';
12
+    const STATO_LISTA_ATTESA = 'lista_attesa';
13
+    const STATO_ANNULLATA = 'annullata';
14
+    const STATO_CANCELLATA = 'cancellata';
15
+    const STATO_RIFIUTATA = 'rifiutata';
16
+
17
+    
18
+    const STATI = [
19
+        self::STATO_INVIATA => ['value' => self::STATO_INVIATA, 
20
+        'label' => 'Inviata', 
21
+        'class' => 'badge bg-info', 
22
+        'message' => 'La prenotazione è stata inviata'],
23
+
24
+        self::STATO_CONFERMATA => 
25
+        ['value' => self::STATO_CONFERMATA, 'label' => 'Confermata', 'class' => 'badge bg-success', 'message' => 'La prenotazione è stata confermata'],
26
+        self::STATO_LISTA_ATTESA => 
27
+        ['value' => self::STATO_LISTA_ATTESA, 'label' => 'Lista d\'attesa', 'class' => 'badge bg-warning', 'message' => 'La prenotazione è stata messa in lista d\'attesa'],
28
+        self::STATO_ANNULLATA => 
29
+        ['value' => self::STATO_ANNULLATA, 'label' => 'Annullata', 'class' => 'badge bg-danger', 'message' => 'La prenotazione è stata annullata'],
30
+        self::STATO_CANCELLATA => 
31
+        ['value' => self::STATO_CANCELLATA, 'label' => 'Cancellata', 'class' => 'badge bg-danger', 'message' => 'La prenotazione è stata cancellata'],
32
+        self::STATO_RIFIUTATA => 
33
+        ['value' => self::STATO_RIFIUTATA, 'label' => 'Rifiutata', 'class' => 'badge bg-danger', 'message' => 'La prenotazione è stata rifiutata'],
34
+    ];
35
+    
36
+    public static function getStati()
37
+    {
38
+        return collect(self::STATI);
39
+    }
40
+}

+ 86
- 0
app/Models/RigaFatturazione.php View File

@@ -0,0 +1,86 @@
1
+<?php
2
+
3
+namespace App\Models;
4
+
5
+use Illuminate\Database\Eloquent\Model;
6
+
7
+class RigaFatturazione extends \App\Models\AbstractModels\AbstractRigaFatturazione
8
+{
9
+    CONST GRATUITO = 'gratuito';
10
+    CONST REGISTRATO = 'registrato';
11
+    CONST FATTURATO = 'fatturato';
12
+    CONST PAGATO = 'pagato';
13
+    CONST ANNULLATO = 'annullato';
14
+
15
+    public static function getStati()
16
+    {
17
+        return collect([
18
+            'registrato' => [
19
+                'value' => self::REGISTRATO, 
20
+                'label' => 'Registrato' ,
21
+                'color' => 'primary', 
22
+                'icon' => 'bx bx-file' ,
23
+                'description' => 'Fatturazione registrata'],
24
+            'fatturato' => [
25
+                'value' => self::FATTURATO, 
26
+                'label' => 'Fatturato' ,
27
+                'color' => 'success', 
28
+                'icon' => 'bx bx-file-text' ,
29
+                'description' => 'Fatturazione fatturata'],
30
+            'pagato' => [
31
+                'value' => self::PAGATO, 
32
+                'label' => 'Pagato' ,
33
+                'color' => 'info', 
34
+                'icon' => 'bx bx-file-text' ,
35
+                'description' => 'Fatturazione pagata'],
36
+            'annullato' => [
37
+                'value' => self::ANNULLATO, 
38
+                'label' => 'Annullato' ,
39
+                'color' => 'danger', 
40
+                'icon' => 'bx bx-file-text' ,
41
+                'description' => 'Fatturazione annullata'
42
+                ],
43
+            'gratuito' => [
44
+                'value' => self::GRATUITO, 
45
+                'label' => 'Gratuito' ,
46
+                'color' => 'warning', 
47
+                'icon' => 'bx bx-file-text' ,
48
+                'description' => 'Fatturazione gratuita'
49
+                ],
50
+        ]);
51
+    }
52
+
53
+    public static function getStato($stato)
54
+    {
55
+        return self::getStati()->get($stato);
56
+    }
57
+
58
+    public static function getStatoLabel($stato)
59
+    {
60
+        return self::getStato($stato)['label'];
61
+    }
62
+
63
+    public static function getStatoColor($stato)
64
+    {
65
+        return self::getStato($stato)['color'];
66
+    }
67
+
68
+    public static function getStatoIcon($stato)
69
+    {
70
+        return self::getStato($stato)['icon'];
71
+    }
72
+
73
+    public static function getStatoDescription($stato)
74
+    {
75
+        return self::getStato($stato)['description'];
76
+    }
77
+
78
+    public function ordini()
79
+    {
80
+        $ordini = Ordine::where('dispositivo_id', $this->dispositivo_id)
81
+        ->where('stato', Ordine::PAGATO)
82
+        ->whereDate('updated_at', today())
83
+        ->count();
84
+        return $ordini;
85
+    }
86
+}

+ 73
- 1
app/Models/Saltacoda.php View File

@@ -6,5 +6,77 @@ use Illuminate\Database\Eloquent\Model;
6 6
 
7 7
 class Saltacoda extends \App\Models\AbstractModels\AbstractSaltacoda
8 8
 {
9
-    //
9
+    public const ISTRUZIONE_SLOTS = [
10
+        'apertura',
11
+        'chiusura',
12
+        'pre_invio',
13
+        'post_invio',
14
+    ];
15
+
16
+    public static function defaultSlot(): array
17
+    {
18
+        return [
19
+            'attivo' => false,
20
+            'titolo' => '',
21
+            'testo' => '',
22
+        ];
23
+    }
24
+
25
+    public static function defaultIstruzioni(): array
26
+    {
27
+        return array_fill_keys(self::ISTRUZIONE_SLOTS, self::defaultSlot());
28
+    }
29
+
30
+    public static function normalizeIstruzioni(mixed $raw): array
31
+    {
32
+        $normalized = self::defaultIstruzioni();
33
+
34
+        if (is_string($raw) && $raw !== '') {
35
+            $raw = json_decode($raw, true);
36
+        }
37
+
38
+        if (! is_array($raw)) {
39
+            return $normalized;
40
+        }
41
+
42
+        foreach (self::ISTRUZIONE_SLOTS as $slot) {
43
+            $incoming = $raw[$slot] ?? null;
44
+            if (! is_array($incoming)) {
45
+                continue;
46
+            }
47
+
48
+            $normalized[$slot] = [
49
+                'attivo' => (bool) ($incoming['attivo'] ?? false),
50
+                'titolo' => trim((string) ($incoming['titolo'] ?? $incoming['label'] ?? '')),
51
+                'testo' => trim((string) ($incoming['testo'] ?? $incoming['value'] ?? '')),
52
+            ];
53
+        }
54
+
55
+        return $normalized;
56
+    }
57
+
58
+    public function getIstruzioni(): array
59
+    {
60
+        return self::normalizeIstruzioni($this->istruzioni);
61
+    }
62
+
63
+    public function getIstruzioniApertura(): ?array
64
+    {
65
+        return $this->getIstruzioni()['apertura'] ?? null;
66
+    }
67
+
68
+    public function getIstruzioniChiusura(): ?array
69
+    {
70
+        return $this->getIstruzioni()['chiusura'] ?? null;
71
+    }
72
+
73
+    public function getIstruzioniPreInvio(): ?array
74
+    {
75
+        return $this->getIstruzioni()['pre_invio'] ?? null;
76
+    }
77
+
78
+    public function getIstruzioniPostInvio(): ?array
79
+    {
80
+        return $this->getIstruzioni()['post_invio'] ?? null;
81
+    }
10 82
 }

+ 2
- 0
app/Models/SaltacodaOrdine.php View File

@@ -3,10 +3,12 @@
3 3
 namespace App\Models;
4 4
 
5 5
 use Illuminate\Database\Eloquent\Model;
6
+use Illuminate\Database\Eloquent\Relations\HasMany;
6 7
 
7 8
 class SaltacodaOrdine extends \App\Models\AbstractModels\AbstractSaltacodaOrdine
8 9
 {
9 10
     const PRE_ORDINE = 'pre_ordine';
11
+    const PRE_CARRELLO = 'pre_carrello';
10 12
     const PRE_PROMOSSO = 'pre_promosso';
11 13
     const IMPORTATO = 'importato';
12 14
 }

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

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

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

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

+ 1
- 0
app/Models/User.php View File

@@ -33,4 +33,5 @@ class User extends \App\Models\AbstractModels\AbstractUser
33 33
   public function fullName(){
34 34
     return $this->cognome." ".$this->nome;
35 35
   }
36
+
36 37
 }

+ 99
- 0
app/Observers/AttivitaObserver.php View File

@@ -0,0 +1,99 @@
1
+<?php
2
+
3
+namespace App\Observers;
4
+
5
+use App\Models\Attivita;
6
+use App\Models\Categoriacontabile;
7
+use App\Models\Impostazioni;
8
+use App\Models\Saltacoda;
9
+use App\Models\Asporto;
10
+
11
+class AttivitaObserver
12
+{
13
+    /**
14
+     * Handle the Attivita "created" event.
15
+     */
16
+    public function created(Attivita $attivita): void
17
+    {
18
+        //Aggiungi infomazioni standard per l'attività
19
+        $attivita->info = json_encode([
20
+            'città' => '',
21
+            'indirizzo' => '',
22
+            'cap' => '',
23
+            'provincia' => '',
24
+            'paese' => '',
25
+            'telefono' => '',
26
+            'email' => '',
27
+            'sito_web' => '',
28
+            'note' => '',
29
+        ]);
30
+
31
+        //crea saltacoda
32
+        Saltacoda::create([
33
+            'is_attivo' => false,
34
+            'max_ordini' => 0,
35
+            'attivita_id' => $attivita->id,
36
+            'istruzioni' => Saltacoda::defaultIstruzioni(),
37
+        ]);
38
+
39
+        //crea punto vendita cassa 0
40
+        \App\Models\Dispositivo::create([
41
+            'nome' => 'Cassa 0',
42
+            'url_stampante' => '',
43
+            'ubicazione' => 'Il tuo primo punto vendita',
44
+            'tipo' => \App\Models\Dispositivo::CASSA,
45
+            'is_attivo' => true,
46
+            'attivita_id' => $attivita->id,
47
+        ]);
48
+        // crea le categorie contabili standard
49
+        Categoriacontabile::standard()->each(function($categoria) use ($attivita){
50
+            $attivita->categorie_contabili()->create([
51
+                'nome' => $categoria['nome'],
52
+                'descrizione' => $categoria['descrizione'],
53
+                'colore' => $categoria['colore'],
54
+                'is_attiva' => true,
55
+                'attivita_id' => $attivita->id,
56
+            ]);
57
+        });
58
+        // crea le impostazioni standard
59
+        Impostazioni::standard()->each(function($impostazione) use ($attivita){
60
+            $attivita->impostazioni()->create($impostazione);
61
+        });
62
+        //crea le impostazioni asporto standard
63
+        Asporto::standard()->each(function($asporto) use ($attivita){
64
+            $attivita->impostazioni()->create($asporto);
65
+        });
66
+    }
67
+
68
+    /**
69
+     * Handle the Attivita "updated" event.
70
+     */
71
+    public function updated(Attivita $attivita): void
72
+    {
73
+        //
74
+    }
75
+
76
+    /**
77
+     * Handle the Attivita "deleted" event.
78
+     */
79
+    public function deleted(Attivita $attivita): void
80
+    {
81
+        //
82
+    }
83
+
84
+    /**
85
+     * Handle the Attivita "restored" event.
86
+     */
87
+    public function restored(Attivita $attivita): void
88
+    {
89
+        //
90
+    }
91
+
92
+    /**
93
+     * Handle the Attivita "force deleted" event.
94
+     */
95
+    public function forceDeleted(Attivita $attivita): void
96
+    {
97
+        //
98
+    }
99
+}

+ 28
- 0
app/Observers/OrdineObserver.php View File

@@ -7,6 +7,7 @@ use App\Models\Ordine;
7 7
 use App\Models\PrintJob;
8 8
 use App\Models\Cucina;
9 9
 use Illuminate\Support\Facades\Log;
10
+use App\Models\SaltacodaOrdine;
10 11
 
11 12
 class OrdineObserver
12 13
 {
@@ -28,6 +29,24 @@ class OrdineObserver
28 29
             return;
29 30
         }
30 31
 
32
+        //Aggiorna lo stato del pre_ordine
33
+        $preOrdineId = data_get($ordine->info, 'pre_ordine_id');
34
+        $preOrdineCodice = data_get($ordine->info, 'pre_ordine_codice');
35
+
36
+        if ($preOrdineId || $preOrdineCodice) {
37
+            $preOrdineQuery = SaltacodaOrdine::query()
38
+                ->where('stato', SaltacodaOrdine::PRE_CARRELLO);
39
+
40
+            if ($preOrdineId) {
41
+                $preOrdineQuery->where('id', $preOrdineId);
42
+            } else {
43
+                $preOrdineQuery->where('codice', $preOrdineCodice);
44
+            }
45
+
46
+            $preOrdineQuery->update(['stato' => SaltacodaOrdine::IMPORTATO]);
47
+        }
48
+        
49
+        //Crea il print job per lo scontrino
31 50
         $printJob = PrintJob::query()->firstOrNew([
32 51
             'ordine_id' => $ordine->id,
33 52
             'tipo' => PrintJob::TIPO_SCONTRINO,
@@ -89,6 +108,15 @@ class OrdineObserver
89 108
             $printJob->save();
90 109
             ElaboraJobStampa::dispatch($printJob->id);
91 110
         }
111
+
112
+        //Aggiorna il utilizzo del dispositivo
113
+        app('\App\Services\Fatturazione\RigaFatturazioneService')->aggiornaUtilizzoDispositivo($ordine->dispositivo_id);
114
+
115
+        //Crea l'asporto se l'ordine è asporto
116
+        if (isset($ordine->info['asporto']) && $ordine->info['asporto'] == true) {
117
+            app('\App\Services\Asporto\AsportoService')->creaAsporto($ordine);
118
+        }
119
+   
92 120
     }
93 121
 
94 122
     /**

+ 3
- 0
app/Providers/AppServiceProvider.php View File

@@ -8,6 +8,8 @@ use App\Observers\DispositivoObserver;
8 8
 use App\Observers\PagamentoObserver;
9 9
 use App\Models\Pagamento;
10 10
 use App\Models\Dispositivo;
11
+use App\Models\Attivita;
12
+use App\Observers\AttivitaObserver;
11 13
 use Illuminate\Support\ServiceProvider;
12 14
 use Illuminate\Support\Facades\Vite;
13 15
 
@@ -29,6 +31,7 @@ class AppServiceProvider extends ServiceProvider
29 31
     Ordine::observe(OrdineObserver::class);
30 32
     Dispositivo::observe(DispositivoObserver::class);
31 33
     Pagamento::observe(PagamentoObserver::class);
34
+    Attivita::observe(AttivitaObserver::class);
32 35
 
33 36
     Vite::useStyleTagAttributes(function (?string $src, string $url, ?array $chunk, ?array $manifest) {
34 37
       if ($src !== null) {

+ 66
- 0
app/Services/Asporto/AsportoService.php View File

@@ -0,0 +1,66 @@
1
+<?php
2
+
3
+namespace App\Services\Asporto;
4
+
5
+use App\Models\Asporto;
6
+use App\Models\Ordine;
7
+use RuntimeException;
8
+
9
+class AsportoService
10
+{
11
+    public function creaAsporto(Ordine $ordine): Asporto
12
+    {
13
+        $asporto = new Asporto();
14
+        $asporto->ordine_id = $ordine->id;
15
+        $asporto->ora_ritiro = $ordine->info['ora_ritiro'] ?? null;
16
+        $asporto->stato = Asporto::PREPARAZIONE;
17
+        $asporto->save();
18
+
19
+        return $asporto;
20
+    }
21
+
22
+    /**
23
+     * Completa la preparazione e passa l'asporto in chiamata.
24
+     */
25
+    public function avanzaAChiamata(Asporto $asporto): Asporto
26
+    {
27
+        if ($asporto->stato !== Asporto::PREPARAZIONE) {
28
+            throw new RuntimeException('L\'asporto non è in preparazione.');
29
+        }
30
+
31
+        $asporto->stato = Asporto::CHIAMATA;
32
+        $asporto->save();
33
+
34
+        return $asporto;
35
+    }
36
+
37
+    /**
38
+     * Completa la chiamata e passa l'asporto in ritiro.
39
+     */
40
+    public function avanzaARitiro(Asporto $asporto): Asporto
41
+    {
42
+        if ($asporto->stato !== Asporto::CHIAMATA) {
43
+            throw new RuntimeException('L\'asporto non è in chiamata.');
44
+        }
45
+
46
+        $asporto->stato = Asporto::RITIRO;
47
+        $asporto->save();
48
+
49
+        return $asporto;
50
+    }
51
+
52
+    /**
53
+     * Completa il ritiro e marca l'asporto come consegnato.
54
+     */
55
+    public function avanzaAConsegnato(Asporto $asporto): Asporto
56
+    {
57
+        if ($asporto->stato !== Asporto::RITIRO) {
58
+            throw new RuntimeException('L\'asporto non è in ritiro.');
59
+        }
60
+
61
+        $asporto->stato = Asporto::CONSEGNATO;
62
+        $asporto->save();
63
+
64
+        return $asporto;
65
+    }
66
+}

+ 24
- 23
app/Services/Bilancio/BilancioServizioService.php View File

@@ -9,6 +9,28 @@ use Carbon\Carbon;
9 9
 
10 10
 class BilancioServizioService
11 11
 {
12
+    /**
13
+     * Righe ordine con incasso reale: solo ordini dell'attività che hanno almeno un pagamento.
14
+     * riga_ordine.prezzo è già il totale riga (non moltiplicare per quantita).
15
+     */
16
+    public function righeOrdineIncassate(int $attivitaId, Carbon $start, Carbon $end)
17
+    {
18
+        return RigaOrdine::query()
19
+            ->with(['piatto.cucina', 'ordine'])
20
+            ->whereHas('ordine', function ($query) use ($attivitaId, $start, $end) {
21
+                $query
22
+                    ->where('attivita_id', $attivitaId)
23
+                    ->whereBetween('created_at', [$start, $end])
24
+                    ->whereHas('pagamenti');
25
+            })
26
+            ->get();
27
+    }
28
+
29
+    public function totaleRigaOrdine(RigaOrdine $riga): float
30
+    {
31
+        return (float) ($riga->prezzo ?? 0);
32
+    }
33
+
12 34
     /**
13 35
      * Inizio del servizio corrente: ultima chiusura registrata, altrimenti inizio giornata.
14 36
      */
@@ -56,28 +78,7 @@ class BilancioServizioService
56 78
      */
57 79
     public function forPeriod(int $attivitaId, Carbon $start, Carbon $end): array
58 80
     {
59
-        $righeOrdini = RigaOrdine::query()
60
-            ->with(['piatto.cucina', 'ordine'])
61
-            ->whereHas('ordine', function ($query) use ($attivitaId, $start, $end) {
62
-                $query
63
-                    ->where('attivita_id', $attivitaId)
64
-                    ->whereBetween('created_at', [$start, $end]);
65
-            })
66
-            ->get();
67
-
68
-        $righeIncassi = RigaOrdine::query()
69
-            ->with(['piatto.cucina', 'ordine'])
70
-            ->whereHas('ordine.pagamenti', function ($query) use ($attivitaId, $start, $end) {
71
-                $query
72
-                    ->where('attivita_id', $attivitaId)
73
-                    ->whereBetween('created_at', [$start, $end]);
74
-            })
75
-            ->get();
76
-
77
-        $righe = $righeOrdini
78
-            ->concat($righeIncassi)
79
-            ->unique('id')
80
-            ->values();
81
+        $righe = $this->righeOrdineIncassate($attivitaId, $start, $end);
81 82
 
82 83
         $cucineRows = $righe
83 84
             ->groupBy(fn ($riga) => $riga->piatto?->cucina_id ?: 0)
@@ -90,7 +91,7 @@ class BilancioServizioService
90 91
                     ->map(function ($piattoItems) {
91 92
                         $firstPiatto = $piattoItems->first();
92 93
                         $quantita = (int) $piattoItems->sum(fn ($r) => (int) ($r->quantita ?? 0));
93
-                        $totale = (float) $piattoItems->sum(fn ($r) => ((float) ($r->quantita ?? 0)) * ((float) ($r->prezzo ?? 0)));
94
+                        $totale = (float) $piattoItems->sum(fn ($r) => $this->totaleRigaOrdine($r));
94 95
 
95 96
                         return [
96 97
                             'nome' => $firstPiatto?->piatto?->nome ?? 'Piatto',

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

@@ -5,6 +5,7 @@ namespace App\Services\Carrello;
5 5
 use App\Models\Ordine;
6 6
 use App\Models\RigaOrdine;
7 7
 use App\Models\Piatto;
8
+use App\Models\SaltacodaOrdine;
8 9
 use App\Models\VariantePiatto;
9 10
 use App\Models\RigaOrdineHasVariante;
10 11
 use Illuminate\Database\Eloquent\ModelNotFoundException;
@@ -140,7 +141,24 @@ class CarrelloService
140 141
                 'data' => null,  
141 142
             ];
142 143
         }
143
-        $righe = RigaOrdine::where('ordine_id', $ordine->id)->delete();
144
+
145
+        $preOrdineId = data_get($ordine->info, 'pre_ordine_id');
146
+        $preOrdineCodice = data_get($ordine->info, 'pre_ordine_codice');
147
+
148
+        if ($preOrdineId || $preOrdineCodice) {
149
+            $preOrdineQuery = SaltacodaOrdine::query()
150
+                ->where('stato', SaltacodaOrdine::PRE_CARRELLO);
151
+
152
+            if ($preOrdineId) {
153
+                $preOrdineQuery->where('id', $preOrdineId);
154
+            } else {
155
+                $preOrdineQuery->where('codice', $preOrdineCodice);
156
+            }
157
+
158
+            $preOrdineQuery->update(['stato' => SaltacodaOrdine::PRE_ORDINE]);
159
+        }
160
+
161
+        RigaOrdine::where('ordine_id', $ordine->id)->delete();
144 162
         $ordine->prezzo = 0;
145 163
         $ordine->save();
146 164
         $ordine->delete();
@@ -179,74 +197,75 @@ class CarrelloService
179 197
         return $riga;
180 198
     }
181 199
 
182
-    public function aggiungiVarianteSingoloPiatto(int $piattoId, int $rigaOrdineId, int $varianteId, string $nota = null): array
200
+    public function aggiungiVarianteSingoloPiatto(int $piattoId, int $rigaOrdineId, array $varianteIds, string $nota = null): array
183 201
     {
184
-        $riga = RigaOrdine::findOrFail($rigaOrdineId);
185
-        if($riga == null){
186
-            return [
187
-                'success' => false,
188
-                'message' => 'Riga ordine non trovata.',
189
-                'data' => null,
190
-            ];
191
-        }
192
-        $variante = VariantePiatto::findOrFail($varianteId);
193
-        if($variante == null){
202
+        $varianteIds = array_values(array_unique(array_map('intval', array_filter($varianteIds))));
203
+    
204
+        if (empty($varianteIds)) {
194 205
             return [
195 206
                 'success' => false,
196
-                'message' => 'Variante non trovata.',
207
+                'message' => 'Nessuna variante selezionata.',
197 208
                 'data' => null,
198 209
             ];
199 210
         }
211
+    
212
+        $riga = RigaOrdine::findOrFail($rigaOrdineId);
200 213
         $piatto = Piatto::findOrFail($piattoId);
201
-        if($piatto == null){
202
-            return [
203
-                'success' => false,
204
-                'message' => 'Piatto non trovato.',
205
-                'data' => null,
206
-            ];
207
-        }
208
-
209
-        if($riga->quantita == 1){
210
-            $pivotVariante = RigaOrdineHasVariante::create([
211
-                'riga_ordine_id' => $riga->id,
212
-                'variante_id' => $varianteId,
213
-                // 'nota' => $nota,
214
+        $targetRiga = $riga;
215
+    
216
+        // qty > 1: splitta una volta, poi tutte le varianti vanno sulla nuova riga
217
+        if ($riga->quantita > 1) {
218
+            $targetRiga = RigaOrdine::create([
219
+                'ordine_id' => $riga->ordine_id,
220
+                'piatto_id' => $piattoId,
221
+                'quantita' => 1,
222
+                'prezzo' => $piatto->prezzo,
223
+                'note' => $nota,
214 224
             ]);
215
-
225
+    
226
+            $riga->quantita -= 1;
216 227
             $riga->prezzo = $this->calcolaPrezzoRiga($riga);
217 228
             $riga->save();
229
+        } elseif ($nota !== null && $nota !== '') {
230
+            $targetRiga->note = $nota;
231
+        }
232
+    
233
+        $aggiunte = 0;
234
+    
235
+        foreach ($varianteIds as $varianteId) {
236
+            VariantePiatto::findOrFail($varianteId);
237
+    
238
+            $giaPresente = RigaOrdineHasVariante::where('riga_ordine_id', $targetRiga->id)
239
+                ->where('variante_id', $varianteId)
240
+                ->exists();
241
+    
242
+            if ($giaPresente) {
243
+                continue;
244
+            }
245
+    
246
+            RigaOrdineHasVariante::create([
247
+                'riga_ordine_id' => $targetRiga->id,
248
+                'variante_id' => $varianteId,
249
+            ]);
250
+    
251
+            $aggiunte++;
252
+        }
253
+    
254
+        if ($aggiunte === 0) {
218 255
             return [
219
-                'success' => true,
220
-                'message' => 'Variante aggiunta al piatto',
221
-                'data' => $pivotVariante,
256
+                'success' => false,
257
+                'message' => 'Le varianti selezionate sono già presenti su questo piatto.',
258
+                'data' => null,
222 259
             ];
223 260
         }
224
-
225
-        $NuovaRigaPerVariante = RigaOrdine::create([
226
-            'ordine_id' => $riga->ordine_id,
227
-            'piatto_id' => $piattoId,
228
-            'quantita' => 1,
229
-            'prezzo' => $piatto->prezzo,
230
-            'note' => $nota,
231
-        ]);
232
-
233
-        $pivotVariante = RigaOrdineHasVariante::create([
234
-            'riga_ordine_id' => $NuovaRigaPerVariante->id,
235
-            'variante_id' => $varianteId,
236
-            // 'nota' => $nota,
237
-        ]);
238
-
239
-        $riga->quantita -= 1;
240
-        $riga->prezzo = $this->calcolaPrezzoRiga($riga);
241
-        $riga->save();
242
-
243
-        $NuovaRigaPerVariante->prezzo = $this->calcolaPrezzoRiga($NuovaRigaPerVariante);
244
-        $NuovaRigaPerVariante->save();
245
-        // dd($NuovaRigaPerVariante ,$riga, $pivotVariante);
261
+    
262
+        $targetRiga->prezzo = $this->calcolaPrezzoRiga($targetRiga);
263
+        $targetRiga->save();
264
+    
246 265
         return [
247 266
             'success' => true,
248
-            'message' => 'Variante aggiornata',
249
-            'data' => $NuovaRigaPerVariante,
267
+            'message' => $aggiunte === 1 ? 'Variante aggiunta al piatto' : 'Varianti aggiunte al piatto',
268
+            'data' => $targetRiga,
250 269
         ];
251 270
     }
252 271
 

+ 79
- 0
app/Services/Fattura/GeneraFatturaMensile.php View File

@@ -0,0 +1,79 @@
1
+<?php
2
+
3
+namespace App\Services\Fattura;
4
+
5
+use App\Models\User;
6
+use App\Models\RigaFatturazione;
7
+use App\Models\Fattura;
8
+use App\Models\Attivita;
9
+
10
+class GeneraFatturaMensile
11
+{
12
+
13
+    public function needFatturazione()
14
+    {
15
+        //carico organizzazioni
16
+        $organizzazioni = User::whereRelation('roles', 'name', 'amministratore')->get();
17
+
18
+        //se hanno riga_fatturazione pendenti, genero fattura mensile
19
+        foreach($organizzazioni as $organizzazione) {
20
+
21
+            $attivitaOrganizzazione = Attivita::where('user_id', $organizzazione->id)->get();
22
+
23
+                foreach($attivitaOrganizzazione as $attivita) {
24
+                    if($this->checkNeedFatturazione($attivita)) {
25
+                        $this->generaFatturaMensile($attivita->user);
26
+                    }
27
+                }
28
+        }
29
+    }
30
+
31
+    public function checkNeedFatturazione(Attivita $attivita)
32
+    {
33
+        $riga_fatturazione = RigaFatturazione::where('attivita_id', $attivita->id)
34
+        ->where('stato', RigaFatturazione::REGISTRATO)
35
+        ->count();
36
+
37
+        return $riga_fatturazione > 0 ? true : false;
38
+    }
39
+
40
+    public function generaFatturaMensile(User $organizzazione)
41
+    {
42
+        $fattura = new Fattura();
43
+        $fattura->user_id = $organizzazione->id;
44
+        $fattura->numero_fattura = $this->generaNumeroFattura($organizzazione);
45
+        $fattura->data_fattura = now();
46
+        $fattura->stato = Fattura::IN_ELABORAZIONE;
47
+        $fattura->note = 'Fattura mensile generata per l\'organizzazione ' . $organizzazione->fullName();
48
+        $fattura->save();
49
+
50
+        $righe_fatturazione = $this->getRigheFatturazionePendenti($organizzazione);
51
+        $fattura->totale_netto = $righe_fatturazione->sum('prezzo');
52
+        $fattura->totale_imponibile = $fattura->totale_netto * 1.22;
53
+        $fattura->save();
54
+       foreach($righe_fatturazione as $riga_fatturazione) {
55
+        $riga_fatturazione->update([
56
+            'fattura_id' => $fattura->id,
57
+            'stato' => RigaFatturazione::FATTURATO
58
+        ]);
59
+       }
60
+       $fattura->update(['stato' => Fattura::EMESSA]);
61
+       return ;
62
+    }
63
+
64
+    private static function generaNumeroFattura()
65
+    {
66
+        $ultimaFattura = Fattura::orderBy('id', 'desc')->first();
67
+        if($ultimaFattura) {
68
+            return $ultimaFattura->numero_fattura + 1;
69
+        } else {
70
+            return 1;
71
+        }
72
+    }
73
+
74
+    private static function getRigheFatturazionePendenti(User $organizzazione)
75
+    {
76
+        $attivitaOrganizzazione = Attivita::where('user_id', $organizzazione->id)->pluck('id');
77
+        return RigaFatturazione::whereIn('attivita_id', $attivitaOrganizzazione)->where('stato', RigaFatturazione::REGISTRATO)->get();
78
+    }
79
+}

+ 85
- 0
app/Services/Fatturazione/RigaFatturazioneService.php View File

@@ -0,0 +1,85 @@
1
+<?php
2
+
3
+namespace App\Services\Fatturazione;
4
+
5
+use App\Models\Dispositivo;
6
+use App\Models\RigaFatturazione;
7
+use App\Models\ListinoPrezzi;
8
+use App\Models\Ordine;
9
+
10
+class RigaFatturazioneService
11
+{
12
+    public function __construct()
13
+    {
14
+    }
15
+
16
+    public function aggiornaUtilizzoDispositivo(int $dispositivo_id){
17
+
18
+        $dispositivo = Dispositivo::find($dispositivo_id);
19
+
20
+        if(!$dispositivo){
21
+            return response(['message' => 'Dispositivo non trovato'], 404);
22
+        }
23
+
24
+        //controllo se la RigaFatturazione è già stata fatta oggi
25
+        $RigaFatturazioneOdierna = $this->RigaFatturazioneOdiernaDispositivo($dispositivo);
26
+
27
+        if($this->RigaFatturazioneOdiernaDispositivo($dispositivo))
28
+            {
29
+                //cerco RigaFatturazione odierna
30
+                $RigaFatturazione = RigaFatturazione::where('dispositivo_id', $dispositivo->id)
31
+                ->whereDate('created_at', today())->first();
32
+                if(!$RigaFatturazione){
33
+                    return response(['message' => 'RigaFatturazione non trovata per oggi'], 404);
34
+                }
35
+                $RigaFatturazione->ordini = $RigaFatturazione->ordini();
36
+                
37
+                if($RigaFatturazione->ordini() > 5 && $RigaFatturazione->stato == RigaFatturazione::GRATUITO){
38
+                    $RigaFatturazione->stato = RigaFatturazione::REGISTRATO;
39
+                    $RigaFatturazione->prezzo = $RigaFatturazione->listinoPrezzi->prezzo;
40
+                }elseif($RigaFatturazione->ordini() <= 5){
41
+                    $RigaFatturazione->stato = RigaFatturazione::GRATUITO;
42
+                }
43
+                $RigaFatturazione->save();
44
+            }else{
45
+                $RigaFatturazione = $this->creaRigaFatturazioneOdiernaDispositivo($dispositivo);
46
+            }
47
+
48
+        return $RigaFatturazione;
49
+    }
50
+
51
+    //Crea RigaFatturazione odierna per dispositivo
52
+    public function creaRigaFatturazioneOdiernaDispositivo(Dispositivo $dispositivo){
53
+        $RigaFatturazione = new RigaFatturazione();
54
+        // $RigaFatturazione->user_id = auth()->user()->id;
55
+        $RigaFatturazione->attivita_id = $dispositivo->attivita_id;
56
+        $RigaFatturazione->dispositivo_id = $dispositivo->id;
57
+        // $RigaFatturazione->tipo_dispositivo = $dispositivo->tipo;
58
+        $RigaFatturazione->listino_prezzi_id = ListinoPrezzi::where('tipo_dispositivo', $dispositivo->tipo)->first()->id;
59
+        $RigaFatturazione->tipo = 'consumo';
60
+        $RigaFatturazione->prezzo = 0;
61
+        $RigaFatturazione->stato = RigaFatturazione::GRATUITO;
62
+        $RigaFatturazione->note = 'Utilizzo dispositivo <b>'.$dispositivo->nome.'</b> per l\'attività <b>'.$dispositivo->attivita->nome.'</b> il <b>'.now()->format('d/m/Y').'</b>';
63
+        $RigaFatturazione->path_pdf = null;
64
+        $RigaFatturazione->path_xml = null;
65
+        $RigaFatturazione->metadata = null;
66
+        $RigaFatturazione->ordini = 1;
67
+        $RigaFatturazione->save();
68
+        return $RigaFatturazione;
69
+    }
70
+    //controllo qta ordini pagati oggi per dispositivo
71
+    public function fatturaDispositivoOggi(Dispositivo $dispositivo){
72
+        $ordiniPagati = Ordine::where('dispositivo_id', $dispositivo->id)
73
+        ->whereDate('created_at', now()->startOfDay())
74
+        ->where('stato', Ordine::PAGATO)
75
+        ->count();
76
+
77
+        return $ordiniPagati > 5 ? true : false;
78
+    }
79
+
80
+    public function RigaFatturazioneOdiernaDispositivo(Dispositivo $dispositivo){
81
+        $RigaFatturazione = RigaFatturazione::where('dispositivo_id', $dispositivo->id)
82
+        ->whereDate('created_at', now()->startOfDay())->first();
83
+        return $RigaFatturazione;
84
+        }
85
+}

+ 96
- 0
app/Services/ListinoPrezzi/ListinoPubblicoPdfExporter.php View File

@@ -0,0 +1,96 @@
1
+<?php
2
+
3
+namespace App\Services\ListinoPrezzi;
4
+
5
+use Illuminate\Support\Facades\File;
6
+use Illuminate\Support\Facades\URL;
7
+use Illuminate\Support\Str;
8
+use RuntimeException;
9
+use Symfony\Component\Process\Process;
10
+
11
+class ListinoPubblicoPdfExporter
12
+{
13
+    public function exportFromHtml(string $html): string
14
+    {
15
+        $chrome = $this->resolveChromePath();
16
+        if ($chrome === null) {
17
+            throw new RuntimeException('Chrome/Edge non trovato: impossibile generare un PDF fedele alla pagina.');
18
+        }
19
+
20
+        $dir = storage_path('app/listino_prezzi');
21
+        File::ensureDirectoryExists($dir);
22
+
23
+        $token = (string) Str::uuid();
24
+        $htmlPath = $dir.DIRECTORY_SEPARATOR.'listino-'.$token.'.html';
25
+        $pdfPath = $dir.DIRECTORY_SEPARATOR.'listino-'.$token.'.pdf';
26
+
27
+        File::put($htmlPath, $html);
28
+
29
+        $fileUrl = 'file:///'.str_replace('\\', '/', $htmlPath);
30
+
31
+        $process = new Process([
32
+            $chrome,
33
+            '--headless=new',
34
+            '--disable-gpu',
35
+            '--allow-file-access-from-files',
36
+            '--no-pdf-header-footer',
37
+            '--hide-scrollbars',
38
+            '--run-all-compositor-stages-before-draw',
39
+            '--virtual-time-budget=10000',
40
+            // Viewport larga: evita che le media query mobile mettano le card in colonna unica
41
+            '--window-size=1280,2000',
42
+            '--force-device-scale-factor=1',
43
+            '--print-to-pdf='.$pdfPath,
44
+            $fileUrl,
45
+        ]);
46
+        $process->setTimeout(90);
47
+        $process->run();
48
+
49
+        File::delete($htmlPath);
50
+
51
+        if (! File::exists($pdfPath) || File::size($pdfPath) < 1000) {
52
+            $error = trim($process->getErrorOutput().' '.$process->getOutput());
53
+            throw new RuntimeException('Generazione PDF fallita.'.($error !== '' ? ' '.$error : ''));
54
+        }
55
+
56
+        return $pdfPath;
57
+    }
58
+
59
+    public function renderPageHtml($listinoPrezzi): string
60
+    {
61
+        $root = rtrim((string) env('LISTINO_PDF_BASE_URL', config('app.url')), '/');
62
+        if ($root !== '') {
63
+            URL::forceRootUrl($root);
64
+        }
65
+
66
+        request()->merge(['pdf' => 1]);
67
+
68
+        return view('listino_prezzi.pubblico.show', [
69
+            'listino_prezzi' => $listinoPrezzi,
70
+        ])->render();
71
+    }
72
+
73
+    private function resolveChromePath(): ?string
74
+    {
75
+        $candidates = array_filter([
76
+            env('CHROME_PATH'),
77
+            'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
78
+            'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
79
+            'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe',
80
+            'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
81
+            '/usr/bin/google-chrome',
82
+            '/usr/bin/google-chrome-stable',
83
+            '/usr/bin/chromium',
84
+            '/usr/bin/chromium-browser',
85
+            '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
86
+        ]);
87
+
88
+        foreach ($candidates as $path) {
89
+            if (is_string($path) && $path !== '' && File::exists($path)) {
90
+                return $path;
91
+            }
92
+        }
93
+
94
+        return null;
95
+    }
96
+}

+ 11
- 0
config/filesystems.php View File

@@ -76,6 +76,17 @@ return [
76 76
             'root' => storage_path('app/public/eventi'),
77 77
             'url' => env('APP_URL').'/storage/eventi',
78 78
             'visibility' => 'public',
79
+        ],'copertinaAttivita' => [
80
+            'driver' => 'local',
81
+            'root' => storage_path('app/public/copertinaAttivita'),
82
+            'url' => env('APP_URL').'/storage/copertinaAttivita',
83
+            'visibility' => 'public',
84
+        ],
85
+        'festAgent' => [
86
+            'driver' => 'local',
87
+            'root' => storage_path('app/public/festAgent'),
88
+            'url' => env('APP_URL').'/storage/festAgent',
89
+            'visibility' => 'public',
79 90
         ],
80 91
         's3' => [
81 92
             'driver' => 's3',

+ 1
- 1
database/migrations/2026_03_25_212234_modifiche_alle_tabelle.php View File

@@ -21,7 +21,7 @@ return new class extends Migration
21 21
         Schema::table('ordine', function (Blueprint $table) {
22 22
             $table->string('stato')->nullable()->change();
23 23
             $table->decimal('prezzo', 10, 2)->nullable()->change();
24
-            $table->foreign('dispositivo_id')->references('id')->on('dispositivo')->onDelete('cascade');
24
+            $table->foreign('dispositivo_id')->references('id')->on('dispositivo')->onDelete('set null');
25 25
 
26 26
         });
27 27
 

+ 1
- 1
database/migrations/2026_06_18_164218_contabilizzare_metodo_di_pagamento.php View File

@@ -12,7 +12,7 @@ return new class extends Migration
12 12
     public function up(): void
13 13
     {
14 14
         Schema::table('metodo_pagamento', function (Blueprint $table) {
15
-            $table->boolean('escludi_contabilita')->default(false)->nullable();
15
+            $table->boolean('escludi_contabilita')->nullable()->default(false);
16 16
         });
17 17
     }
18 18
 

+ 41
- 0
database/migrations/2026_06_25_131429_prenotazione_tavolo.php View File

@@ -0,0 +1,41 @@
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('prenotazione_tavolo', function (Blueprint $table) {
15
+            $table->id();
16
+            $table->string('nome');
17
+            $table->string('email')->nullable();
18
+            $table->string('telefono')->nullable();
19
+            $table->bigInteger('tavolo_id')->unsigned()->nullable();
20
+            $table->string('data_prenotazione')->nullable();
21
+            $table->string('ora_prenotazione')->nullable();
22
+            $table->string('note')->nullable();
23
+            $table->string('stato')->nullable();
24
+            $table->bigInteger('attivita_id')->unsigned()->nullable();
25
+            $table->integer('coperti')->nullable();
26
+            $table->timestamps();
27
+        });
28
+        Schema::table('prenotazione_tavolo', function (Blueprint $table) {
29
+            $table->foreign('attivita_id')->references('id')->on('attivita')->onDelete('cascade');
30
+            $table->foreign('tavolo_id')->references('id')->on('tavolo')->onDelete('cascade');
31
+        });
32
+    }
33
+
34
+    /**
35
+     * Reverse the migrations.
36
+     */
37
+    public function down(): void
38
+    {
39
+        Schema::dropIfExists('prenotazione_tavolo');
40
+    }
41
+};

+ 38
- 0
database/migrations/2026_06_26_160233_slot_temporali_prenotazione_tavoli.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('slot_temporali_prenotazione_tavoli', function (Blueprint $table) {
15
+            $table->id();
16
+            $table->bigInteger('attivita_id')->unsigned()->nullable();
17
+            $table->string('data_prenotazione')->nullable();
18
+            $table->time('ora_inizio')->nullable();
19
+            $table->time('ora_fine')->nullable();
20
+            $table->bigInteger('tavolo_id')->unsigned()->nullable();
21
+            $table->string('tipo')->nullable();
22
+            $table->integer('posti_disponibili')->nullable();
23
+            $table->timestamps();
24
+        });
25
+        Schema::table('slot_temporali_prenotazione_tavoli', function (Blueprint $table) {
26
+            $table->foreign('attivita_id')->references('id')->on('attivita')->onDelete('cascade');
27
+            $table->foreign('tavolo_id')->references('id')->on('tavolo')->onDelete('cascade');
28
+        });
29
+    }
30
+
31
+    /**
32
+     * Reverse the migrations.
33
+     */
34
+    public function down(): void
35
+    {
36
+        Schema::dropIfExists('slot_temporali_prenotazione_tavoli');
37
+    }
38
+};

+ 28
- 0
database/migrations/2026_06_29_225649_deeplink_colonna.php View File

@@ -0,0 +1,28 @@
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::table('endpoint', function (Blueprint $table) {
15
+            $table->json('deeplink_token')->nullable();
16
+        });
17
+    }
18
+
19
+    /**
20
+     * Reverse the migrations.
21
+     */
22
+    public function down(): void
23
+    {
24
+        Schema::table('endpoint', function (Blueprint $table) {
25
+            $table->dropColumn('deeplink_token');
26
+        });
27
+    }
28
+};

+ 34
- 0
database/migrations/2026_07_09_145153_saltacoda_riga_ordine_has_variante.php View File

@@ -0,0 +1,34 @@
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('saltacoda_riga_ordine_has_variante', function (Blueprint $table) {
15
+            $table->id();
16
+            $table->bigInteger('saltacoda_riga_ordine_id')->unsigned()->nullable();
17
+            $table->foreign('saltacoda_riga_ordine_id')->references('id')->on('saltacoda_riga_ordine')->onDelete('cascade')->name('sc_ro_has_var_riga_foreign');
18
+            $table->bigInteger('variante_id')->unsigned()->nullable();
19
+            $table->foreign('variante_id')->references('id')->on('variante_piatto')->onDelete('cascade')->name('sc_ro_has_var_variante_foreign');
20
+            $table->string('nota')->nullable();
21
+            $table->timestamps();
22
+
23
+            $table->unique(['saltacoda_riga_ordine_id', 'variante_id'],'sc_ro_has_var_riga_var_unique');
24
+        });
25
+    }
26
+    /**
27
+     * Reverse the migrations.
28
+     */
29
+
30
+    public function down(): void
31
+    {
32
+        Schema::dropIfExists('saltacoda_riga_ordine_has_variante');
33
+    }
34
+};

+ 41
- 0
database/migrations/2026_07_09_181834_fatturazione.php View File

@@ -0,0 +1,41 @@
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('fatturazione', function (Blueprint $table) {
15
+            $table->id();
16
+            $table->bigInteger('user_id')->nullable()->unsigned();
17
+            $table->foreign('user_id')->references('id')->on('users')->onDelete('set null');
18
+            $table->bigInteger('attivita_id')->nullable()->unsigned();
19
+            $table->foreign('attivita_id')->references('id')->on('attivita')->onDelete('set null');
20
+            $table->bigInteger('dispositivo_id')->nullable()->unsigned();
21
+            $table->foreign('dispositivo_id')->references('id')->on('dispositivo')->onDelete('set null');
22
+            $table->string('tipo_dispositivo')->nullable();
23
+            $table->integer('ordini')->nullable();
24
+            $table->decimal('prezzo', 10, 2)->nullable();
25
+            $table->string('stato')->nullable();
26
+            $table->string('note')->nullable();
27
+            $table->string('path_pdf')->nullable();
28
+            $table->string('path_xml')->nullable();
29
+            $table->json('metadata')->nullable();
30
+            $table->timestamps();
31
+        });
32
+    }
33
+
34
+    /**
35
+     * Reverse the migrations.
36
+     */
37
+    public function down(): void
38
+    {
39
+        Schema::dropIfExists('fatturazione');
40
+    }
41
+};

+ 28
- 0
database/migrations/2026_07_12_175737_modifica_saltacoda.php View File

@@ -0,0 +1,28 @@
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::table('saltacoda', function (Blueprint $table) {
15
+            $table->json('istruzioni')->nullable();
16
+        });
17
+    }
18
+
19
+    /**
20
+     * Reverse the migrations.
21
+     */
22
+    public function down(): void
23
+    {
24
+        Schema::table('saltacoda', function (Blueprint $table) {
25
+            $table->dropColumn('istruzioni');
26
+        });
27
+    }
28
+};

+ 33
- 0
database/migrations/2026_07_18_160138_listino_prezzi.php View File

@@ -0,0 +1,33 @@
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('listino_prezzi', function (Blueprint $table) {
15
+            $table->id();
16
+            $table->string('label'); //articolo o servizio
17
+            $table->string('descrizione')->nullable(); //descrizione dell'articolo o servizio
18
+            $table->string('tipo_dispositivo')->nullable(); // hardware, licenza, aux
19
+            $table->decimal('prezzo', 10, 2)->nullable(); //prezzo dell'articolo o servizio
20
+            $table->string('tipo_prezzo')->nullable(); //giornaliero, annuale, abbonamento,dedicata
21
+            $table->string('note')->nullable(); //note dell'articolo o servizio
22
+            $table->timestamps();
23
+        });
24
+    }
25
+
26
+    /**
27
+     * Reverse the migrations.
28
+     */
29
+    public function down(): void
30
+    {
31
+        Schema::dropIfExists('listino_prezzi');
32
+    }
33
+};

+ 32
- 0
database/migrations/2026_07_19_175351_edit_fatturazione.php View File

@@ -0,0 +1,32 @@
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::table('fatturazione', function (Blueprint $table) {
15
+            $table->integer('qta')->nullable();
16
+            $table->bigInteger('listino_prezzi_id')->unsigned()->nullable();
17
+            $table->foreign('listino_prezzi_id')->references('id')->on('listino_prezzi');
18
+        });
19
+    }
20
+
21
+    /**
22
+     * Reverse the migrations.
23
+     */
24
+    public function down(): void
25
+    {
26
+        Schema::table('fatturazione', function (Blueprint $table) {
27
+            $table->dropColumn('qta');
28
+            $table->dropForeign(['listino_prezzi_id']);
29
+            $table->dropColumn('listino_prezzi_id');
30
+        });
31
+    }
32
+};

+ 122
- 0
database/migrations/2026_07_19_182854_edit_fatturazione_to_riga_fatturazione.php View File

@@ -0,0 +1,122 @@
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
+        if(!Schema::hasTable('fattura')){
15
+        Schema::create('fattura', function (Blueprint $table) {
16
+            $table->id();
17
+            $table->bigInteger('user_id')->nullable()->unsigned();
18
+            $table->foreign('user_id')->references('id')->on('users')->onDelete('set null');
19
+            $table->bigInteger('attivita_id')->nullable()->unsigned();
20
+            $table->foreign('attivita_id')->references('id')->on('attivita')->onDelete('set null');
21
+            $table->string('numero_fattura')->nullable();
22
+            $table->date('data_fattura')->nullable();
23
+            $table->string('stato')->nullable();
24
+            $table->string('note')->nullable();
25
+            $table->string('path_pdf')->nullable();
26
+            $table->string('path_xml')->nullable();
27
+            $table->json('metadata')->nullable();
28
+            $table->decimal('totale_imponibile', 10, 2)->nullable();
29
+            $table->decimal('totale_netto', 10, 2)->nullable();
30
+            $table->decimal('totale_iva', 10, 2)->nullable();
31
+            $table->decimal('totale_lordo', 10, 2)->nullable();
32
+            $table->decimal('sconto', 10, 2)->nullable();
33
+            $table->json('info')->nullable();
34
+            $table->timestamps();
35
+        });
36
+        }
37
+        if(!Schema::hasTable('riga_fatturazione')){
38
+            Schema::rename('fatturazione', 'riga_fatturazione');
39
+            }
40
+        Schema::table('riga_fatturazione', function (Blueprint $table) {
41
+            // Corretto scenario per rimozione colonne e foreign key:
42
+            if (Schema::hasColumn('riga_fatturazione', 'user_id')) {
43
+                $table->dropForeign('fatturazione_user_id_foreign');
44
+                $table->dropColumn('user_id');
45
+            }
46
+            if (Schema::hasColumn('riga_fatturazione', 'tipo_dispositivo')) {
47
+                $table->dropColumn('tipo_dispositivo');
48
+            }
49
+    
50
+        });
51
+
52
+        Schema::table('riga_fatturazione', function (Blueprint $table) {
53
+            $table->bigInteger('fattura_id')->nullable()->unsigned();
54
+            $table->foreign('fattura_id')->references('id')->on('fattura');
55
+            $table->string('tipo')->nullable();
56
+        });
57
+        
58
+    }
59
+
60
+    /**
61
+     * Reverse the migrations.
62
+     */
63
+    public function down(): void
64
+    {
65
+        // Rollback: torna alla situazione originale
66
+
67
+        // Prima rimuove la foreign key e colonna appena aggiunte
68
+        if (Schema::hasTable('riga_fatturazione')) {
69
+            Schema::table('riga_fatturazione', function (Blueprint $table) {
70
+                // Drop new foreign keys/columns only if they exist
71
+                if (Schema::hasColumn('riga_fatturazione', 'fattura_id')) {
72
+                    $table->dropForeign(['fattura_id']);
73
+                    $table->dropColumn('fattura_id');
74
+                }
75
+                if (Schema::hasColumn('riga_fatturazione', 'tipo')) {
76
+                    $table->dropColumn('tipo');
77
+                }
78
+            });
79
+
80
+            // Rinomina la tabella di nuovo a 'fatturazione'
81
+            Schema::rename('riga_fatturazione', 'fatturazione');
82
+
83
+            // Riaggiungi le colonne rimosse se necessario
84
+            Schema::table('fatturazione', function (Blueprint $table) {
85
+                if (!Schema::hasColumn('fatturazione', 'user_id')) {
86
+                    $table->bigInteger('user_id')->nullable()->unsigned();
87
+                }
88
+                if (!Schema::hasColumn('fatturazione', 'tipo_dispositivo')) {
89
+                    $table->string('tipo_dispositivo')->nullable();
90
+                }
91
+                if (!Schema::hasColumn('fatturazione', 'attivita_id')) {
92
+                    $table->bigInteger('attivita_id')->nullable()->unsigned();
93
+                }
94
+                if (!Schema::hasColumn('fatturazione', 'dispositivo_id')) {
95
+                    $table->bigInteger('dispositivo_id')->nullable()->unsigned();
96
+                }
97
+            });
98
+
99
+            // Ricrea le foreign key originarie
100
+            Schema::table('fatturazione', function (Blueprint $table) {
101
+                $fk = $table->getConnection()->getDoctrineSchemaManager();
102
+                $foreigns = $fk->listTableForeignKeys('fatturazione');
103
+                $fk_names = array_map(fn($f) => $f->getName(), $foreigns);
104
+
105
+                if (!in_array('fatturazione_user_id_foreign', $fk_names)) {
106
+                    $table->foreign('user_id')->references('id')->on('users')->onDelete('set null');
107
+                }
108
+                if (!in_array('fatturazione_attivita_id_foreign', $fk_names)) {
109
+                    $table->foreign('attivita_id')->references('id')->on('attivita')->onDelete('set null');
110
+                }
111
+                if (!in_array('fatturazione_dispositivo_id_foreign', $fk_names)) {
112
+                    $table->foreign('dispositivo_id')->references('id')->on('dispositivo')->onDelete('set null');
113
+                }
114
+            });
115
+        }
116
+
117
+        // Infine elimina la tabella 'fattura'
118
+        if (Schema::hasTable('fattura')) {
119
+            Schema::dropIfExists('fattura');
120
+        }
121
+    }
122
+};

+ 29
- 0
database/migrations/2026_07_19_193750_edit_listino_prezzi.php View File

@@ -0,0 +1,29 @@
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::table('listino_prezzi', function (Blueprint $table) {
15
+            $table->string('categoria')->nullable();
16
+
17
+        });
18
+    }
19
+
20
+    /**
21
+     * Reverse the migrations.
22
+     */
23
+    public function down(): void
24
+    {
25
+        Schema::table('listino_prezzi', function (Blueprint $table) {
26
+            $table->dropColumn('categoria');
27
+        });
28
+    }
29
+};

+ 24
- 0
database/migrations/2026_07_20_224254_fatture_emesse.php View File

@@ -0,0 +1,24 @@
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
+        //
15
+    }
16
+
17
+    /**
18
+     * Reverse the migrations.
19
+     */
20
+    public function down(): void
21
+    {
22
+        //
23
+    }
24
+};

+ 36
- 0
database/migrations/2026_07_24_154650_asporto.php View File

@@ -0,0 +1,36 @@
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('asporto', function (Blueprint $table) {
15
+            $table->id();
16
+            $table->bigInteger('ordine_id')->nullable()->unsigned();
17
+            $table->string('stato')->nullable();
18
+            $table->time('ora_ritiro')->nullable();
19
+            $table->json('info')->nullable();
20
+            $table->timestamps();
21
+
22
+            $table->foreign('ordine_id')->references('id')->on('ordine')->onDelete('set null');
23
+        });
24
+    }
25
+
26
+    /**
27
+     * Reverse the migrations.
28
+     */
29
+    public function down(): void
30
+    {
31
+        Schema::table('asporto', function (Blueprint $table) {
32
+            $table->dropForeign(['ordine_id']);
33
+            });
34
+        Schema::dropIfExists('asporto');
35
+    }
36
+};

+ 1
- 0
database/seeders/DatabaseSeeder.php View File

@@ -17,6 +17,7 @@ class DatabaseSeeder extends Seeder
17 17
     $this->call(AdminSeed::class);
18 18
     $this->call(PermissionSeed::class);
19 19
     $this->call(AllergeneSeed::class);
20
+    $this->call(ListinoPrezziSeed::class);
20 21
     $this->call(TombolaSeed::class);
21 22
     $this->call(MetodoPagamentoSeed::class);
22 23
     $this->call(CategoriaContabileSeed::class);

+ 68
- 0
database/seeders/ListinoPrezziSeed.php View File

@@ -0,0 +1,68 @@
1
+<?php
2
+
3
+namespace Database\Seeders;
4
+
5
+use Illuminate\Database\Console\Seeds\WithoutModelEvents;
6
+use Illuminate\Database\Seeder;
7
+use App\Models\ListinoPrezzi;
8
+
9
+class ListinoPrezziSeed extends Seeder
10
+{
11
+    /**
12
+     * Run the database seeds.
13
+     */
14
+
15
+    const VOCI_LISTINO_PREZZI = [
16
+        //Hardware
17
+        [
18
+            'label' => 'NOLO Stampante POS 80',
19
+            'descrizione' => 'Noleggio giornaliero di Stampante POS 80',
20
+            'tipo_dispositivo' => 'stampante',
21
+            'categoria' => ListinoPrezzi::CATEGORIA_HARDWARE,
22
+            'tipo_prezzo' => ListinoPrezzi::TIPI_PREZZO_GIORNALIERO,
23
+            'prezzo' => 5,
24
+        ],
25
+        [
26
+            'label' => 'NOLO PC per cassa',
27
+            'descrizione' => 'Noleggio giornaliero di PC per cassa',
28
+            'tipo_dispositivo' => 'pc',
29
+            'categoria' => ListinoPrezzi::CATEGORIA_HARDWARE,
30
+            'tipo_prezzo' => ListinoPrezzi::TIPI_PREZZO_GIORNALIERO,
31
+            'prezzo' => 5,
32
+        ],
33
+        //Software
34
+        [
35
+            'label' => 'NOLO Licenza punto vendita',
36
+            'descrizione' => 'Noleggio giornaliero di Licenza punto vendita',
37
+            'tipo_dispositivo' => \App\Models\Dispositivo::CASSA,
38
+            'categoria' => ListinoPrezzi::CATEGORIA_SOFTWARE,
39
+            'tipo_prezzo' => ListinoPrezzi::TIPI_PREZZO_GIORNALIERO,
40
+            'prezzo' => 20,
41
+        ],
42
+        //Servizi
43
+        [
44
+            'label' => 'NOLO Configurazione punto vendita',
45
+            'descrizione' => 'Configurazione punto vendita',
46
+            'tipo_dispositivo' => 'servizio',
47
+            'categoria' => ListinoPrezzi::CATEGORIA_SERVIZIO,
48
+            'tipo_prezzo' => ListinoPrezzi::TIPI_PREZZO_UNA_TANTUM,
49
+            'prezzo' => 50,
50
+        ],
51
+        [
52
+            'label' => 'NOLO Copertura WiFi ausiliaria)',
53
+            'descrizione' => 'Copertura WiFi ausiliaria: 1Km con rete SIM 5G',
54
+            'tipo_dispositivo' => 'servizio',
55
+            'categoria' => ListinoPrezzi::CATEGORIA_SERVIZIO,
56
+            'tipo_prezzo' => ListinoPrezzi::TIPI_PREZZO_GIORNALIERO,
57
+            'prezzo' => 15,
58
+        ],
59
+    ];
60
+
61
+
62
+    public function run(): void
63
+    {
64
+        foreach(self::VOCI_LISTINO_PREZZI as $voce){
65
+            ListinoPrezzi::create($voce);
66
+        }
67
+    }
68
+}

BIN
public/assets/img/cover_login.jpeg View File


BIN
public/assets/img/cover_login.png View File


BIN
public/assets/img/cover_login1.jpg View File


BIN
public/assets/img/logo_elephantech_icon.png View File


+ 0
- 0
public/assets/img/logo_fest.png View File


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save