Roberto Santini 2 horas atrás
pai
commit
8f89633e21

+ 3
- 0
app/DataTables/ContrattoServizioDataTableEditor.php Ver arquivo

@@ -100,6 +100,9 @@ class ContrattoServizioDataTableEditor extends DataTablesEditor
100 100
         if (empty($data['periodo']) && $tpl->periodo) {
101 101
           $data['periodo'] = $tpl->periodo;
102 102
         }
103
+        if (! array_key_exists('ore_illimitate', $data) && ($this->prevTemplateId === null || (int) $data['contratto_template_id'] !== (int) $this->prevTemplateId)) {
104
+          $data['ore_illimitate'] = $tpl->haOreIllimitate();
105
+        }
103 106
       }
104 107
     }
105 108
 

+ 6
- 0
app/Http/Controllers/ContrattoServizioController.php Ver arquivo

@@ -73,6 +73,7 @@ class ContrattoServizioController extends Controller implements HasMiddleware
73 73
       'nome' => 'nullable|string|max:255',
74 74
       'costo' => 'nullable|numeric|min:0',
75 75
       'periodo' => 'nullable|string|max:50',
76
+      'ore_illimitate' => 'nullable|boolean',
76 77
       'data_inizio' => 'nullable|date',
77 78
       'data_fine' => 'nullable|date|after_or_equal:data_inizio',
78 79
       'note' => 'nullable|string',
@@ -132,6 +133,7 @@ class ContrattoServizioController extends Controller implements HasMiddleware
132 133
       'nome' => 'nullable|string|max:255',
133 134
       'costo' => 'nullable|numeric|min:0',
134 135
       'periodo' => 'nullable|string|max:50',
136
+      'ore_illimitate' => 'nullable|boolean',
135 137
       'data_inizio' => 'nullable|date',
136 138
       'data_fine' => 'nullable|date|after_or_equal:data_inizio',
137 139
       'note' => 'nullable|string',
@@ -251,6 +253,7 @@ class ContrattoServizioController extends Controller implements HasMiddleware
251 253
       ? $validated['costo']
252 254
       : null;
253 255
     $contratto->periodo = $validated['periodo'] ?? null;
256
+    $contratto->ore_illimitate = request()->boolean('ore_illimitate');
254 257
     $contratto->data_inizio = $validated['data_inizio'] ?? null;
255 258
     $contratto->data_fine = $validated['data_fine'] ?? null;
256 259
     $contratto->note = $validated['note'] ?? null;
@@ -270,6 +273,9 @@ class ContrattoServizioController extends Controller implements HasMiddleware
270 273
         if (empty($contratto->periodo) && $tpl->periodo) {
271 274
           $contratto->periodo = $tpl->periodo;
272 275
         }
276
+        if (! request()->exists('ore_illimitate') && $tpl->haOreIllimitate()) {
277
+          $contratto->ore_illimitate = true;
278
+        }
273 279
       }
274 280
     }
275 281
   }

+ 3
- 0
app/Http/Controllers/ContrattoTemplateController.php Ver arquivo

@@ -131,6 +131,7 @@ class ContrattoTemplateController extends Controller implements HasMiddleware
131 131
         'text' => $t->label,
132 132
         'costo_base' => $t->costo_base,
133 133
         'periodo' => $t->periodo,
134
+        'ore_illimitate' => $t->haOreIllimitate(),
134 135
         'nome' => $t->nome,
135 136
       ])->values(),
136 137
     ]);
@@ -143,6 +144,7 @@ class ContrattoTemplateController extends Controller implements HasMiddleware
143 144
       'nome' => 'required|string|max:255',
144 145
       'descrizione' => 'nullable|string',
145 146
       'periodo' => 'nullable|string|max:50',
147
+      'ore_illimitate' => 'nullable|boolean',
146 148
       'costo_base' => 'nullable|numeric|min:0',
147 149
       'attivo' => 'nullable|boolean',
148 150
       'todos' => 'nullable|array',
@@ -172,6 +174,7 @@ class ContrattoTemplateController extends Controller implements HasMiddleware
172 174
     $template->nome = $validated['nome'];
173 175
     $template->descrizione = $validated['descrizione'] ?? null;
174 176
     $template->periodo = $validated['periodo'] ?? null;
177
+    $template->ore_illimitate = request()->boolean('ore_illimitate');
175 178
     $template->costo_base = $validated['costo_base'] ?? null;
