Roberto Santini hace 1 semana
padre
commit
81dc0452aa

+ 1
- 1
app/DataTables/ContrattoServizioDataTable.php Ver fichero

@@ -27,7 +27,7 @@ class ContrattoServizioDataTable extends DataTable
27 27
     return (new EloquentDataTable($query))
28 28
       ->addColumn('action', fn ($e) => view('contratto_servizio.menu', ['entity' => $e, 'mode' => 'embed']))
29 29
       ->addColumn('template_label', fn ($e) => $e->contrattoTemplate ? $e->contrattoTemplate->nome : '—')
30
-      ->addColumn('costo_label', fn ($e) => number_format((float) $e->totale_righe, 2, ',', '.').' €')
30
+      ->addColumn('costo_label', fn ($e) => number_format((float) $e->canone, 2, ',', '.').' €')
31 31
       ->addColumn('righe_count', fn ($e) => (int) ($e->righe_count ?? $e->righe->filter(fn ($r) => $r->isValidaOggi())->count()))
32 32
       ->addColumn('periodo_label', fn ($e) => Prodotto::PERIODI[$e->periodo]
33 33
         ?? (\App\Models\ContrattoTemplate::PERIODI[$e->periodo] ?? ($e->periodo ?: '—')))

+ 1
- 1
app/DataTables/ContrattoServizioIndexDataTable.php Ver fichero

@@ -26,7 +26,7 @@ class ContrattoServizioIndexDataTable extends DataTable
26 26
         ? $e->user->full_name.($e->user->is_azienda ? ' (azienda)' : '')
27 27
         : '—')
28 28
       ->addColumn('template_label', fn ($e) => $e->contrattoTemplate ? $e->contrattoTemplate->nome : '—')
29
-      ->addColumn('costo_label', fn ($e) => number_format((float) $e->totale_righe, 2, ',', '.').' €')
29
+      ->addColumn('costo_label', fn ($e) => number_format((float) $e->canone, 2, ',', '.').' €')
30 30
       ->addColumn('periodo_label', fn ($e) => $e->periodo_label)
31 31
       ->addColumn('date_label', function ($e) {
32 32
         $i = $e->data_inizio ? \Carbon\Carbon::parse($e->data_inizio)->format('d/m/Y') : '—';

+ 6
- 2
app/Http/Controllers/ContrattoServizioController.php Ver fichero

@@ -169,7 +169,9 @@ class ContrattoServizioController extends Controller implements HasMiddleware
169 169
       $this->syncRighe($contratto, $request->input('righe', []));
170 170
       $contratto->unsetRelation('righe');
171 171
       $contratto->load('righe.prodotto');
172
-      $contratto->costo = $contratto->totale_righe;
172
+      if ($contratto->haCanoneDaRighe()) {
173
+        $contratto->costo = $contratto->totale_righe;
174
+      }
173 175
       $contratto->save();
174 176
     });
175 177
 
@@ -353,7 +355,9 @@ class ContrattoServizioController extends Controller implements HasMiddleware
353 355
         'prodotto_id' => $prodottoId,
354 356
         'quantita' => $row['quantita'] ?? 1,
355 357
         'unita_misura' => $row['unita_misura'] ?? ($prodotto->unita_misura ?? null),
356
-        'prezzo_unitario' => $row['prezzo_unitario'] ?? ($prodotto->prezzo ?? null),
358
+        'prezzo_unitario' => ($row['prezzo_unitario'] === '' || ! array_key_exists('prezzo_unitario', $row))
359
+          ? null
360
+          : $row['prezzo_unitario'],
357 361
         'sconto_tipo' => $scontoTipo,
358 362
         'sconto_valore' => $scontoValore,
359 363
         'periodo' => $row['periodo'] ?? ($prodotto->periodo ?? null),

+ 3
- 1
app/Http/Controllers/ContrattoTemplateController.php Ver fichero

@@ -218,7 +218,9 @@ class ContrattoTemplateController extends Controller implements HasMiddleware
218 218
         'prodotto_id' => $prodottoId,
219 219
         'quantita' => $row['quantita'] ?? 1,
220 220
         'unita_misura' => $row['unita_misura'] ?? ($prodotto->unita_misura ?? null),
221
-        'prezzo_unitario' => $row['prezzo_unitario'] ?? ($prodotto->prezzo ?? null),
221
+        'prezzo_unitario' => ($row['prezzo_unitario'] === '' || ! array_key_exists('prezzo_unitario', $row))
222
+          ? null
223
+          : $row['prezzo_unitario'],
222 224
         'periodo' => $row['periodo'] ?? ($prodotto->periodo ?? null),
