ReportTaxes::exportAction()   F
last analyzed

Complexity

Conditions 14
Paths 1540

Size

Total Lines 61
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 14
eloc 45
nc 1540
nop 0
dl 0
loc 61
rs 2.1
c 1
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * This file is part of FacturaScripts
4
 * Copyright (C) 2021-2024 Carlos Garcia Gomez <[email protected]>
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU Lesser General Public License as
8
 * published by the Free Software Foundation, either version 3 of the
9
 * License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
 * GNU Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public License
17
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18
 */
19
20
namespace FacturaScripts\Core\Controller;
21
22
use FacturaScripts\Core\Base\Controller;
23
use FacturaScripts\Core\Base\ControllerPermissions;
24
use FacturaScripts\Core\DataSrc\Divisas;
25
use FacturaScripts\Core\Tools;
26
use FacturaScripts\Dinamic\Lib\ExportManager;
27
use FacturaScripts\Dinamic\Lib\InvoiceOperation;
28
use FacturaScripts\Dinamic\Model\Divisa;
29
use FacturaScripts\Dinamic\Model\Pais;
30
use FacturaScripts\Dinamic\Model\Serie;
31
use FacturaScripts\Dinamic\Model\User;
32
use Symfony\Component\HttpFoundation\Response;
33
34
/**
35
 * Description of ReportTaxes
36
 *
37
 * @author Carlos Garcia Gomez <[email protected]>
38
 */
