Roberto Santini 3 недель назад
Родитель
Сommit
bac6e66c09

+ 1
- 1
app/Http/Controllers/AziendaFicController.php Просмотреть файл

36
             'confronto' => null,
36
             'confronto' => null,
37
             'message' => $fic->configured()
37
             'message' => $fic->configured()
38
                 ? null
38
                 ? null
39
-                : 'Inserisci Company ID e access token in Configurazione → Fatture in Cloud. Il token deve avere gli scope entity.clients:r e entity.clients:a.',
39
+                : 'Inserisci Company ID e access token in Configurazione → Fatture in Cloud. Il token deve avere gli scope entity.clients:r, entity.clients:a, settings:r e issued_documents.invoices:a.',
40
         ];
40
         ];
41
 
41
 
42
         if ($fic->configured() && $azienda->ficCollegamento) {
42
         if ($fic->configured() && $azienda->ficCollegamento) {

+ 13
- 1
app/Http/Controllers/FatturazioneController.php Просмотреть файл

6
 use App\Models\Config;
6
 use App\Models\Config;
7
 use App\Models\Fattura;
7
 use App\Models\Fattura;
8
 use App\Models\FatturaRiga;
8
 use App\Models\FatturaRiga;
9
+use App\Services\FatturaFicService;
9
 use App\Services\FatturazioneGeneraService;
10
 use App\Services\FatturazioneGeneraService;
10
 use Carbon\Carbon;
11
 use Carbon\Carbon;
11
 use Illuminate\Http\Request;
12
 use Illuminate\Http\Request;
34
       new Middleware('permission:view-fatturazione', only: ['admin_index', 'admin_genera', 'admin_show']),
35
       new Middleware('permission:view-fatturazione', only: ['admin_index', 'admin_genera', 'admin_show']),
35
       new Middleware('permission:genera-fatturazione', only: ['admin_genera']),
36
       new Middleware('permission:genera-fatturazione', only: ['admin_genera']),
36
       new Middleware('permission:create-fattura', only: ['admin_crea']),
37
       new Middleware('permission:create-fattura', only: ['admin_crea']),
37
-      new Middleware('permission:edit-fattura', only: ['admin_update', 'admin_emetti', 'admin_annulla']),
38
+      new Middleware('permission:edit-fattura', only: ['admin_update', 'admin_emetti', 'admin_annulla', 'admin_invia_fic']),
38
       new Middleware('permission:delete-fattura', only: ['admin_destroy']),
39
       new Middleware('permission:delete-fattura', only: ['admin_destroy']),
39
     ];
40
     ];
40
   }
41
   }
239
     return redirect()->back()->with('success', 'Fattura emessa.');
240
     return redirect()->back()->with('success', 'Fattura emessa.');
240
   }
241
   }
241
 
242
 
243
+  public function admin_invia_fic($id, FatturaFicService $fic)
244
+  {
245
+    try {
246
+      $message = $fic->invia(Fattura::findOrFail($id));
247
+    } catch (RuntimeException $e) {
248
+      return redirect()->back()->with('error', $e->getMessage());
249
+    }
250
+
251
+    return redirect()->back()->with('success', $message);
252
+  }
253
+
242
   public function admin_annulla($id)
254
   public function admin_annulla($id)