176 179
     $template->attivo = array_key_exists('attivo', $validated)
177 180
       ? filter_var($validated['attivo'], FILTER_VALIDATE_BOOLEAN)

+ 1
- 0
app/Http/Controllers/SegnalazioneController.php Ver arquivo

@@ -112,6 +112,7 @@ class SegnalazioneController extends Controller implements HasMiddleware
112 112
           'ore_rimanenti_label' => Config::formatTempo((int) round(max(0, $rimanenti) * 60)),
113 113
           'periodo' => $cs->periodo_ore,
114 114
           'periodo_label' => $cs->periodo_riferimento_label,
115
+          'ore_illimitate' => $cs->haOreIllimitate(),
115 116
         ];
116 117
       })
117 118
       ->values();

+ 15
- 0
app/Models/ContrattoServizio.php Ver arquivo

@@ -5,6 +5,21 @@ use Carbon\Carbon;
5 5
 
6 6
 class ContrattoServizio extends \App\Models\AbstractModels\AbstractContrattoServizio
7 7
 {
8
+    protected function casts(): array
9
+    {
10
+        return array_merge(parent::casts(), [
11
+            'ore_illimitate' => 'boolean',
12
+        ]);
13
+    }
14
+
15
+    /**
16
+     * Il canone comprende tutto il tempo dei ticket collegati, senza monte ore.
17
+     */
18
+    public function haOreIllimitate(): bool
19
+    {
20
+        return (bool) $this->ore_illimitate;
21
+    }
22
+
8 23
     protected static function booted(): void
9 24
     {
10 25
         static::saving(function (self $contratto) {

+ 12
- 0
app/Models/ContrattoTemplate.php Ver arquivo

@@ -3,6 +3,18 @@ namespace App\Models;
3 3
 
4 4
 class ContrattoTemplate extends \App\Models\AbstractModels\AbstractContrattoTemplate
5 5
 {
6
+    protected function casts(): array
7
+    {
8
+        return array_merge(parent::casts(), [
9
+            'ore_illimitate' => 'boolean',
10
+        ]);
11
+    }
12
+
13
+    public function haOreIllimitate(): bool
14
+    {
15
+        return (bool) $this->ore_illimitate;
16
+    }
17
+
6 18
     public const PERIODI = [
7 19
         'mensile' => 'Mensile',
8 20
         'trimestrale' => 'Trimestrale',

+ 13
- 0
app/Services/FatturazioneGeneraService.php Ver arquivo

@@ -449,6 +449,19 @@ class FatturazioneGeneraService
449 449
 
450 450
         if ($segnalazione->contratto_servizio_id && $segnalazione->contrattoServizio) {
451 451
             $contratto = $segnalazione->contrattoServizio;
452
+            if ($contratto->haOreIllimitate()) {
453
+                return $this->conCostoUscita([
454
+                    'copertura' => 'contratto',
455
+                    'minuti_coperti' => $minuti,
456
+                    'minuti_fatturare' => 0,
457
+                    'tariffa' => null,
458
+                    'importo_tempo' => 0.0,
459
+                    'importo_spesa' => $fatturaSpesa,
460
+                    'tariffa_mancante' => false,
461
+                    'note' => 'Compreso nel canone, senza limite di ore',
462
+                ], $annotazione, $segnalazione);
463
+            }
464
+
452 465
             [$wFrom, $wTo] = $contratto->intervalloPeriodoOrePerData($data);
453 466
             $poolKey = 'c:'.$contratto->id.':'.$this->chiaveFinestra($wFrom, $wTo);
454 467
             if (! isset($pools[$poolKey])) {

+ 22
- 0
database/migrations/2026_11_03_154500_add_ore_illimitate_to_contratto_servizio.php Ver arquivo

@@ -0,0 +1,22 @@
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
+    public function up(): void
10
+    {
11
+        Schema::table('contratto_servizio', function (Blueprint $table) {
12
+            $table->boolean('ore_illimitate')->default(false)->after('periodo');
13
+        });
14
+    }
15
+
16
+    public function down(): void
17
+    {
18
+        Schema::table('contratto_servizio', function (Blueprint $table) {
19
+            $table->dropColumn('ore_illimitate');
20
+        });
21
+    }
22
+};

+ 22
- 0
database/migrations/2026_11_03_154800_add_ore_illimitate_to_contratto_template.php Ver arquivo

@@ -0,0 +1,22 @@
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
+    public function up(): void
10
+    {
11
+        Schema::table('contratto_template', function (Blueprint $table) {
12
+            $table->boolean('ore_illimitate')->default(false)->after('periodo');
13
+        });
14
+    }
15
+
16
+    public function down(): void
17
+    {
18
+        Schema::table('contratto_template', function (Blueprint $table) {
19
+            $table->dropColumn('ore_illimitate');
20
+        });
21
+    }
22
+};

+ 13
- 0
resources/views/contratto_servizio/create.blade.php Ver arquivo

@@ -60,6 +60,7 @@
60 60
               data-nome="{{ $t->nome }}"
61 61
               data-costo="{{ $t->costo_base }}"
62 62
               data-periodo="{{ $t->periodo }}"
63
+              data-ore-illimitate="{{ $t->haOreIllimitate() ? '1' : '0' }}"
63 64
               @selected((string)old('contratto_template_id') === (string)$t->id)>{{ $t->label }}</option>
64 65
             @endforeach
65 66
           </select>
@@ -90,6 +91,16 @@
90 91
             @endforeach
91 92
           </select>
92 93
         </div>
94
+        <div class="col-md-4">
95
+          <label class="form-label d-block">Ticket compresi</label>
96
+          <div class="form-check form-switch mt-2">
97
+            <input type="hidden" name="ore_illimitate" value="0">
98
+            <input class="form-check-input" type="checkbox" name="ore_illimitate" value="1" id="ore_illimitate"
99
+              @checked(old('ore_illimitate'))>
100
+            <label class="form-check-label" for="ore_illimitate">Ore illimitate, comprese nel canone</label>
101
+          </div>
102
+          <div class="form-text">Il tempo dei ticket collegati non si fattura. Costi e uscite restano a parte.</div>
103
+        </div>
93 104
         <div class="col-md-2">
94 105
           <label class="form-label d-block">Attivo</label>
95 106
           <div class="form-check form-switch mt-2">
@@ -155,6 +166,8 @@
155 166
     if (!nome.value) nome.value = opt.dataset.nome || '';
156 167
     if (!costo.value && opt.dataset.costo) costo.value = opt.dataset.costo;
157 168
     if (!periodo.value && opt.dataset.periodo) periodo.value = opt.dataset.periodo;
169
+    const ore = document.getElementById('ore_illimitate');
170
+    if (ore) ore.checked = opt.dataset.oreIllimitate === '1';
158 171
   });
159 172
 
160 173
   toggleOrigine();

+ 12
- 1
resources/views/contratto_servizio/form.blade.php Ver arquivo

@@ -184,7 +184,7 @@
184 184
           <label class="form-label">Costo (€)</label>
185 185
           <input type="number" step="0.01" min="0" name="costo" id="contratto_costo" class="form-control"
186 186
             value="{{ old('costo', $contratto->canone ?: $contratto->costo) }}">
187
-          <div class="form-text">Canone del contratto. Se i servizi hanno un prezzo, viene ricalcolato da quelli. Se le ore sono comprese (prezzo vuoto), indica qui l’importo (es. 1200 €).</div>
187
+          <div class="form-text">Canone del contratto. Se i servizi hanno un prezzo, viene ricalcolato da quelli. Se le ore sono comprese (prezzo vuoto), indica qui l’importo (es. 170 €).</div>
188 188
         </div>
189 189
         <div class="col-md-3">
190 190
           <label class="form-label">Periodo</label>
@@ -205,6 +205,16 @@
205 205
           <input type="date" name="data_fine" id="contratto_data_fine" class="form-control"
206 206
             value="{{ old('data_fine', optional($contratto->data_fine)->format('Y-m-d') ?? $contratto->data_fine) }}">
207 207
         </div>
208
+        <div class="col-md-4">
209
+          <label class="form-label d-block">Ticket compresi</label>
210
+          <div class="form-check form-switch mt-2">
211
+            <input type="hidden" name="ore_illimitate" value="0">
212
+            <input class="form-check-input" type="checkbox" name="ore_illimitate" value="1" id="ore_illimitate"
213
+              @checked(old('ore_illimitate', $contratto->haOreIllimitate()))>
214
+            <label class="form-check-label" for="ore_illimitate">Ore illimitate, comprese nel canone</label>
215
+          </div>
216
+          <div class="form-text">Il tempo dei ticket collegati non si fattura. Costi e uscite restano a parte.</div>
217
+        </div>
208 218
         <div class="col-md-2">
209 219
           <label class="form-label d-block">Attivo</label>
210 220
           <div class="form-check form-switch mt-2">
@@ -241,6 +251,7 @@
241 251
           Se l’unità è già un tempo (mese, anno) o un forfait, il periodo non compare.<br>
242 252
           <strong>A consumo</strong> = solo tariffa extra (es. 40 €/ora): non è un monte ore e non entra nel canone. Le ore extra sulle segnalazioni si fatturano a parte.<br>
243 253
           <strong>Ore comprese nel canone:</strong> una riga con le ore (es. 12, U.M. ora, periodo annuale) e <em>prezzo vuoto</em>. Il cliente vede “compreso”, non 12 × 100 €. Il canone sta nel campo <em>Costo</em> in testata (es. 1200 €). L’extra è a consumo.<br>
254
+          <strong>Senza limite di ore:</strong> spunta <em>Ore illimitate</em> in testata (es. servizio web 170 €/anno). Tutti i ticket collegati sono coperti, qualunque tempo ci sia sopra.<br>
244 255
           <strong>Validità</strong> (Dal → Al, sotto il prodotto) = da quando vale questa riga. Vuoto = tutta la durata del contratto.
245 256
           Per un aumento tariffe: chiudi la riga vecchia con <em>Al</em>, aggiungine una nuova con <em>Dal</em> e il prezzo aggiornato.
246 257
         </p>

+ 3
- 0
resources/views/contratto_servizio/pdf.blade.php Ver arquivo

@@ -40,6 +40,9 @@
40 40
         <span class="muted">Periodo: {{ $contratto->periodo_label }}</span>
41 41
         <br>
42 42
         <span class="muted">Costo: {{ number_format($contratto->canone, 2, ',', '.') }} €</span>
43
+        @if($contratto->haOreIllimitate())
44
+        <br><span class="muted">Ticket compresi senza limite di ore</span>
45
+        @endif
43 46
       </td>
44 47
     </tr>
45 48
   </table>

+ 3
- 0
resources/views/contratto_servizio/show.blade.php Ver arquivo

@@ -88,6 +88,9 @@
88 88
         <p class="mb-1"><strong>Costo:</strong>
89 89
           {{ number_format($contratto->canone, 2, ',', '.') }} €
90 90
         </p>
91
+        @if($contratto->haOreIllimitate())
92
+        <p class="mb-1"><strong>Ticket:</strong> ore illimitate, comprese nel canone</p>
93
+        @endif
91 94
         <p class="mb-1"><strong>Validità:</strong>
92 95
           {{ $contratto->data_inizio ? \Carbon\Carbon::parse($contratto->data_inizio)->format('d/m/Y') : '—' }}
93 96
           →

+ 11
- 1
resources/views/contratto_template/form.blade.php Ver arquivo

@@ -41,6 +41,16 @@
41 41
           <label class="form-label">Costo base (€)</label>
42 42
           <input type="number" step="0.01" min="0" name="costo_base" class="form-control" value="{{ old('costo_base', $template->costo_base) }}">
43 43
         </div>
44
+        <div class="col-md-4">
45
+          <label class="form-label d-block">Ticket compresi</label>
46
+          <div class="form-check form-switch mt-2">
47
+            <input type="hidden" name="ore_illimitate" value="0">
48
+            <input class="form-check-input" type="checkbox" name="ore_illimitate" value="1" id="ore_illimitate"
49
+              @checked(old('ore_illimitate', $template->haOreIllimitate()))>
50
+            <label class="form-check-label" for="ore_illimitate">Ore illimitate, comprese nel canone</label>
51
+          </div>
52
+          <div class="form-text">I contratti creati da questo template ereditano l’opzione. Il tempo dei ticket non si fattura.</div>
53
+        </div>
44 54
         <div class="col-md-2">
45 55
           <label class="form-label d-block">Attivo</label>
46 56
           <div class="form-check form-switch mt-2">
@@ -60,7 +70,7 @@
60 70
     <div class="card-header d-flex justify-content-between align-items-center">
61 71
       <div>
62 72
         <h5 class="mb-0">Servizi / prodotti inclusi</h5>
63
-        <small class="text-muted">Dal catalogo; verranno importati sul contratto cliente. Periodo <em>A consumo</em> = tariffa extra, senza monte ore e fuori canone.</small>
73
+        <small class="text-muted">Dal catalogo; verranno importati sul contratto cliente. Periodo <em>A consumo</em> = tariffa extra, senza monte ore e fuori canone. Con <em>Ore illimitate</em> il canone copre tutto il tempo dei ticket.</small>
64 74
       </div>
65 75
       <button type="button" class="btn btn-sm btn-outline-primary" id="add-riga-row"><i class="bx bx-plus"></i> Aggiungi</button>
66 76
     </div>

+ 3
- 0
resources/views/contratto_template/show.blade.php Ver arquivo

@@ -25,6 +25,9 @@
25 25
           @if($template->costo_base !== null)
26 26
           <span class="badge bg-label-primary">{{ number_format((float)$template->costo_base, 2, ',', '.') }} €</span>
27 27
           @endif
28
+          @if($template->haOreIllimitate())
29
+          <span class="badge bg-label-info">Ore illimitate</span>
30
+          @endif
28 31
           @if($template->attivo)
29 32
           <span class="badge bg-label-success">Attivo</span>
30 33
           @else

+ 12
- 1
resources/views/segnalazione/admin/nuova.blade.php Ver arquivo

@@ -525,7 +525,8 @@ $selectedContrattoId = old('contratto_servizio_id');
525 525
           totali_label: item.ore_totali_label,
526 526
           usate_label: item.ore_usate_label,
527 527
           rimanenti_label: item.ore_rimanenti_label,
528
-          periodo_label: item.periodo_label || ''
528
+          periodo_label: item.periodo_label || '',
529
+          illimitate: !!item.ore_illimitate
529 530
         });
530 531
         $el.append($opt);
531 532
       });