223 225
         'note' => $row['note'] ?? null,
224 226
         'ordine' => $ordine++,

+ 16
- 0
app/Models/ContrattoServizio.php Ver fichero

@@ -102,6 +102,22 @@ class ContrattoServizio extends \App\Models\AbstractModels\AbstractContrattoServ
102 102
         return (float) $this->righe->sum(fn (ContrattoServizioRiga $r) => $r->importo ?? 0);
103 103
     }
104 104
 
105
+    /**
106
+     * Canone commerciale: somma delle righe a prezzo, altrimenti il costo indicato in testata
107
+     * (es. 1200 €/anno con ore comprese a prezzo vuoto).
108
+     */
109
+    public function getCanoneAttribute(): float
110
+    {
111
+        $righe = (float) $this->totale_righe;
112
+
113
+        return $righe > 0 ? $righe : (float) ($this->costo ?? 0);
114
+    }
115
+
116
+    public function haCanoneDaRighe(): bool
117
+    {
118
+        return $this->righe->contains(fn (ContrattoServizioRiga $riga) => $riga->importo_ciclo > 0);
119
+    }
120
+
105 121
     /**
106 122
      * Durata in mesi usata per annualizzare le voci ricorrenti.
107 123
      */

+ 44
- 0
app/Models/ContrattoServizioRiga.php Ver fichero

@@ -153,6 +153,50 @@ class ContrattoServizioRiga extends \App\Models\AbstractModels\AbstractContratto
153 153
         return $this->periodo === 'a_consumo';
154 154
     }
155 155
 
156
+    public function isCompresa(): bool
157
+    {
158
+        return ! $this->isAConsumo()
159
+            && ($this->prezzo_unitario === null || (float) $this->prezzo_unitario == 0.0);
160
+    }
161
+
162
+    public function getPrezzoClienteLabelAttribute(): string
163
+    {
164
+        if ($this->isAConsumo()) {
165
+            $um = $this->unita_misura ?: optional($this->prodotto)->unita_misura;
166
+            $suffix = $um ? '/'.strtolower(Prodotto::UNITA_MISURA[$um] ?? $um) : '';
167
+
168
+            return $this->prezzo_unitario !== null
169
+                ? number_format((float) $this->prezzo_unitario, 2, ',', '.').' €'.$suffix
170
+                : '—';
171
+        }
172
+
173
+        if ($this->isCompresa()) {
174
+            return 'compreso';
175
+        }
176
+
177
+        $label = number_format((float) $this->prezzo_unitario, 2, ',', '.').' €';
178
+        if ($this->sconto_su_prezzo > 0 && $this->prezzo_netto !== null) {
179
+            $label .= ' (netto '.number_format($this->prezzo_netto, 2, ',', '.').' €)';
180
+        }
181
+
182
+        return $label;
183
+    }
184
+
185
+    public function getImportoClienteLabelAttribute(): string
186
+    {
187
+        if ($this->isAConsumo()) {
188
+            return 'fuori canone';
189
+        }
190
+
191
+        if ($this->isCompresa()) {
192
+            return 'compreso';
193
+        }
194
+
195
+        return $this->importo !== null
196
+            ? number_format($this->importo, 2, ',', '.').' €'
197
+            : '—';
198
+    }
199
+
156 200
     public function getMoltiplicatorePeriodoAttribute(): int
157 201
     {
158 202
         $um = $this->unita_misura ?: optional($this->prodotto)->unita_misura;

+ 39
- 0
app/Models/ContrattoTemplateRiga.php Ver fichero

@@ -19,6 +19,45 @@ class ContrattoTemplateRiga extends \App\Models\AbstractModels\AbstractContratto
19 19
         return $this->periodo === 'a_consumo';
20 20
     }
21 21
 
22
+    public function isCompresa(): bool
23
+    {
24
+        return ! $this->isAConsumo()
25
+            && ($this->prezzo_unitario === null || (float) $this->prezzo_unitario == 0.0);
26
+    }
27
+
28
+    public function getPrezzoClienteLabelAttribute(): string
29
+    {
30
+        if ($this->isAConsumo()) {
31
+            $um = $this->unita_misura ?: optional($this->prodotto)->unita_misura;
32
+            $suffix = $um ? '/'.strtolower(Prodotto::UNITA_MISURA[$um] ?? $um) : '';
33
+
34
+            return $this->prezzo_unitario !== null
35
+                ? number_format((float) $this->prezzo_unitario, 2, ',', '.').' €'.$suffix
36
+                : '—';
37
+        }
38
+
39
+        if ($this->isCompresa()) {
40
+            return 'compreso';
41
+        }
42
+
43
+        return number_format((float) $this->prezzo_unitario, 2, ',', '.').' €';
44
+    }
45
+
46
+    public function getImportoClienteLabelAttribute(): string
47
+    {
48
+        if ($this->isAConsumo()) {
49
+            return 'fuori canone';
50
+        }
51
+
52
+        if ($this->isCompresa()) {
53
+            return 'compreso';
54
+        }
55
+
56
+        return $this->importo !== null
57
+            ? number_format($this->importo, 2, ',', '.').' €'
58
+            : '—';
59
+    }
60
+
22 61
     public function getImportoAttribute(): ?float