243
   {
255
   {
244
     $fattura = Fattura::findOrFail($id);
256
     $fattura = Fattura::findOrFail($id);

+ 12
- 0
app/Models/Fattura.php Просмотреть файл

30
     public function getNumeroDocumentoAttribute(): string
30
     public function getNumeroDocumentoAttribute(): string
31
     {
31
     {
32
         $n = $this->numero ?: $this->id;
32
         $n = $this->numero ?: $this->id;
33
+        $numeration = trim((string) data_get($this->meta, 'fic.numeration'));
34
+        if ($this->ficDocumentId() && $numeration !== '') {
35
+            return $n.(str_starts_with($numeration, '/') ? '' : '/').$numeration;
36
+        }
37
+
33
         $year = $this->data
38
         $year = $this->data
34
             ? Carbon::parse($this->data)->format('y')
39
             ? Carbon::parse($this->data)->format('y')
35
             : now()->format('y');
40
             : now()->format('y');
57
         return $this->stato !== self::STATO_ANNULLATA;
62
         return $this->stato !== self::STATO_ANNULLATA;
58
     }
63
     }
59
 
64
 
65
+    public function ficDocumentId(): ?int
66
+    {
67
+        $id = data_get($this->meta, 'fic.document_id');
68
+
69
+        return $id ? (int) $id : null;
70
+    }
71
+
60
     public function scopeNonAnnullate($query)
72
     public function scopeNonAnnullate($query)
61
     {
73
     {
62
         return $query->where('stato', '!=', self::STATO_ANNULLATA);
74
         return $query->where('stato', '!=', self::STATO_ANNULLATA);

+ 191
- 0
app/Services/FatturaFicService.php Просмотреть файл

1
+<?php
2
+
3
+namespace App\Services;
4
+
5
+use App\Models\Config;
6
+use App\Models\Fattura;
7
+use App\Models\FatturaRiga;
8
+use App\Models\FicCollegamento;
9
+use App\Models\Prodotto;
10
+use Carbon\Carbon;
11
+use RuntimeException;
12
+
13
+class FatturaFicService
14
+{
15
+    public function __construct(protected FattureInCloudClient $fic) {}
16
+
17
+    public function invia(Fattura $fattura): string
18
+    {
19
+        $fattura->loadMissing(['righe', 'user']);
20
+        if ($fattura->isAnnullata()) {
21
+            throw new RuntimeException('Una fattura annullata non si invia.');
22
+        }
23
+        if ($fattura->ficDocumentId()) {
24
+            throw new RuntimeException('Questa fattura è già su Fatture in Cloud.');
25
+        }
26
+        if ($fattura->righe->isEmpty()) {
27
+            throw new RuntimeException('La fattura non ha righe.');
28
+        }
29
+
30
+        $collegamento = $this->collegamento($fattura);
31
+        if (! $collegamento) {
32
+            throw new RuntimeException('Collega il cliente a Fatture in Cloud dalla scheda azienda, poi riprova.');
33
+        }
34
+
35
+        try {
36
+            $client = $this->fic->getClient((int) $collegamento->fic_client_id);
37
+            $vatId = $this->vatId((float) ($fattura->iva_percentuale ?? Config::ivaPercentualeDefault()));
38
+            $document = $this->fic->createIssuedDocument($this->payload($fattura, $client, $vatId));
39
+            if (! isset($document['number']) || $document['number'] === '' || $document['number'] === null) {
40
+                $document = $this->fic->getIssuedDocument((int) $document['id']);
41
+            }
42
+        } catch (RuntimeException $e) {
43
+            throw new RuntimeException($this->conScope($e->getMessage()));
44
+        }
45
+
46
+        $numeroFic = (int) $document['number'];
47
+        if ($numeroFic < 1) {
48
+            throw new RuntimeException('Fatture in Cloud ha creato il documento ma non ha assegnato un numero.');
49
+        }
50
+
51
+        $meta = is_array($fattura->meta) ? $fattura->meta : [];
52
+        $meta['fic'] = [
53
+            'document_id' => (int) $document['id'],
54
+            'number' => $numeroFic,
55
+            'numeration' => $document['numeration'] ?? null,
56
+            'date' => isset($document['date']) ? (string) $document['date'] : null,
57
+            'sent_at' => now()->toIso8601String(),
58
+        ];
59
+        $fattura->meta = $meta;
60
+        $fattura->numero = $numeroFic;
61
+        if (! empty($document['date'])) {
62
+            $fattura->data = Carbon::parse($document['date'])->toDateString();
63
+        }
64
+        if ($fattura->isBozza()) {
65
+            $fattura->stato = Fattura::STATO_EMESSA;
66
+            if (! $fattura->data) {
67
+                $fattura->data = now()->toDateString();
68
+            }
69
+        }
70
+        $fattura->save();
71
+
72
+        return 'Fattura '.$fattura->numero_documento.' allineata a Fatture in Cloud. L\'invio allo SDI lo fai da lì.';
73
+    }
74
+
75
+    protected function collegamento(Fattura $fattura): ?FicCollegamento
76
+    {
77
+        $ids = array_values(array_filter([
78
+            (int) $fattura->user_id,
79
+            (int) ($fattura->user->azienda_id ?? 0),
80
+        ]));
81
+
82
+        return FicCollegamento::query()->whereIn('user_id', $ids)->first();
83
+    }
84
+
85
+    /**
86
+     * @param  array<string, mixed>  $client
87
+     * @return array<string, mixed>
88
+     */
89
+    protected function payload(Fattura $fattura, array $client, int $vatId): array
90
+    {
91
+        $data = $fattura->data ? Carbon::parse($fattura->data)->toDateString() : now()->toDateString();
92
+        $entity = ['id' => (int) $client['id']];
93
+        foreach ([
94
+            'name',
95
+            'vat_number',
96
+            'tax_code',
97
+            'address_street',
98
+            'address_postal_code',
99
+            'address_city',
100
+            'address_province',
101
+            'country',
102
+            'email',
103
+        ] as $field) {
104
+            $value = trim((string) ($client[$field] ?? ''));
105
+            if ($value !== '') {
106
+                $entity[$field] = $value;
107
+            }
108
+        }
109
+        if (empty($entity['name'])) {
110
+            $entity['name'] = $fattura->ragione_sociale ?: 'Cliente';
111
+        }
112
+
113
+        $items = [];
114
+        foreach ($fattura->righe as $riga) {
115
+            $items[] = $this->item($riga, $vatId);
116
+        }
117
+
118
+        $payload = [
119
+            'type' => 'invoice',
120
+            'entity' => $entity,
121
+            'date' => $data,
122
+            'items_list' => $items,
123
+            'e_invoice' => false,
124
+        ];
125
+
126
+        if ($fattura->periodo_da && $fattura->periodo_a) {
127
+            $payload['visible_subject'] = 'Periodo '
128
+                .Carbon::parse($fattura->periodo_da)->format('d/m/Y')
129
+                .' – '
130
+                .Carbon::parse($fattura->periodo_a)->format('d/m/Y');
131
+        }
132
+
133
+        return $payload;
134
+    }
135
+
136
+    /**
137
+     * @return array<string, mixed>
138
+     */
139
+    protected function item(FatturaRiga $riga, int $vatId): array
140
+    {
141
+        $qty = (float) ($riga->quantita ?? 1);
142
+        if ($qty <= 0) {
143
+            $qty = 1;
144
+        }
145
+        $name = trim((string) $riga->descrizione);
146
+        if ($name === '') {
147
+            $name = $riga->tipo_label;
148
+        }
149
+        if (mb_strlen($name) > 255) {
150
+            $name = mb_substr($name, 0, 252).'…';
151
+        }
152
+
153
+        $item = [
154
+            'name' => $name,
155
+            'qty' => $qty,
156
+            'net_price' => round((float) ($riga->importo ?? 0) / $qty, 5),
157
+            'vat' => ['id' => $vatId],
158
+        ];
159
+        $measure = Prodotto::UNITA_MISURA[$riga->unita_misura] ?? $riga->unita_misura;
160
+        if (filled($measure)) {
161
+            $item['measure'] = mb_substr((string) $measure, 0, 20);
162
+        }
163
+
164
+        return $item;
165
+    }
166
+
167
+    protected function vatId(float $percent): int
168
+    {
169
+        foreach ($this->fic->vatTypes() as $type) {
170
+            if (! is_array($type) || ! empty($type['is_disabled'])) {
171
+                continue;
172
+            }
173
+            if (abs((float) ($type['value'] ?? -1) - $percent) < 0.001 && isset($type['id'])) {
174
+                return (int) $type['id'];
175
+            }
176
+        }
177
+
178
+        $label = rtrim(rtrim(number_format($percent, 2, ',', ''), '0'), ',');
179
+
180
+        throw new RuntimeException('Su Fatture in Cloud non c\'è un\'aliquota IVA del '.$label.'%.');
181
+    }
182
+
183
+    protected function conScope(string $message): string
184
+    {
185
+        if (preg_match('/403|permission|permess|scope/i', $message)) {
186
+            return $message.' Ricrea il token con anche gli scope settings:r e issued_documents.invoices:a.';
187
+        }
188
+
189
+        return $message;
190
+    }
191
+}

+ 1
- 1
app/Services/FatturazioneGeneraService.php Просмотреть файл

498
             'importo_tempo' => round(($minuti / 60) * $tariffaDefault, 2),
498
             'importo_tempo' => round(($minuti / 60) * $tariffaDefault, 2),
499
             'importo_spesa' => $fatturaSpesa,
499
             'importo_spesa' => $fatturaSpesa,
500
             'tariffa_mancante' => $mancante,
500
             'tariffa_mancante' => $mancante,
501
-            'note' => 'Senza contratto né ordine',
501
+            'note' => null,
502
         ], $annotazione, $segnalazione);
502
         ], $annotazione, $segnalazione);