@@ -541,6 +542,16 @@ $selectedContrattoId = old('contratto_servizio_id');
541 542
       }
542 543
       const rimanenti = Number(ore.rimanenti || 0);
543 544
       const totali = Number(ore.totali || 0);
545
+      if (selectId === '#contratto_servizio_id' && ore.illimitate) {
546
+        $info
547
+          .removeClass('d-none border-info border-warning border-danger')
548
+          .addClass('border-info')
549
+          .html(
550
+            '<div class="fw-semibold mb-1">Contratto</div>' +
551
+            '<div class="text-muted">Ore illimitate, comprese nel canone. Il tempo dei ticket non si fattura.</div>'
552
+          );
553
+        return;
554
+      }
544 555
       if (selectId === '#contratto_servizio_id' && totali <= 0) {
545 556
         $info
546 557
           .removeClass('d-none border-info border-warning border-danger')

+ 6
- 2
resources/views/segnalazione/admin/show.blade.php Ver arquivo

@@ -669,7 +669,9 @@ use App\Models\Config;
669 669
             <div class="fw-semibold mb-1">
670 670
               <span class="badge bg-label-success"><i class="bx bx-check me-1"></i>{{ $csOre->label }}</span>
671 671
             </div>
672
-            @if($csOre->ore_totali > 0)
672
+            @if($csOre->haOreIllimitate())
673
+            <div class="small mb-0 text-muted">Ore illimitate, comprese nel canone</div>
674
+            @elseif($csOre->ore_totali > 0)
673 675
             <div class="small mb-0">
674 676
               <span class="text-muted">Ore rimanenti{{ $csOre->periodo_riferimento_label ? ' ('.$csOre->periodo_riferimento_label.')' : '' }}:</span>
675 677
               <strong class="{{ $csRimanentiNegativi ? 'text-danger' : '' }}">
@@ -690,7 +692,9 @@ use App\Models\Config;
690 692
             <div class="fw-semibold mb-1">
691 693
               <span class="badge bg-label-success"><i class="bx bx-check me-1"></i>{{ $csOre->label }}</span>
692 694
             </div>
693
-            @if($csOre->ore_totali > 0)
695
+            @if($csOre->haOreIllimitate())
696
+            <div class="small mb-0 text-muted">Ore illimitate, comprese nel canone</div>
697
+            @elseif($csOre->ore_totali > 0)
694 698
             <div class="small mb-0">
695 699
               <span class="text-muted">Ore rimanenti{{ $csOre->periodo_riferimento_label ? ' ('.$csOre->periodo_riferimento_label.')' : '' }}:</span>
696 700
               <strong class="{{ $csRimanentiNegativi ? 'text-danger' : '' }}">

Carregando…
Cancelar
Salvar