Browse Source

fatture in cloud

Roberto Santini 3 weeks ago
parent
commit
36e68297a2

+ 46
- 1
app/Services/FatturaFicService.php View File

@@ -134,11 +134,27 @@ class FatturaFicService
134 134
             $items[] = $this->item($riga, $vatId);
135 135
         }
136 136
 
137
+        $netto = 0.0;
138
+        foreach ($items as $item) {
139
+            $netto += round(((float) $item['qty']) * ((float) $item['net_price']), 2);
140
+        }
141
+        $netto = round($netto, 2);
142
+        $pct = (float) ($fattura->iva_percentuale ?? Config::ivaPercentualeDefault());
143
+        $lordo = round($netto + round($netto * $pct / 100, 2), 2);
144
+        $scadenza = $fattura->data_scadenza
145
+            ? Carbon::parse($fattura->data_scadenza)->toDateString()
146
+            : Carbon::parse($data)->addDays(30)->toDateString();
147
+
137 148
         $payload = [
138 149
             'type' => 'invoice',
139 150
             'entity' => $entity,
140 151
             'date' => $data,
141 152
             'items_list' => $items,
153
+            'payments_list' => [[
154
+                'amount' => $lordo,
155
+                'due_date' => $scadenza,
156
+                'status' => 'not_paid',
157
+            ]],
142 158
             'e_invoice' => false,
143 159
         ];
144 160
 
@@ -161,6 +177,7 @@ class FatturaFicService
161 177
         if ($qty <= 0) {
162 178
             $qty = 1;
163 179
         }
180
+        $importo = round((float) ($riga->importo ?? 0), 2);
164 181
         $name = trim((string) $riga->descrizione);
165 182
         if ($name === '') {
166 183
             $name = $riga->tipo_label;
@@ -172,7 +189,7 @@ class FatturaFicService
172 189
         $item = [
173 190
             'name' => $name,
174 191
             'qty' => $qty,
175
-            'net_price' => round((float) ($riga->importo ?? 0) / $qty, 5),
192
+            'net_price' => $this->prezzoUnitario($qty, $importo),
176 193
             'vat' => ['id' => $vatId],
177 194
         ];
178 195
         $measure = Prodotto::UNITA_MISURA[$riga->unita_misura] ?? $riga->unita_misura;
@@ -183,6 +200,34 @@ class FatturaFicService
183 200
         return $item;
184 201
     }
185 202
 
203
+    /**
204
+     * Prezzo unitario che, moltiplicato per la quantità e arrotondato a 2 decimali, ridà l'importo riga.
205
+     */
206
+    protected function prezzoUnitario(float $qty, float $importo): float
207
+    {
208
+        $target = (int) round($importo * 100);
209
+        $price = round($importo / $qty, 5);
210
+        if ($this->centesimiRiga($qty, $price) === $target) {
211
+            return $price;
212
+        }
213
+
214
+        for ($step = 1; $step <= 50; $step++) {
215
+            foreach ([1, -1] as $sign) {
216
+                $candidate = round($price + ($sign * $step * 0.00001), 5);
217
+                if ($this->centesimiRiga($qty, $candidate) === $target) {
218
+                    return $candidate;
219
+                }
220
+            }
221
+        }
222
+
223
+        return $price;
224
+    }
225
+
226
+    protected function centesimiRiga(float $qty, float $price): int
227
+    {
228
+        return (int) round(round($qty * $price, 2) * 100);
229
+    }
230
+
186 231
     protected function vatId(float $percent): int