503
     }
503
     }
504
 
504
 

+ 45
- 0
app/Services/FattureInCloudClient.php Просмотреть файл

379
         return $fromEnv !== '' ? $fromEnv : null;
379
         return $fromEnv !== '' ? $fromEnv : null;
380
     }
380
     }
381
 
381
 
382
+    /**
383
+     * @return array<int, array<string, mixed>>
384
+     */
385
+    public function vatTypes(): array
386
+    {
387
+        $json = $this->decode($this->http()->get($this->url('/info/vat_types')));
388
+
389
+        return is_array($json['data'] ?? null) ? $json['data'] : [];
390
+    }
391
+
392
+    /**
393
+     * @param  array<string, mixed>  $data
394
+     * @return array<string, mixed>
395
+     */
396
+    public function createIssuedDocument(array $data): array
397
+    {
398
+        $json = $this->decode($this->http()->post($this->url('/issued_documents'), [
399
+            'data' => $data,
400
+        ]));
401
+
402
+        return $this->documentOrFail($json['data'] ?? null);
403
+    }
404
+
405
+    /**
406
+     * @return array<string, mixed>
407
+     */
408
+    public function getIssuedDocument(int $id): array
409
+    {
410
+        $json = $this->decode($this->http()->get($this->url('/issued_documents/'.$id)));
411
+
412
+        return $this->documentOrFail($json['data'] ?? null);
413
+    }
414
+
415
+    /**
416
+     * @return array<string, mixed>
417
+     */
418
+    private function documentOrFail(mixed $document): array
419
+    {
420
+        if (! is_array($document) || empty($document['id'])) {
421
+            throw new RuntimeException('Fatture in Cloud non ha restituito la fattura creata.');
422
+        }
423
+
424
+        return $document;
425
+    }
426
+
382
     private function url(string $path): string