39
class ReportTaxes extends Controller
40
{
41
    const MAX_TOTAL_DIFF = 0.05;
42
43
    /** @var string */
44
    public $coddivisa;
45
46
    /** @var string */
47
    public $codpais;
48
49
    /** @var string */
50
    public $codserie;
51
52
    /** @var string */
53
    public $datefrom;
54
55
    /** @var string */
56
    public $dateto;
57
58
    /** @var Divisa */
59
    public $divisa;
60
61
    /** @var string */
62
    public $format;
63
64
    /** @var int */
65
    public $idempresa;
66
67
    /** @var Pais */
68
    public $pais;
69
70
    /** @var Serie */
71
    public $serie;
72
73
    /** @var string */
74
    public $source;
75
76
    /** @var string */
77
    public $typeDate;
78
79
    public function getPageData(): array
80
    {
81
        $data = parent::getPageData();
82
        $data['title'] = 'taxes';
83
        $data['menu'] = 'reports';
84
        $data['icon'] = 'fas fa-wallet';
85
        return $data;
86
    }
87
88
    /**
89
     * @param Response $response
90
     * @param User $user
91
     * @param ControllerPermissions $permissions
92
     */
93
    public function privateCore(&$response, $user, $permissions)
94
    {
95
        parent::privateCore($response, $user, $permissions);
96
97
        $this->divisa = new Divisa();
98
        $this->pais = new Pais();
99
        $this->serie = new Serie();
100
        $this->initFilters();
101
102
        if ('export' === $this->request->request->get('action')) {
103
            $this->exportAction();
104
        }
105
    }
106
107
    protected function exportAction(): void
108
    {
109
        $i18n = Tools::lang();
110
        $data = $this->getReportData();
111
        if (empty($data)) {
112
            Tools::log()->warning('no-data');
113
            return;
114
        }
115
116
        // prepare lines
117
        $lastCode = '';
118
        $lines = [];
119
        foreach ($data as $row) {
120
            $hide = $row['codigo'] === $lastCode && $this->format === 'PDF';
121
            $num2title = $this->source === 'sales' ? 'number2' : 'numproveedor';
122
123
            $lines[] = [
124
                $i18n->trans('serie') => $hide ? '' : $row['codserie'],
125
                $i18n->trans('code') => $hide ? '' : $row['codigo'],
126
                $i18n->trans($num2title) => $hide ? '' : $row['numero2'],
127
                $i18n->trans('date') => $hide ? '' : Tools::date($row['fecha']),
128
                $i18n->trans('name') => $hide ? '' : Tools::fixHtml($row['nombre']),
129
                $i18n->trans('cifnif') => $hide ? '' : $row['cifnif'],
130
                $i18n->trans('net') => $this->exportFieldFormat('number', $row['neto']),
131
                $i18n->trans('pct-tax') => $this->exportFieldFormat('number', $row['iva']),
132
                $i18n->trans('tax') => $this->exportFieldFormat('number', $row['totaliva']),
133
                $i18n->trans('pct-surcharge') => $this->exportFieldFormat('number', $row['recargo']),
134
                $i18n->trans('surcharge') => $this->exportFieldFormat('number', $row['totalrecargo']),
135
                $i18n->trans('pct-irpf') => $this->exportFieldFormat('number', $row['irpf']),
136
                $i18n->trans('irpf') => $this->exportFieldFormat('number', $row['totalirpf']),
137
                $i18n->trans('supplied-amount') => $this->exportFieldFormat('number', $row['suplidos']),
138
                $i18n->trans('total') => $hide ? '' : $this->exportFieldFormat('number', $row['total'])
139
            ];
140
141
            $lastCode = $row['codigo'];
142
        }
143
144
        $totalsData = $this->getTotals($data);
145
        if (false === $this->validateTotals($totalsData)) {
146
            return;
147
        }
148
149
        // prepare totals
150
        $totals = [];
151
        foreach ($totalsData as $row) {
152
            $total = $row['neto'] + $row['totaliva'] + $row['totalrecargo'] - $row['totalirpf'] - $row['suplidos'];
153
            $totals[] = [
154
                $i18n->trans('net') => $this->exportFieldFormat('number', $row['neto']),
155
                $i18n->trans('pct-tax') => $this->exportFieldFormat('percentage', $row['iva']),
156
                $i18n->trans('tax') => $this->exportFieldFormat('number', $row['totaliva']),
157
                $i18n->trans('pct-surcharge') => $this->exportFieldFormat('percentage', $row['recargo']),
158
                $i18n->trans('surcharge') => $this->exportFieldFormat('number', $row['totalrecargo']),
159
                $i18n->trans('pct-irpf') => $this->exportFieldFormat('percentage', $row['irpf']),
160
                $i18n->trans('irpf') => $this->exportFieldFormat('number', $row['totalirpf']),
161
                $i18n->trans('supplied-amount') => $this->exportFieldFormat('number', $row['suplidos']),
162
                $i18n->trans('total') => $this->exportFieldFormat('number', $total)
163
            ];
164
        }
165
166
        $this->setTemplate(false);
167
        $this->processLayout($lines, $totals);
168
    }
169
170
    protected function exportFieldFormat(string $format, string $value): string
171
    {
172
        switch ($format) {
173
            case 'number':
174
                return $this->format === 'PDF' ? Tools::number($value) : $value;
0 ignored issues
show
Bug introduced by
$value of type string is incompatible with the type double|null expected by parameter $number of FacturaScripts\Core\Tools::number(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

174
                return $this->format === 'PDF' ? Tools::number(/** @scrutinizer ignore-type */ $value) : $value;
Loading history...
175
176
            case 'percentage':
177
                return $this->format === 'PDF' ? Tools::number($value) . ' %' : $value;
178
179
            default:
180
                return $value;
181
        }
182
    }
183
184
    protected function getQuarterDate(bool $start): string
185
    {
186
        $month = (int)date('m');
187
188
        // si la fecha actual es de enero, seleccionamos el trimestre anterior
189
        if ($month === 1) {
190
            return $start ?
191
                date('Y-10-01', strtotime('-1 year')) :
192
                date('Y-12-31', strtotime('-1 year'));
193
        }
194
195
        // comprobamos si la fecha actual está en el primer trimestre o justo en el siguiente mes
196
        if ($month >= 1 && $month <= 4) {
197
            return $start ? date('Y-01-01') : date('Y-03-31');
198
        }
199
200
        // comprobamos si la fecha actual está en el segundo trimestre o justo en el siguiente mes
201
        if ($month >= 4 && $month <= 7) {
202
            return $start ? date('Y-04-01') : date('Y-06-30');
203
        }
204
205
        // comprobamos si la fecha actual está en el tercer trimestre o justo en el siguiente mes
206
        if ($month >= 7 && $month <= 10) {
207
            return $start ? date('Y-07-01') : date('Y-09-30');
208
        }
209
210
        // la fecha actual está en el cuarto trimestre
211
        return $start ? date('Y-10-01') : date('Y-12-31');
212
    }
213
214
    protected function getReportData(): array
215
    {
216
        $sql = '';
217
        $numCol = strtolower(FS_DB_TYPE) == 'postgresql' ? 'CAST(f.numero as integer)' : 'CAST(f.numero as unsigned)';
0 ignored issues
show
introduced by
The condition strtolower(FacturaScript...B_TYPE) == 'postgresql' is always true.
Loading history...
218
        $columnDate = $this->typeDate === 'create' ? 'f.fecha' : 'COALESCE(f.fechadevengo, f.fecha)';
219
        switch ($this->source) {
220
            case 'purchases':
221
                $sql .= 'SELECT f.codserie, f.codigo, f.numproveedor AS numero2, f.fecha, f.fechadevengo, f.nombre, f.cifnif, l.pvptotal,'
222
                    . ' l.iva, l.recargo, l.irpf, l.suplido, f.dtopor1, f.dtopor2, f.total, f.operacion'
223
                    . ' FROM lineasfacturasprov AS l'
224
                    . ' LEFT JOIN facturasprov AS f ON l.idfactura = f.idfactura '
225
                    . ' WHERE f.idempresa = ' . $this->dataBase->var2str($this->idempresa)
226
                    . ' AND ' . $columnDate . ' >= ' . $this->dataBase->var2str($this->datefrom)
227
                    . ' AND ' . $columnDate . ' <= ' . $this->dataBase->var2str($this->dateto)
228
                    . ' AND (l.pvptotal <> 0.00 OR l.iva <> 0.00)'
229
                    . ' AND f.coddivisa = ' . $this->dataBase->var2str($this->coddivisa);
230
                break;
231
232
            case 'sales':
233
                $sql .= 'SELECT f.codserie, f.codigo, f.numero2, f.fecha, f.fechadevengo, f.nombrecliente AS nombre, f.cifnif, l.pvptotal,'
234
                    . ' l.iva, l.recargo, l.irpf, l.suplido, f.dtopor1, f.dtopor2, f.total, f.operacion'
235
                    . ' FROM lineasfacturascli AS l'
236
                    . ' LEFT JOIN facturascli AS f ON l.idfactura = f.idfactura '
237
                    . ' WHERE f.idempresa = ' . $this->dataBase->var2str($this->idempresa)
238
                    . ' AND ' . $columnDate . ' >= ' . $this->dataBase->var2str($this->datefrom)
239
                    . ' AND ' . $columnDate . ' <= ' . $this->dataBase->var2str($this->dateto)
240
                    . ' AND (l.pvptotal <> 0.00 OR l.iva <> 0.00)'
241
                    . ' AND f.coddivisa = ' . $this->dataBase->var2str($this->coddivisa);
242
                if ($this->codpais) {
243
                    $sql .= ' AND codpais = ' . $this->dataBase->var2str($this->codpais);
244
                }
245
                break;
246
247
            default:
248
                Tools::log()->warning('wrong-source');
249
                return [];
250
        }
251
        if ($this->codserie) {
252
            $sql .= ' AND codserie = ' . $this->dataBase->var2str($this->codserie);
253
        }
254
        $sql .= ' ORDER BY ' . $columnDate . ', ' . $numCol . ' ASC;';
255
256
        $data = [];
257
        foreach ($this->dataBase->select($sql) as $row) {
258
            $pvpTotal = floatval($row['pvptotal']) * (100 - floatval($row['dtopor1'])) * (100 - floatval($row['dtopor2'])) / 10000;
259
            $code = $row['codigo'] . '-' . $row['iva'] . '-' . $row['recargo'] . '-' . $row['irpf'] . '-' . $row['suplido'];
260
            if (isset($data[$code])) {
261
                $data[$code]['neto'] += $row['suplido'] ? 0 : $pvpTotal;
262
                $data[$code]['totaliva'] += $row['suplido'] || $row['operacion'] === InvoiceOperation::INTRA_COMMUNITY ? 0 : (float)$row['iva'] * $pvpTotal / 100;
263
                $data[$code]['totalrecargo'] += $row['suplido'] ? 0 : (float)$row['recargo'] * $pvpTotal / 100;
264
                $data[$code]['totalirpf'] += $row['suplido'] ? 0 : (float)$row['irpf'] * $pvpTotal / 100;
265
                $data[$code]['suplidos'] += $row['suplido'] ? $pvpTotal : 0;
266
                continue;
267
            }
268
269
            $data[$code] = [
270
                'codserie' => $row['codserie'],
271
                'codigo' => $row['codigo'],
272
                'numero2' => $row['numero2'],
273
                'fecha' => $this->typeDate == 'create' ?
274
                    $row['fecha'] :
275
                    $row['fechadevengo'] ?? $row['fecha'],
276
                'nombre' => $row['nombre'],
277
                'cifnif' => $row['cifnif'],
278
                'neto' => $row['suplido'] ? 0 : $pvpTotal,
279
                'iva' => $row['suplido'] ? 0 : (float)$row['iva'],
280
                'totaliva' => $row['suplido'] || $row['operacion'] === InvoiceOperation::INTRA_COMMUNITY ? 0 : (float)$row['iva'] * $pvpTotal / 100,
281
                'recargo' => $row['suplido'] ? 0 : (float)$row['recargo'],
282
                'totalrecargo' => $row['suplido'] ? 0 : (float)$row['recargo'] * $pvpTotal / 100,
283
                'irpf' => $row['suplido'] ? 0 : (float)$row['irpf'],
284
                'totalirpf' => $row['suplido'] ? 0 : (float)$row['irpf'] * $pvpTotal / 100,
285
                'suplidos' => $row['suplido'] ? $pvpTotal : 0,
286
                'total' => (float)$row['total']
287
            ];
288
        }
289
290
        // round
291
        foreach ($data as $key => $value) {
292
            $data[$key]['neto'] = round($value['neto'], FS_NF0);
293
            $data[$key]['totaliva'] = round($value['totaliva'], FS_NF0);
294
            $data[$key]['totalrecargo'] = round($value['totalrecargo'], FS_NF0);
295
            $data[$key]['totalirpf'] = round($value['totalirpf'], FS_NF0);
296
            $data[$key]['suplidos'] = round($value['suplidos'], FS_NF0);
297
        }
298
299
        return $data;
300
    }
301
302
    protected function getTotals(array $data): array
303
    {
304
        $totals = [];
305
        foreach ($data as $row) {
306
            $code = $row['iva'] . '-' . $row['recargo'] . '-' . $row['irpf'];
307
            if (isset($totals[$code])) {
308
                $totals[$code]['neto'] += $row['neto'];
309
                $totals[$code]['totaliva'] += $row['totaliva'];
310
                $totals[$code]['totalrecargo'] += $row['totalrecargo'];
311
                $totals[$code]['totalirpf'] += $row['totalirpf'];
312
                $totals[$code]['suplidos'] += $row['suplidos'];
313
                continue;
314
            }
315
316
            $totals[$code] = [
317
                'neto' => $row['neto'],
318
                'iva' => $row['iva'],
319
                'totaliva' => $row['totaliva'],
320
                'recargo' => $row['recargo'],
321
                'totalrecargo' => $row['totalrecargo'],
322
                'irpf' => $row['irpf'],
323
                'totalirpf' => $row['totalirpf'],
324
                'suplidos' => $row['suplidos']
325
            ];
326
        }
327
328
        return $totals;
329
    }
330
331
    protected function initFilters(): void
332
    {
333
        $this->coddivisa = $this->request->request->get(
334
            'coddivisa',
335
            Tools::settings('default', 'coddivisa')
336
        );
337
338
        $this->codpais = $this->request->request->get('codpais', '');
339
        $this->codserie = $this->request->request->get('codserie', '');
340
        $this->datefrom = $this->request->request->get('datefrom', $this->getQuarterDate(true));
341
        $this->dateto = $this->request->request->get('dateto', $this->getQuarterDate(false));
342
343
        $this->idempresa = (int)$this->request->request->get(
344
            'idempresa',
345
            Tools::settings('default', 'idempresa')
346
        );
347
348
        $this->format = $this->request->request->get('format');
349
        $this->source = $this->request->request->get('source');
350
        $this->typeDate = $this->request->request->get('type-date');
351
    }
352
353
    protected function processLayout(array &$lines, array &$totals): void
354
    {
355
        $i18n = Tools::lang();
356
        $exportManager = new ExportManager();
357
        $exportManager->setOrientation('landscape');
358
        $exportManager->newDoc($this->format, $i18n->trans('taxes'));
359
360
        // add information table
361
        $exportManager->addTablePage(
362
            [
363
                $i18n->trans('report'),
364
                $i18n->trans('currency'),
365
                $i18n->trans('date'),
366
                $i18n->trans('from-date'),
367
                $i18n->trans('until-date')
368
            ],
369
            [[
370
                $i18n->trans('report') => $i18n->trans('taxes') . ' ' . $i18n->trans($this->source),
371
                $i18n->trans('currency') => Divisas::get($this->coddivisa)->descripcion,
372
                $i18n->trans('date') => $i18n->trans($this->typeDate === 'create' ? 'creation-date' : 'accrual-date'),
373
                $i18n->trans('from-date') => Tools::date($this->datefrom),
374
                $i18n->trans('until-date') => Tools::date($this->dateto)
375
            ]]
376
        );
377
378
        $options = [
379
            $i18n->trans('net') => ['display' => 'right'],
380
            $i18n->trans('pct-tax') => ['display' => 'right'],
381
            $i18n->trans('tax') => ['display' => 'right'],
382
            $i18n->trans('pct-surcharge') => ['display' => 'right'],
383
            $i18n->trans('surcharge') => ['display' => 'right'],
384
            $i18n->trans('pct-irpf') => ['display' => 'right'],
385
            $i18n->trans('irpf') => ['display' => 'right'],
386
            $i18n->trans('supplied-amount') => ['display' => 'right'],
387
            $i18n->trans('total') => ['display' => 'right']
388
        ];
389
390
        // add lines table
391
        $this->reduceLines($lines);
392
        $headers = empty($lines) ? [] : array_keys(end($lines));
393
        $exportManager->addTablePage($headers, $lines, $options);
394
395
        // add totals table
396
        $headTotals = empty($totals) ? [] : array_keys(end($totals));
397
        $exportManager->addTablePage($headTotals, $totals, $options);
398
399
        $exportManager->show($this->response);
400
    }
401
402
    protected function reduceLines(array &$lines): void
403
    {
404
        $i18n = Tools::lang();
405
        $zero = Tools::number(0);
406
        $numero2 = $recargo = $totalRecargo = $irpf = $totalIrpf = $suplidos = false;
407
        foreach ($lines as $row) {
408
            if (!empty($row[$i18n->trans('number2')])) {
409
                $numero2 = true;
410
            }
411
412
            if ($row[$i18n->trans('pct-surcharge')] !== $zero) {
413
                $recargo = true;
414
            }
415
416
            if ($row[$i18n->trans('surcharge')] !== $zero) {
417
                $totalRecargo = true;
418
            }
419
420
            if ($row[$i18n->trans('pct-irpf')] !== $zero) {
421
                $irpf = true;
422
            }
423
424
            if ($row[$i18n->trans('irpf')] !== $zero) {
425
                $totalIrpf = true;
426
            }
427
428
            if ($row[$i18n->trans('supplied-amount')] !== $zero) {
429
                $suplidos = true;
430
            }
431
        }
432
433
        foreach (array_keys($lines) as $key) {
434
            if (false === $numero2) {
435
                unset($lines[$key][$i18n->trans('number2')]);
436
            }
437
438
            if (false === $recargo) {
439
                unset($lines[$key][$i18n->trans('pct-surcharge')]);
440
            }
441
442
            if (false === $totalRecargo) {
443
                unset($lines[$key][$i18n->trans('surcharge')]);
444
            }
445
446
            if (false === $irpf) {
447
                unset($lines[$key][$i18n->trans('pct-irpf')]);
448
            }
449
450
            if (false === $totalIrpf) {
451
                unset($lines[$key][$i18n->trans('irpf')]);
452
            }
453
454
            if (false === $suplidos) {
455
                unset($lines[$key][$i18n->trans('supplied-amount')]);
456
            }
457
        }
458
    }
459
460
    protected function validateTotals(array $totalsData): bool
461
    {
462
        // sum totals from the given data
463
        $neto = $totalIva = $totalRecargo = 0.0;
464
        foreach ($totalsData as $row) {
465
            $neto += $row['neto'];
466
            $totalIva += $row['totaliva'];
467
            $totalRecargo += $row['totalrecargo'];
468
        }
469
470
        // gets totals from the database
471
        $neto2 = $totalIva2 = $totalRecargo2 = 0.0;
472
        $tableName = $this->source === 'sales' ? 'facturascli' : 'facturasprov';
473
        $columnDate = $this->typeDate === 'create' ? 'fecha' : 'COALESCE(fechadevengo, fecha)';
474
        $sql = 'SELECT SUM(neto) as neto, SUM(totaliva) as t1, SUM(totalrecargo) as t2'
475
            . ' FROM ' . $tableName
476
            . ' WHERE idempresa = ' . $this->dataBase->var2str($this->idempresa)
477
            . ' AND ' . $columnDate . ' >= ' . $this->dataBase->var2str($this->datefrom)
478
            . ' AND ' . $columnDate . ' <= ' . $this->dataBase->var2str($this->dateto)
479
            . ' AND coddivisa = ' . $this->dataBase->var2str($this->coddivisa);
480
        if ($this->codserie) {
481
            $sql .= ' AND codserie = ' . $this->dataBase->var2str($this->codserie);
482
        }
483
        if ($this->codpais && $this->source === 'sales') {
484
            $sql .= ' AND codpais = ' . $this->dataBase->var2str($this->codpais);
485
        }
486
        foreach ($this->dataBase->selectLimit($sql) as $row) {
487
            $neto2 += (float)$row['neto'];
488
            $totalIva2 += (float)$row['t1'];
489
            $totalRecargo2 += (float)$row['t2'];
490
        }
491
492
        // compare
493
        $result = true;
494
        if (abs($neto - $neto2) > self::MAX_TOTAL_DIFF) {
495
            Tools::log()->error('calculated-net-diff', ['%net%' => $neto, '%net2%' => $neto2]);
496
            $result = false;
497
        }
498
499
        if (abs($totalIva - $totalIva2) > self::MAX_TOTAL_DIFF) {
500
            Tools::log()->error('calculated-tax-diff', ['%tax%' => $totalIva, '%tax2%' => $totalIva2]);
501
            $result = false;
502
        }
503
504
        if (abs($totalRecargo - $totalRecargo2) > self::MAX_TOTAL_DIFF) {
505
            Tools::log()->error('calculated-surcharge-diff', [
506
                '%surcharge%' => $totalRecargo, '%surcharge2%' => $totalRecargo2
507
            ]);
508
            $result = false;
509
        }
510
511
        return $result;
512
    }
513
}
514