23 62
     {
24 63
         if ($this->isAConsumo()) {

+ 4
- 2
app/Services/FatturazioneGeneraService.php Ver fichero

@@ -649,7 +649,7 @@ class FatturazioneGeneraService
649 649
                 }
650 650
             }
651 651
 
652
-            if ($contratto->righe->isEmpty() && $contratto->costo !== null && (float) $contratto->costo > 0) {
652
+            if (! $contratto->haCanoneDaRighe() && $contratto->costo !== null && (float) $contratto->costo > 0) {
653 653
                 $periodo = $contratto->periodo ?: 'una_tantum';
654 654
                 $fake = new ContrattoServizioRiga([
655 655
                     'periodo' => $periodo,
@@ -673,7 +673,9 @@ class FatturazioneGeneraService
673 673
             if ($contratto->data_fine) {
674 674
                 $fine = Carbon::parse($contratto->data_fine)->startOfDay();
675 675
                 if ($fine->gte($from->copy()->startOfDay()) && $fine->lte($to->copy()->startOfDay())) {
676
-                    $suggested = round((float) $contratto->righe->sum(fn (ContrattoServizioRiga $r) => $r->isAConsumo() ? 0 : $r->importo_ciclo), 2);
676
+                    $suggested = $contratto->haCanoneDaRighe()
677
+                        ? round((float) $contratto->righe->sum(fn (ContrattoServizioRiga $r) => $r->isAConsumo() ? 0 : $r->importo_ciclo), 2)
678
+                        : (float) $contratto->costo;
677 679
                     $clienti[$key]['rinnovi'][] = [
678 680
                         'tipo' => 'scadenza',
679 681
                         'contratto_id' => $contratto->id,

+ 25
- 10
resources/views/contratto_servizio/form.blade.php Ver fichero

@@ -183,8 +183,8 @@
183 183
         <div class="col-md-2">
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
-            value="{{ old('costo', $contratto->totale_righe ?: $contratto->costo) }}">
187
-          <div class="form-text">Calcolato dai servizi (qty × prezzo netto × durata)</div>
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>
188 188
         </div>
189 189
         <div class="col-md-3">
190 190
           <label class="form-label">Periodo</label>
@@ -239,10 +239,8 @@
239 239
           <strong>Periodo</strong> = ogni quanto vale quella quantità/prezzo (<em>mensile</em>, <em>annuale</em>, <em>una tantum</em>).
240 240
           Esempio: 20 ore <em>mensili</em> non è lo stesso di 20 ore <em>annuali</em>.
241 241
           Se l’unità è già un tempo (mese, anno) o un forfait, il periodo non compare.<br>
242
-          <strong>A consumo</strong> = solo tariffa extra (es. 35 €/ora): non è un monte ore e non entra nel canone. Le ore extra sulle segnalazioni si fatturano a parte.<br>
243
-          <strong>Canone annuale + ore mensili:</strong> se a gennaio fai <em>una</em> fattura (es. 1320 € Zabbix + 12 h) e l’assistenza è 1 h/mese, non mettere prezzo sull’ora inclusa.
244
-          Il forfait (1320 €, U.M. anno/forfait) è ciò che viene fatturato a gennaio; l’ora <em>mensile a 0 €</em> è solo il monte ore (si azzera ogni mese); l’extra è a consumo.
245
-          Se metti 35 € sull’ora mensile, il sistema proverebbe a fatturarla ogni mese.<br>
242
+          <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
+          <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>
246 244
           <strong>Validità</strong> (Dal → Al, sotto il prodotto) = da quando vale questa riga. Vuoto = tutta la durata del contratto.
247 245
           Per un aumento tariffe: chiudi la riga vecchia con <em>Al</em>, aggiungine una nuova con <em>Dal</em> e il prezzo aggiornato.
248 246
         </p>
@@ -310,7 +308,13 @@
310 308
               </div>
311 309
             </td>
312 310
             <td class="text-end text-nowrap riga-netto">
313
-              {{ $riga->prezzo_netto !== null ? number_format($riga->prezzo_netto, 2, ',', '.').' €' : '—' }}
311
+              @if($riga->isCompresa())
312
+              compreso
313
+              @elseif($riga->prezzo_netto !== null)
314
+              {{ number_format($riga->prezzo_netto, 2, ',', '.') }} €
315
+              @else
316
+              —
317
+              @endif
314 318
             </td>
315 319
             <td class="field-periodo">
316 320
               <select name="righe[{{ $i }}][periodo]" class="form-select form-select-sm riga-periodo">
@@ -592,6 +596,7 @@
592 596
   function aggiornaCostoContratto() {
593 597
     const costoInput = document.getElementById('contratto_costo');
594 598
     let totale = 0;
599
+    let haPrezzo = false;
595 600
     righeBody.querySelectorAll('tr.riga-row').forEach(function (tr) {
596 601
       const prodotto = tr.querySelector('.riga-prodotto');
597 602
       const nettoCell = tr.querySelector('.riga-netto');
@@ -599,12 +604,22 @@
599 604
         if (nettoCell) nettoCell.textContent = '—';
600 605
         return;
601 606
       }
607
+      const periodo = ((tr.querySelector('.riga-periodo') || {}).value) || '';
602 608
       const prezzoNetto = rigaPrezzoNetto(tr);
603 609
       const netto = rigaImporto(tr);
604
-      if (nettoCell) nettoCell.textContent = prezzoNetto === null ? '—' : formatEuro(prezzoNetto);
605
-      if (netto !== null) totale += netto;
610
+      if (nettoCell) {
611
+        if (periodo !== 'a_consumo' && (prezzoNetto === null || prezzoNetto === 0)) {
612
+          nettoCell.textContent = 'compreso';
613
+        } else {
614
+          nettoCell.textContent = prezzoNetto === null ? '—' : formatEuro(prezzoNetto);
615
+        }
616
+      }
617
+      if (netto !== null && netto > 0) {
618
+        totale += netto;
619
+        haPrezzo = true;
620
+      }
606 621
     });
607
-    if (costoInput) {
622
+    if (costoInput && haPrezzo) {
608 623
       costoInput.value = Math.round(totale * 100) / 100;
609 624
     }
610 625
   }

+ 4
- 9
resources/views/contratto_servizio/pdf.blade.php Ver fichero

@@ -9,7 +9,7 @@
9 9
 
10 10
   @php
11 11
     $documentoLabel = 'Contratto '.$contratto->label;
12
-    $imponibile = (float) $contratto->totale_righe;
12
+    $imponibile = (float) $contratto->canone;
13 13
     $aliquotaIva = 22;
14 14
     $importoIva = round($imponibile * $aliquotaIva / 100, 2);
15 15
     $totaleIvato = round($imponibile + $importoIva, 2);
@@ -39,7 +39,7 @@
39 39
         <br>
40 40
         <span class="muted">Periodo: {{ $contratto->periodo_label }}</span>
41 41
         <br>
42
-        <span class="muted">Costo: {{ number_format($contratto->totale_righe, 2, ',', '.') }} €</span>
42
+        <span class="muted">Costo: {{ number_format($contratto->canone, 2, ',', '.') }} €</span>
43 43
       </td>
44 44
     </tr>
45 45
   </table>
@@ -78,15 +78,10 @@
78 78
           @endif
79 79
         </td>
80 80
         <td>{{ \App\Models\Prodotto::UNITA_MISURA[$riga->unita_misura] ?? ($riga->unita_misura ?: '—') }}</td>
81
-        <td>
82
-          {{ $riga->prezzo_unitario !== null ? number_format((float)$riga->prezzo_unitario, 2, ',', '.').' €' : '—' }}@if($riga->isAConsumo() && $riga->unita_misura)/{{ strtolower(\App\Models\Prodotto::UNITA_MISURA[$riga->unita_misura] ?? $riga->unita_misura) }}@endif
83
-          {{-- @if($riga->sconto_su_prezzo > 0 && $riga->prezzo_netto !== null)
84
-          <br><span class="muted">netto {{ number_format($riga->prezzo_netto, 2, ',', '.') }} €</span>
85
-          @endif --}}
86
-        </td>
81
+        <td>{{ $riga->prezzo_cliente_label }}</td>
87 82
         <td>{{ $riga->sconto_label }}</td>
88 83
         <td>{{ $riga->periodo_label }}</td>
89
-        <td class="text-end">{{ $riga->isAConsumo() ? 'fuori canone' : ($riga->importo !== null ? number_format($riga->importo, 2, ',', '.').' €' : '—') }}</td>
84
+        <td class="text-end">{{ $riga->importo_cliente_label }}</td>
90 85
       </tr>
91 86
       @empty
92 87
       <tr><td colspan="7">Nessun servizio</td></tr>

+ 4
- 9
resources/views/contratto_servizio/show.blade.php Ver fichero

@@ -86,7 +86,7 @@
86 86
         <hr>
87 87
         <p class="mb-1"><strong>Periodo:</strong> {{ $contratto->periodo_label }}</p>
88 88
         <p class="mb-1"><strong>Costo:</strong>
89
-          {{ number_format($contratto->totale_righe, 2, ',', '.') }} €
89
+          {{ number_format($contratto->canone, 2, ',', '.') }} €
90 90
         </p>
91 91
         <p class="mb-1"><strong>Validità:</strong>
92 92
           {{ $contratto->data_inizio ? \Carbon\Carbon::parse($contratto->data_inizio)->format('d/m/Y') : '—' }}
@@ -104,7 +104,7 @@
104 104
     <div class="card mb-4">
105 105
       <div class="card-header d-flex justify-content-between align-items-center">
106 106
         <h5 class="mb-0">Servizi / prodotti</h5>
107
-        <span class="text-muted">Totale righe: {{ number_format($contratto->totale_righe, 2, ',', '.') }} €</span>
107
+        <span class="text-muted">Totale: {{ number_format($contratto->canone, 2, ',', '.') }} €</span>
108 108
       </div>
109 109
       <div class="table-responsive">
110 110
         <table class="table mb-0">
@@ -138,15 +138,10 @@
138 138
                 @endif
139 139
               </td>
140 140
               <td>{{ \App\Models\Prodotto::UNITA_MISURA[$riga->unita_misura] ?? ($riga->unita_misura ?: '—') }}</td>
141
-              <td>
142
-                {{ $riga->prezzo_unitario !== null ? number_format((float)$riga->prezzo_unitario, 2, ',', '.').' €' : '—' }}@if($riga->isAConsumo() && $riga->unita_misura)/{{ strtolower(\App\Models\Prodotto::UNITA_MISURA[$riga->unita_misura] ?? $riga->unita_misura) }}@endif
143
-                @if($riga->sconto_su_prezzo > 0 && $riga->prezzo_netto !== null)
144
-                <br><small class="text-muted">netto {{ number_format($riga->prezzo_netto, 2, ',', '.') }} €</small>
145
-                @endif
146
-              </td>
141
+              <td>{{ $riga->prezzo_cliente_label }}</td>
147 142
               <td>{{ $riga->sconto_label }}</td>
148 143
               <td>{{ $riga->periodo_label }}</td>
149
-              <td class="text-end">{{ $riga->isAConsumo() ? 'fuori canone' : ($riga->importo !== null ? number_format($riga->importo, 2, ',', '.').' €' : '—') }}</td>
144
+              <td class="text-end">{{ $riga->importo_cliente_label }}</td>
150 145
             </tr>
151 146
             @empty
152 147
             <tr><td colspan="7" class="text-muted">Nessun servizio</td></tr>

+ 2
- 2
resources/views/contratto_template/show.blade.php Ver fichero

@@ -88,9 +88,9 @@
88 88
           <td>{{ optional($riga->prodotto)->nome ?: '—' }}</td>
89 89
           <td>{{ $riga->isAConsumo() ? '—' : $riga->quantita }}</td>
90 90
           <td>{{ \App\Models\Prodotto::UNITA_MISURA[$riga->unita_misura] ?? ($riga->unita_misura ?: '—') }}</td>
91
-          <td>{{ $riga->prezzo_unitario !== null ? number_format((float)$riga->prezzo_unitario, 2, ',', '.').' €' : '—' }}@if($riga->isAConsumo() && $riga->unita_misura)/{{ strtolower(\App\Models\Prodotto::UNITA_MISURA[$riga->unita_misura] ?? $riga->unita_misura) }}@endif</td>
91
+          <td>{{ $riga->prezzo_cliente_label }}</td>
92 92
           <td>{{ $riga->periodo_label }}</td>
93
-          <td>{{ $riga->isAConsumo() ? 'fuori canone' : ($riga->importo !== null ? number_format($riga->importo, 2, ',', '.').' €' : '—') }}</td>
93
+          <td>{{ $riga->importo_cliente_label }}</td>
94 94
           <td class="small text-muted">{{ $riga->note }}</td>
95 95
         </tr>
96 96
         @endforeach

Loading…
Cancelar
Guardar