427
     private function url(string $path): string
383
     {
428
     {
384
         return '/c/'.$this->companyId().$path;
429
         return '/c/'.$this->companyId().$path;

+ 1
- 1
resources/views/azienda/fic-scripts.blade.php Просмотреть файл

159
         if (!data.configured) {
159
         if (!data.configured) {
160
           const el = document.getElementById('ficConfigAlert');
160
           const el = document.getElementById('ficConfigAlert');
161
           el.classList.remove('d-none');
161
           el.classList.remove('d-none');
162
-          el.innerHTML = 'Inserisci Company ID e access token nella <a href="' + @json(route('admin.config.index')) + '">configurazione</a>, scheda Fatture in Cloud. Il token va creato come da <a href="https://developers.fattureincloud.it/docs/getting-started/" target="_blank" rel="noopener">guida Fatture in Cloud</a> e deve avere gli scope <code>entity.clients:r</code> e <code>entity.clients:a</code>.';
162
+          el.innerHTML = 'Inserisci Company ID e access token nella <a href="' + @json(route('admin.config.index')) + '">configurazione</a>, scheda Fatture in Cloud. Il token va creato come da <a href="https://developers.fattureincloud.it/docs/getting-started/" target="_blank" rel="noopener">guida Fatture in Cloud</a> e deve avere gli scope <code>entity.clients:r</code>, <code>entity.clients:a</code>, <code>settings:r</code> e <code>issued_documents.invoices:a</code>.';
163
           document.getElementById('ficSearchWrap').classList.add('d-none');
163
           document.getElementById('ficSearchWrap').classList.add('d-none');
164
           document.getElementById('ficCreate').classList.add('d-none');
164
           document.getElementById('ficCreate').classList.add('d-none');
165
           return;
165
           return;

+ 1
- 1
resources/views/config/index.blade.php Просмотреть файл

186
               @case('password')
186
               @case('password')
187
               <input type="password" class="form-control" name="value[{{ $config->key }}]" id="{{ $fid }}-control" value="" autocomplete="new-password" placeholder="{{ $config->value ? 'Token già salvato' : '' }}" {{ $disabled }}>
187
               <input type="password" class="form-control" name="value[{{ $config->key }}]" id="{{ $fid }}-control" value="" autocomplete="new-password" placeholder="{{ $config->value ? 'Token già salvato' : '' }}" {{ $disabled }}>
188
               @if($config->key === Config::FIC_ACCESS_TOKEN)
188
               @if($config->key === Config::FIC_ACCESS_TOKEN)
189
-              <div class="form-text">Token manuale di <a href="https://developers.fattureincloud.it/docs/getting-started/" target="_blank" rel="noopener">Fatture in Cloud</a> con scope <code>entity.clients:r</code> e <code>entity.clients:a</code>. Lascia vuoto per non sostituirlo.</div>
189
+              <div class="form-text">Token manuale di <a href="https://developers.fattureincloud.it/docs/getting-started/" target="_blank" rel="noopener">Fatture in Cloud</a> con scope <code>entity.clients:r</code>, <code>entity.clients:a</code>, <code>settings:r</code> e <code>issued_documents.invoices:a</code>. Lascia vuoto per non sostituirlo.</div>
190
               @endif
190
               @endif
191
               @break
191
               @break
192
 
192
 

+ 24
- 28
resources/views/fatturazione/show.blade.php Просмотреть файл

35
     </button>
35
     </button>
36
   </form>
36
   </form>
37
   @endif
37
   @endif
38
+  @if($fattura->ficDocumentId())
39
+  <span class="badge bg-label-success">Su Fatture in Cloud n. {{ data_get($fattura->meta, 'fic.number') }}{{ data_get($fattura->meta, 'fic.numeration') }}</span>
40
+  @elseif(! $fattura->isAnnullata())
41
+  <form method="POST" action="{{ route('admin.fatturazione.fic', $fattura->id) }}" class="d-inline"
42
+    onsubmit="return confirm('Creare questa fattura su Fatture in Cloud?');">
43
+    @csrf
44
+    <button type="submit" class="btn btn-label-info btn-sm">
45
+      <i class="bx bx-cloud-upload me-1"></i>Invia a Fatture in Cloud
46
+    </button>
47
+  </form>
48
+  @endif
38
   @if(! $fattura->isAnnullata())
49
   @if(! $fattura->isAnnullata())
39
   <form method="POST" action="{{ route('admin.fatturazione.annulla', $fattura->id) }}" class="d-inline"
50
   <form method="POST" action="{{ route('admin.fatturazione.annulla', $fattura->id) }}" class="d-inline"
40
     onsubmit="return confirm('Annullare questa fattura? Le voci torneranno disponibili al genera.');">
51
     onsubmit="return confirm('Annullare questa fattura? Le voci torneranno disponibili al genera.');">
202
       <table class="table mb-0" id="righeTable">
213
       <table class="table mb-0" id="righeTable">
203
         <thead>
214
         <thead>
204
           <tr>
215
           <tr>
205
-            <th style="width: 160px;">Tipo</th>
206
             <th>Descrizione</th>
216
             <th>Descrizione</th>
207
             <th style="width: 90px;">Qty</th>
217
             <th style="width: 90px;">Qty</th>
208
             <th style="width: 120px;">U.M.</th>
218
             <th style="width: 120px;">U.M.</th>
217
           <tr class="riga-row">
227
           <tr class="riga-row">
218
             <td>
228
             <td>
219
               <input type="hidden" name="righe[{{ $i }}][id]" value="{{ $riga['id'] ?? '' }}">
229
               <input type="hidden" name="righe[{{ $i }}][id]" value="{{ $riga['id'] ?? '' }}">
220
-              <select name="righe[{{ $i }}][tipo]" class="form-select form-select-sm">
221
-                @foreach(\App\Models\FatturaRiga::TIPI as $val => $label)
222
-                <option value="{{ $val }}" @selected(($riga['tipo'] ?? '') === $val)>{{ $label }}</option>
223
-                @endforeach
224
-              </select>
225
-              <input type="number" min="0" step="1" name="righe[{{ $i }}][minuti]" class="form-control form-control-sm mt-1" value="{{ $riga['minuti'] ?? '' }}" placeholder="Minuti">
226
-            </td>
227
-            <td>
230
+              <input type="hidden" name="righe[{{ $i }}][tipo]" value="{{ $riga['tipo'] ?? \App\Models\FatturaRiga::TIPO_MANUALE }}">
231
+              <input type="hidden" name="righe[{{ $i }}][minuti]" value="{{ $riga['minuti'] ?? '' }}">
228
               <input type="text" name="righe[{{ $i }}][descrizione]" class="form-control form-control-sm" value="{{ $riga['descrizione'] ?? '' }}" placeholder="Descrizione">
232
               <input type="text" name="righe[{{ $i }}][descrizione]" class="form-control form-control-sm" value="{{ $riga['descrizione'] ?? '' }}" placeholder="Descrizione">
229
-              <textarea name="righe[{{ $i }}][nota]" class="form-control form-control-sm mt-1" rows="1" placeholder="Nota">{{ $riga['nota'] ?? '' }}</textarea>
233
+              <textarea name="righe[{{ $i }}][nota]" class="form-control form-control-sm mt-1" rows="1" placeholder="Nota">{{ ($riga['nota'] ?? '') === 'Senza contratto né ordine' ? '' : ($riga['nota'] ?? '') }}</textarea>
230
               @if(!empty($riga['segnalazione_id']))
234
               @if(!empty($riga['segnalazione_id']))
231
               <div class="small mt-1"><a href="{{ route('admin.segnalazione.show', $riga['segnalazione_id']) }}">Apri ticket</a></div>
235
               <div class="small mt-1"><a href="{{ route('admin.segnalazione.show', $riga['segnalazione_id']) }}">Apri ticket</a></div>
232
               @endif
236
               @endif
258
         </tbody>
262
         </tbody>
259
         <tfoot>
263
         <tfoot>
260
           <tr>
264
           <tr>
261
-            <th colspan="6" class="text-end align-middle">Imponibile</th>
265
+            <th colspan="5" class="text-end align-middle">Imponibile</th>
262
             <th>
266
             <th>
263
               <input type="number" step="0.01" name="imponibile" id="imponibile" class="form-control form-control-sm text-end totale-input" value="{{ old('imponibile', $numInput($fattura->imponibile)) }}">
267
               <input type="number" step="0.01" name="imponibile" id="imponibile" class="form-control form-control-sm text-end totale-input" value="{{ old('imponibile', $numInput($fattura->imponibile)) }}">
264
             </th>
268
             </th>
265
             <th></th>
269
             <th></th>
266
           </tr>
270
           </tr>
267
           <tr>
271
           <tr>
268
-            <th colspan="6" class="text-end align-middle">IVA</th>
272
+            <th colspan="5" class="text-end align-middle">IVA</th>
269
             <th>
273
             <th>
270
               <input type="number" step="0.01" name="iva" id="iva" class="form-control form-control-sm text-end totale-input" value="{{ old('iva', $numInput($fattura->iva)) }}">
274
               <input type="number" step="0.01" name="iva" id="iva" class="form-control form-control-sm text-end totale-input" value="{{ old('iva', $numInput($fattura->iva)) }}">
271
             </th>
275
             </th>
272
             <th></th>
276
             <th></th>
273
           </tr>
277
           </tr>
274
           <tr>
278
           <tr>
275
-            <th colspan="6" class="text-end align-middle">Totale</th>
279
+            <th colspan="5" class="text-end align-middle">Totale</th>
276
             <th>
280
             <th>
277
               <input type="number" step="0.01" name="totale" id="totale" class="form-control form-control-sm text-end fw-semibold totale-input" value="{{ old('totale', $numInput($fattura->totale)) }}">
281
               <input type="number" step="0.01" name="totale" id="totale" class="form-control form-control-sm text-end fw-semibold totale-input" value="{{ old('totale', $numInput($fattura->totale)) }}">
278
             </th>
282
             </th>
292
   <tr class="riga-row">
296
   <tr class="riga-row">
293
     <td>
297
     <td>
294
       <input type="hidden" name="righe[__IDX__][id]" value="">
298
       <input type="hidden" name="righe[__IDX__][id]" value="">
295
-      <select name="righe[__IDX__][tipo]" class="form-select form-select-sm">
296
-        @foreach(\App\Models\FatturaRiga::TIPI as $val => $label)
297
-        <option value="{{ $val }}" @selected($val === \App\Models\FatturaRiga::TIPO_MANUALE)>{{ $label }}</option>
298
-        @endforeach
299
-      </select>
300
-      <input type="number" min="0" step="1" name="righe[__IDX__][minuti]" class="form-control form-control-sm mt-1" placeholder="Minuti">
301
-    </td>
302
-    <td>
299
+      <input type="hidden" name="righe[__IDX__][tipo]" value="{{ \App\Models\FatturaRiga::TIPO_MANUALE }}">
300
+      <input type="hidden" name="righe[__IDX__][minuti]" value="">
303
       <input type="text" name="righe[__IDX__][descrizione]" class="form-control form-control-sm" placeholder="Descrizione">
301
       <input type="text" name="righe[__IDX__][descrizione]" class="form-control form-control-sm" placeholder="Descrizione">
304
       <textarea name="righe[__IDX__][nota]" class="form-control form-control-sm mt-1" rows="1" placeholder="Nota"></textarea>
302
       <textarea name="righe[__IDX__][nota]" class="form-control form-control-sm mt-1" rows="1" placeholder="Nota"></textarea>
305
     </td>
303
     </td>
358
         <table class="table mb-0">
356
         <table class="table mb-0">
359
           <thead>
357
           <thead>
360
             <tr>
358
             <tr>
361
-              <th>Tipo</th>
362
               <th>Descrizione</th>
359
               <th>Descrizione</th>
363
               <th>Qty</th>
360
               <th>Qty</th>
364
               <th>Prezzo</th>
361
               <th>Prezzo</th>
368
           <tbody>
365
           <tbody>
369
             @forelse($fattura->righe as $riga)
366
             @forelse($fattura->righe as $riga)
370
             <tr>
367
             <tr>
371
-              <td><span class="badge bg-label-secondary">{{ $riga->tipo_label }}</span></td>
372
               <td>
368
               <td>
373
                 {{ $riga->descrizione }}
369
                 {{ $riga->descrizione }}
374
                 @if($riga->segnalazione_id)
370
                 @if($riga->segnalazione_id)
380
                 @if($riga->ordine_lavoro_id)
376
                 @if($riga->ordine_lavoro_id)
381
                 <div class="small"><a href="{{ route('admin.ordine_lavoro.show', $riga->ordine_lavoro_id) }}">Apri ordine</a></div>
377
                 <div class="small"><a href="{{ route('admin.ordine_lavoro.show', $riga->ordine_lavoro_id) }}">Apri ordine</a></div>
382
                 @endif
378
                 @endif
383
-                @if($riga->nota)
379
+                @if($riga->nota && $riga->nota !== 'Senza contratto né ordine')
384
                 <div class="text-muted small">{{ $riga->nota }}</div>
380
                 <div class="text-muted small">{{ $riga->nota }}</div>
385
                 @endif
381
                 @endif
386
               </td>
382
               </td>
392
               <td class="text-end">{{ \App\Models\Config::formatCosto($riga->importo ?? 0) }}</td>
388
               <td class="text-end">{{ \App\Models\Config::formatCosto($riga->importo ?? 0) }}</td>
393
             </tr>
389
             </tr>
394
             @empty
390
             @empty
395
-            <tr><td colspan="5" class="text-muted">Nessuna voce</td></tr>
391
+            <tr><td colspan="4" class="text-muted">Nessuna voce</td></tr>
396
             @endforelse
392
             @endforelse
397
           </tbody>
393
           </tbody>
398
           <tfoot>
394
           <tfoot>
399
             <tr>
395
             <tr>
400
-              <th colspan="4" class="text-end">Imponibile</th>
396
+              <th colspan="3" class="text-end">Imponibile</th>
401
               <th class="text-end">{{ \App\Models\Config::formatCosto($fattura->imponibile ?? 0) }}</th>
397
               <th class="text-end">{{ \App\Models\Config::formatCosto($fattura->imponibile ?? 0) }}</th>
402
             </tr>
398
             </tr>
403
             <tr>
399
             <tr>
404
-              <th colspan="4" class="text-end">IVA {{ rtrim(rtrim(number_format((float) $fattura->iva_percentuale, 2, ',', ''), '0'), ',') }}%</th>
400
+              <th colspan="3" class="text-end">IVA {{ rtrim(rtrim(number_format((float) $fattura->iva_percentuale, 2, ',', ''), '0'), ',') }}%</th>
405
               <th class="text-end">{{ \App\Models\Config::formatCosto($fattura->iva ?? 0) }}</th>
401
               <th class="text-end">{{ \App\Models\Config::formatCosto($fattura->iva ?? 0) }}</th>
406
             </tr>
402
             </tr>
407
             <tr>
403
             <tr>
408
-              <th colspan="4" class="text-end">Totale</th>
404
+              <th colspan="3" class="text-end">Totale</th>
409
               <th class="text-end">{{ \App\Models\Config::formatCosto($fattura->totale ?? 0) }}</th>
405
               <th class="text-end">{{ \App\Models\Config::formatCosto($fattura->totale ?? 0) }}</th>
410
             </tr>
406
             </tr>
411
           </tfoot>
407
           </tfoot>

+ 1
- 0
routes/web.php Просмотреть файл

122
     Route::get('fatturazione/{id}', [FatturazioneController::class, 'admin_show'])->name('admin.fatturazione.show')->whereNumber('id');
122
     Route::get('fatturazione/{id}', [FatturazioneController::class, 'admin_show'])->name('admin.fatturazione.show')->whereNumber('id');
123
     Route::put('fatturazione/{id}', [FatturazioneController::class, 'admin_update'])->name('admin.fatturazione.update')->whereNumber('id');
123
     Route::put('fatturazione/{id}', [FatturazioneController::class, 'admin_update'])->name('admin.fatturazione.update')->whereNumber('id');
124
     Route::post('fatturazione/{id}/emetti', [FatturazioneController::class, 'admin_emetti'])->name('admin.fatturazione.emetti')->whereNumber('id');
124
     Route::post('fatturazione/{id}/emetti', [FatturazioneController::class, 'admin_emetti'])->name('admin.fatturazione.emetti')->whereNumber('id');
125
+    Route::post('fatturazione/{id}/fic', [FatturazioneController::class, 'admin_invia_fic'])->name('admin.fatturazione.fic')->whereNumber('id');
125
     Route::post('fatturazione/{id}/annulla', [FatturazioneController::class, 'admin_annulla'])->name('admin.fatturazione.annulla')->whereNumber('id');
126
     Route::post('fatturazione/{id}/annulla', [FatturazioneController::class, 'admin_annulla'])->name('admin.fatturazione.annulla')->whereNumber('id');
126
     Route::delete('fatturazione/{id}', [FatturazioneController::class, 'admin_destroy'])->name('admin.fatturazione.destroy')->whereNumber('id');
127
     Route::delete('fatturazione/{id}', [FatturazioneController::class, 'admin_destroy'])->name('admin.fatturazione.destroy')->whereNumber('id');
127
 
128
 

Загрузка…
Отмена
Сохранить