187 232
     {
188 233
         foreach ($this->fic->vatTypes() as $type) {

+ 93
- 18
app/Services/FatturaReportTicketPdfService.php View File

@@ -18,9 +18,9 @@ class FatturaReportTicketPdfService
18 18
      */
19 19
     public function righe(Fattura $fattura): array
20 20
     {
21
-        $daRighe = $this->righeDaFattura($fattura);
22
-        if ($daRighe !== []) {
23
-            return $daRighe;
21
+        $cronologia = $this->cronologia($fattura);
22
+        if ($cronologia !== []) {
23
+            return $cronologia;
24 24
         }
25 25
 
26 26
         $salvate = data_get($fattura->meta, 'dettaglio_ticket');
@@ -59,24 +59,41 @@ class FatturaReportTicketPdfService
59 59
     }
60 60
 
61 61
     /**
62
+     * Una riga per ogni tempo registrato sui ticket della fattura, nel periodo.
63
+     *
62 64
      * @return array<int, array{titolo: string, data: string, minuti_ticket: int, minuti_fatturati: int, uscita: ?float}>
63 65
      */
64
-    protected function righeDaFattura(Fattura $fattura): array
66
+    protected function cronologia(Fattura $fattura): array
65 67
     {
66 68
         $fattura->loadMissing('righe');
67
-        $ids = $fattura->righe
69
+        $idsFatturati = $fattura->righe
68 70
             ->pluck('annotazione_id')
69 71
             ->filter()
70 72
             ->map(fn ($id) => (int) $id)
73
+            ->merge(collect(data_get($fattura->meta, 'annotazione_ids', []))->map(fn ($id) => (int) $id))
71 74
             ->unique()
72 75
             ->values();
73
-        if ($ids->isEmpty()) {
76
+
77
+        $segnalazioneIds = $fattura->righe
78
+            ->pluck('segnalazione_id')
79
+            ->filter()
80
+            ->map(fn ($id) => (int) $id);
81
+        if ($idsFatturati->isNotEmpty()) {
82
+            $segnalazioneIds = $segnalazioneIds->merge(
83
+                Annotazione::query()->whereIn('id', $idsFatturati)->pluck('segnalazione_id')
84
+            );
85
+        }
86
+        $segnalazioneIds = $segnalazioneIds->filter()->map(fn ($id) => (int) $id)->unique()->values();
87
+        if ($segnalazioneIds->isEmpty()) {
74 88
             return [];
75 89
         }
76 90
 
91
+        $from = $fattura->periodo_da ? Carbon::parse($fattura->periodo_da)->startOfDay() : null;
92
+        $to = $fattura->periodo_a ? Carbon::parse($fattura->periodo_a)->endOfDay() : null;
93
+
77 94
         $tipiOre = [FatturaRiga::TIPO_TEMPO, FatturaRiga::TIPO_EXTRA_CONTRATTO, FatturaRiga::TIPO_EXTRA_ORDINE];
78 95
         $uscite = [];
79
-        $minuti = [];
96
+        $minutiFatturati = [];
80 97
         foreach ($fattura->righe as $riga) {
81 98
             if (! $riga->annotazione_id) {
82 99
                 continue;
@@ -86,35 +103,93 @@ class FatturaReportTicketPdfService
86 103
                 $uscite[$id] = round(($uscite[$id] ?? 0) + (float) ($riga->importo ?? 0), 2);
87 104
             }
88 105
             if (in_array($riga->tipo, $tipiOre, true)) {
89
-                $minuti[$id] = ($minuti[$id] ?? 0) + (int) ($riga->minuti ?? 0);
106
+                $minutiFatturati[$id] = ($minutiFatturati[$id] ?? 0) + (int) ($riga->minuti ?? 0);
90 107
             }
91 108
         }
92 109
 
93
-        $annotazioni = Annotazione::query()->with('segnalazione')->whereIn('id', $ids)->get()->keyBy('id');
94
-        $righe = [];
95
-        foreach ($ids as $id) {
96
-            $annotazione = $annotazioni->get($id);
97
-            if (! $annotazione) {
110
+        $daSnapshot = [];
111
+        $daSnapshotData = [];
112
+        $usciteSnapshot = [];
113
+        $usciteSnapshotData = [];
114
+        foreach (data_get($fattura->meta, 'dettaglio_ticket', []) as $salvata) {
115
+            if (! is_array($salvata)) {
98 116
                 continue;
99 117
             }
118
+            if (array_key_exists('minuti_fatturati', $salvata)) {
119
+                $minutiSalvati = (int) $salvata['minuti_fatturati'];
120
+            } elseif (array_key_exists('minuti', $salvata)) {
121
+                $minutiSalvati = (int) $salvata['minuti'];
122
+            } else {
123
+                $minutiSalvati = null;
124
+            }
125
+            $chiaveData = (string) ($salvata['data'] ?? '').'|'.(string) ($salvata['titolo'] ?? '');
126
+            if (! empty($salvata['annotazione_id']) && $minutiSalvati !== null) {
127
+                $daSnapshot[(int) $salvata['annotazione_id']] = $minutiSalvati;
128
+            }
129
+            if ($minutiSalvati !== null && $chiaveData !== '|') {
130
+                $daSnapshotData[$chiaveData] = $minutiSalvati;
131
+            }
132
+            if (! empty($salvata['uscita'])) {
133
+                if (! empty($salvata['annotazione_id'])) {
134
+                    $usciteSnapshot[(int) $salvata['annotazione_id']] = (float) $salvata['uscita'];
135
+                }
136
+                if ($chiaveData !== '|') {
137
+                    $usciteSnapshotData[$chiaveData] = (float) $salvata['uscita'];
138
+                }
139
+            }
140
+        }
141
+
142
+        $annotazioni = Annotazione::query()
143
+            ->with('segnalazione')
144
+            ->where(function ($q) use ($segnalazioneIds, $from, $to, $idsFatturati) {
145
+                $q->where(function ($q2) use ($segnalazioneIds, $from, $to) {
146
+                    $q2->whereIn('segnalazione_id', $segnalazioneIds);
147
+                    if ($from && $to) {
148
+                        $q2->where(function ($q3) use ($from, $to) {
149
+                            $q3->whereBetween('tempo_data', [$from, $to])
150
+                                ->orWhere(function ($q4) use ($from, $to) {
151
+                                    $q4->whereNull('tempo_data')->whereBetween('created_at', [$from, $to]);
152
+                                });
153
+                        });
154
+                    }
155
+                });
156
+                if ($idsFatturati->isNotEmpty()) {
157
+                    $q->orWhereIn('id', $idsFatturati);
158
+                }
159
+            })
160
+            ->where(function ($q) {
161
+                $q->whereNull('non_fatturare')->orWhere('non_fatturare', false);
162
+            })
163
+            ->where(function ($q) {
164
+                $q->where('tempo_minuti', '>', 0)->orWhere('costo_uscita', true);
165
+            })
166
+            ->orderByRaw('COALESCE(tempo_data, created_at)')
167
+            ->orderBy('id')
168
+            ->get();
169
+
170
+        $righe = [];
171
+        foreach ($annotazioni as $annotazione) {
172
+            $id = (int) $annotazione->id;
100 173
             $quando = $annotazione->tempo_data
101 174
                 ? Carbon::parse($annotazione->tempo_data)
102 175
                 : Carbon::parse($annotazione->created_at);
103
-            $minutiFattura = (int) ($minuti[$id] ?? 0);
176
+            $titolo = $annotazione->segnalazione?->titolo ?: 'Ticket';
177
+            $chiaveData = $quando->format('Y-m-d H:i').'|'.$titolo;
178
+            $minutiFattura = $minutiFatturati[$id] ?? $daSnapshot[$id] ?? $daSnapshotData[$chiaveData] ?? 0;
104 179
             $minutiTicket = (int) $annotazione->tempo_minuti;
105
-            $uscita = $uscite[$id] ?? null;
180
+            $uscita = $uscite[$id] ?? $usciteSnapshot[$id] ?? $usciteSnapshotData[$chiaveData] ?? null;
106 181
             if ($minutiTicket <= 0 && $minutiFattura <= 0 && ! $uscita) {
107 182
                 continue;
108 183
             }
184
+            $numero = $annotazione->segnalazione?->getNumeroLabel();
109 185
             $righe[] = [
110
-                'titolo' => $annotazione->segnalazione?->titolo ?: 'Ticket',
186
+                'titolo' => $numero ? $numero.' — '.$titolo : $titolo,
111 187
                 'data' => $quando->format('Y-m-d H:i'),
112 188
                 'minuti_ticket' => $minutiTicket,
113
-                'minuti_fatturati' => $minutiFattura,
189
+                'minuti_fatturati' => (int) $minutiFattura,
114 190
                 'uscita' => $uscita,
115 191
             ];
116 192
         }
117
-        usort($righe, fn ($a, $b) => strcmp($a['data'], $b['data']));
118 193
 
119 194
         return $righe;
120 195
     }

+ 39
- 16
app/Services/FatturazioneGeneraService.php View File

@@ -133,7 +133,7 @@ class FatturazioneGeneraService
133 133
 
134 134
         return DB::transaction(function () use ($cliente, $from, $to, $author, $user, $accorpaSegnalazioni) {
135 135
             $voci = $cliente['voci'];
136
-            $dettaglio = $this->dettaglioTicket($voci);
136
+            $dettaglio = $this->dettaglioTicket($voci, $from, $to);
137 137
             $annotazioneIds = collect($voci)->pluck('annotazione_id')->filter()->map(fn ($id) => (int) $id)->unique()->values()->all();
138 138
             if ($accorpaSegnalazioni) {
139 139
                 $voci = $this->accorpaVociSegnalazioni($voci);
@@ -1234,12 +1234,13 @@ class FatturazioneGeneraService
1234 1234
 
1235 1235
     /**
1236 1236
      * @param  array<int, array<string, mixed>>  $voci
1237
-     * @return array<int, array{titolo: string, data: string, minuti_ticket: int, minuti_fatturati: int, uscita: ?float}>
1237
+     * @return array<int, array{annotazione_id: int, titolo: string, data: string, minuti_ticket: int, minuti_fatturati: int, uscita: ?float}>
1238 1238
      */
1239
-    protected function dettaglioTicket(array $voci): array
1239
+    protected function dettaglioTicket(array $voci, Carbon $from, Carbon $to): array
1240 1240
     {
1241
-        $ids = collect($voci)->pluck('annotazione_id')->filter()->map(fn ($id) => (int) $id)->unique()->values();
1242
-        if ($ids->isEmpty()) {
1241
+        $idsFatturati = collect($voci)->pluck('annotazione_id')->filter()->map(fn ($id) => (int) $id)->unique()->values();
1242
+        $segnalazioneIds = collect($voci)->pluck('segnalazione_id')->filter()->map(fn ($id) => (int) $id)->unique()->values();
1243
+        if ($idsFatturati->isEmpty() && $segnalazioneIds->isEmpty()) {
1243 1244
             return [];
1244 1245
         }
1245 1246
 
@@ -1259,18 +1260,39 @@ class FatturazioneGeneraService
1259 1260
             }
1260 1261
         }
1261 1262
 
1263
+        if ($segnalazioneIds->isEmpty()) {
1264
+            $segnalazioneIds = Annotazione::query()->whereIn('id', $idsFatturati)->pluck('segnalazione_id')->filter()->map(fn ($id) => (int) $id)->unique()->values();
1265
+        }
1266
+
1262 1267
         $annotazioni = Annotazione::query()
1263 1268
             ->with('segnalazione')
1264
-            ->whereIn('id', $ids)
1265
-            ->get()
1266
-            ->keyBy('id');
1269
+            ->where(function ($q) use ($segnalazioneIds, $from, $to, $idsFatturati) {
1270
+                $q->where(function ($q2) use ($segnalazioneIds, $from, $to) {
1271
+                    $q2->whereIn('segnalazione_id', $segnalazioneIds)
1272
+                        ->where(function ($q3) use ($from, $to) {
1273
+                            $q3->whereBetween('tempo_data', [$from, $to])
1274
+                                ->orWhere(function ($q4) use ($from, $to) {
1275
+                                    $q4->whereNull('tempo_data')->whereBetween('created_at', [$from, $to]);
1276
+                                });
1277
+                        });
1278
+                });
1279
+                if ($idsFatturati->isNotEmpty()) {
1280
+                    $q->orWhereIn('id', $idsFatturati);
1281
+                }
1282
+            })
1283
+            ->where(function ($q) {
1284
+                $q->whereNull('non_fatturare')->orWhere('non_fatturare', false);
1285
+            })
1286
+            ->where(function ($q) {
1287
+                $q->where('tempo_minuti', '>', 0)->orWhere('costo_uscita', true);
1288
+            })
1289
+            ->orderByRaw('COALESCE(tempo_data, created_at)')
1290
+            ->orderBy('id')
1291
+            ->get();
1267 1292
 
1268 1293
         $righe = [];
1269
-        foreach ($ids as $id) {
1270
-            $annotazione = $annotazioni->get($id);
1271
-            if (! $annotazione) {
1272
-                continue;
1273
-            }
1294
+        foreach ($annotazioni as $annotazione) {
1295
+            $id = (int) $annotazione->id;
1274 1296
             $quando = $annotazione->tempo_data
1275 1297
                 ? Carbon::parse($annotazione->tempo_data)
1276 1298
                 : Carbon::parse($annotazione->created_at);
@@ -1280,8 +1302,11 @@ class FatturazioneGeneraService
1280 1302
             if ($minutiTicket <= 0 && $minutiFattura <= 0 && ! $uscita) {
1281 1303
                 continue;
1282 1304
             }
1305
+            $titolo = $annotazione->segnalazione?->titolo ?: 'Ticket';
1306
+            $numero = $annotazione->segnalazione?->getNumeroLabel();
1283 1307
             $righe[] = [
1284
-                'titolo' => $annotazione->segnalazione?->titolo ?: 'Ticket',
1308
+                'annotazione_id' => $id,
1309
+                'titolo' => $numero ? $numero.' — '.$titolo : $titolo,
1285 1310
                 'data' => $quando->format('Y-m-d H:i'),
1286 1311
                 'minuti_ticket' => $minutiTicket,
1287 1312
                 'minuti_fatturati' => $minutiFattura,
@@ -1289,8 +1314,6 @@ class FatturazioneGeneraService
1289 1314
             ];
1290 1315
         }
1291 1316
 
1292
-        usort($righe, fn ($a, $b) => strcmp($a['data'], $b['data']));
1293
-
1294 1317
         return $righe;
1295 1318
     }
1296 1319
 

+ 1
- 1
resources/views/fatturazione/report-ticket.blade.php View File

@@ -38,7 +38,7 @@
38 38
       @forelse($righe as $riga)
39 39
       <tr>
40 40
         <td>{{ $riga['titolo'] }}</td>
41
-        <td>{{ \Carbon\Carbon::parse($riga['data'])->format('d/m/Y') }}</td>
41
+        <td class="text-nowrap">{{ \Carbon\Carbon::parse($riga['data'])->format('d/m/Y H:i') }}</td>
42 42
         <td class="text-end">{{ ($riga['minuti_ticket'] ?? 0) > 0 ? \App\Models\Config::formatTempo((int) $riga['minuti_ticket']) : '—' }}</td>
43 43
         <td class="text-end">{{ ($riga['minuti_fatturati'] ?? 0) > 0 ? \App\Models\Config::formatTempo((int) $riga['minuti_fatturati']) : '—' }}</td>
44 44
         <td class="text-end">{{ !empty($riga['uscita']) ? \App\Models\Config::formatCosto($riga['uscita']) : '—' }}</td>

Loading…
Cancel
Save