1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace CfdiUtils\SumasPagos20; |
4
|
|
|
|
5
|
|
|
use CfdiUtils\Nodes\NodeInterface; |
6
|
|
|
use CfdiUtils\Nodes\Nodes; |
7
|
|
|
|
8
|
|
|
class Calculator |
9
|
|
|
{ |
10
|
|
|
/** @var int */ |
11
|
|
|
private $paymentTaxesPrecision; |
12
|
|
|
|
13
|
|
|
/** @var Currencies */ |
14
|
|
|
private $currencies; |
15
|
|
|
|
16
|
13 |
|
public function __construct(int $paymentTaxesPrecision = 6, ?Currencies $currencies = null) |
17
|
|
|
{ |
18
|
13 |
|
$this->setPaymentTaxesPrecision($paymentTaxesPrecision); |
19
|
13 |
|
$this->currencies = $currencies ?? new Currencies(['MXN' => 2, 'USD' => 2]); |
20
|
|
|
} |
21
|
|
|
|
22
|
13 |
|
public function getPaymentTaxesPrecision(): int |
23
|
|
|
{ |
24
|
13 |
|
return $this->paymentTaxesPrecision; |
25
|
13 |
|
} |
26
|
13 |
|
|
27
|
|
|
public function setPaymentTaxesPrecision(int $paymentTaxesPrecision): void |
28
|
|
|
{ |
29
|
13 |
|
$this->paymentTaxesPrecision = min(6, max(0, $paymentTaxesPrecision)); |
30
|
13 |
|
} |
31
|
|
|
|
32
|
|
|
public function getCurrencies(): Currencies |
33
|
13 |
|
{ |
34
|
|
|
return $this->currencies; |
35
|
13 |
|
} |
36
|
13 |
|
|
37
|
13 |
|
public function setCurrencies(Currencies $currencies): void |
38
|
13 |
|
{ |
39
|
13 |
|
$this->currencies = $currencies; |
40
|
13 |
|
} |
41
|
|
|
|
42
|
13 |
|
public function calculate(NodeInterface $nodePagos): Pagos |
43
|
13 |
|
{ |
44
|
13 |
|
$pagos = []; |
45
|
13 |
|
foreach ($nodePagos->searchNodes('pago20:Pago') as $nodePago) { |
46
|
13 |
|
$pagos[] = $this->buildPago($nodePago); |
47
|
|
|
} |
48
|
|
|
|
49
|
13 |
|
$totales = $this->buildTotales($pagos); |
50
|
|
|
return new Pagos($totales, ...$pagos); |
51
|
13 |
|
} |
52
|
|
|
|
53
|
13 |
|
private function buildPago(NodeInterface $nodePago): Pago |
54
|
13 |
|
{ |
55
|
|
|
$sumMonto = new Decimal('0'); |
56
|
13 |
|
$impuestos = new Impuestos(); |
57
|
13 |
|
foreach ($nodePago->searchNodes('pago20:DoctoRelacionado') as $nodeDoctoRelacionado) { |
58
|
13 |
|
$doctoRelacionado = $this->buildDoctoRelacionado($nodeDoctoRelacionado); |
59
|
13 |
|
$sumMonto = $sumMonto->sum($doctoRelacionado->getImpPagado()); |
60
|
13 |
|
$impuestos = $impuestos->aggregate($doctoRelacionado->getImpuestos()); |
61
|
13 |
|
} |
62
|
13 |
|
$montoMinimo = $sumMonto->truncate($this->currencies->get($nodePago['MonedaP'])); |
63
|
13 |
|
$monto = (isset($nodePago['Monto'])) ? new Decimal($nodePago['Monto']) : $montoMinimo; |
64
|
13 |
|
$impuestos = $impuestos->round($this->paymentTaxesPrecision); |
65
|
|
|
$tipoCambioP = new Decimal($nodePago['TipoCambioP']); |
66
|
13 |
|
return new Pago($monto, $montoMinimo, $tipoCambioP, $impuestos); |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
private function buildDoctoRelacionado(NodeInterface $nodeDoctoRelacionado): DoctoRelacionado |
70
|
13 |
|
{ |
71
|
|
|
$equivalenciaDr = new Decimal($nodeDoctoRelacionado['EquivalenciaDR']); |
72
|
13 |
|
|
73
|
13 |
|
$impPagado = new Decimal($nodeDoctoRelacionado['ImpPagado']); |
74
|
12 |
|
$impPagado = $impPagado->divide($equivalenciaDr); |
75
|
12 |
|
|
76
|
12 |
|
$traslados = $this->processImpuestosTraslados( |
77
|
12 |
|
$equivalenciaDr, |
78
|
12 |
|
$nodeDoctoRelacionado->searchNodes('pago20:ImpuestosDR', 'pago20:TrasladosDR', 'pago20:TrasladoDR') |
79
|
12 |
|
); |
80
|
12 |
|
$retenciones = $this->processImpuestosRetenciones( |
81
|
12 |
|
$equivalenciaDr, |
82
|
12 |
|
$nodeDoctoRelacionado->searchNodes('pago20:ImpuestosDR', 'pago20:RetencionesDR', 'pago20:RetencionDR') |
83
|
12 |
|
); |
84
|
|
|
$impuestos = new Impuestos(...$traslados, ...$retenciones); |
85
|
13 |
|
|
86
|
|
|
return new DoctoRelacionado($impPagado, $impuestos); |
87
|
|
|
} |
88
|
|
|
|
89
|
13 |
|
/** @return list<Impuesto> */ |
|
|
|
|
90
|
|
|
private function processImpuestosTraslados(Decimal $equivalenciaDr, Nodes $nodeImpuestos): array |
91
|
13 |
|
{ |
92
|
13 |
|
$impuestos = []; |
93
|
3 |
|
foreach ($nodeImpuestos as $nodeImpuesto) { |
94
|
3 |
|
$impuesto = new Impuesto( |
95
|
3 |
|
'Traslado', |
96
|
3 |
|
$nodeImpuesto['ImpuestoDR'], |
97
|
3 |
|
$nodeImpuesto['TipoFactorDR'], |
98
|
3 |
|
$nodeImpuesto['TasaOCuotaDR'], |
99
|
3 |
|
new Decimal($nodeImpuesto['BaseDR']), |
100
|
3 |
|
new Decimal($nodeImpuesto['ImporteDR']) |
101
|
3 |
|
); |
102
|
3 |
|
$impuesto = $impuesto->divide($equivalenciaDr); |
103
|
|
|
$impuestos[] = $impuesto; |
104
|
13 |
|
} |
105
|
|
|
return $impuestos; |
|
|
|
|
106
|
|
|
} |
107
|
|
|
|
108
|
13 |
|
/** @return list<Impuesto> */ |
109
|
|
|
private function processImpuestosRetenciones(Decimal $equivalenciaDr, Nodes $nodeImpuestos): array |
110
|
13 |
|
{ |
111
|
13 |
|
$impuestos = []; |
112
|
13 |
|
foreach ($nodeImpuestos as $nodeImpuesto) { |
113
|
13 |
|
$impuesto = new Impuesto( |
114
|
13 |
|
'Retencion', |
115
|
13 |
|
$nodeImpuesto['ImpuestoDR'], |
116
|
|
|
'', |
117
|
13 |
|
'', |
118
|
|
|
new Decimal('0'), |
119
|
13 |
|
new Decimal($nodeImpuesto['ImporteDR']) |
120
|
13 |
|
); |
121
|
13 |
|
$impuesto = $impuesto->divide($equivalenciaDr); |
122
|
13 |
|
$impuestos[] = $impuesto; |
123
|
13 |
|
} |
124
|
13 |
|
return $impuestos; |
|
|
|
|
125
|
13 |
|
} |
126
|
|
|
|
127
|
13 |
|
/** @param Pago[] $pagos */ |
128
|
13 |
|
private function buildTotales(array $pagos): Totales |
129
|
13 |
|
{ |
130
|
13 |
|
$total = new Decimal('0'); |
131
|
13 |
|
$impuestos = new Impuestos(); |
132
|
13 |
|
foreach ($pagos as $pago) { |
133
|
13 |
|
$tipoCambioP = $pago->getTipoCambioP(); |
134
|
13 |
|
$impuestos = $impuestos->aggregate($pago->getImpuestos()->multiply($tipoCambioP)); |
135
|
13 |
|
$total = $total->sum($pago->getMonto()->multiply($tipoCambioP)); |
136
|
13 |
|
} |
137
|
13 |
|
$impuestos = $impuestos->round(2); // MXN |
138
|
13 |
|
|
139
|
13 |
|
$retencionIva = $impuestos->find('Retencion', '002'); |
140
|
|
|
$retencionIsr = $impuestos->find('Retencion', '001'); |
141
|
|
|
$retencionIeps = $impuestos->find('Retencion', '003'); |
142
|
|
|
$trasladoIva16 = $impuestos->find('Traslado', '002', 'Tasa', '0.160000'); |
143
|
|
|
$trasladoIva08 = $impuestos->find('Traslado', '002', 'Tasa', '0.080000'); |
144
|
|
|
$trasladoIva00 = $impuestos->find('Traslado', '002', 'Tasa', '0.000000'); |
145
|
|
|
$trasladoIvaEx = $impuestos->find('Traslado', '002', 'Exento'); |
146
|
|
|
|
147
|
|
|
return new Totales( |
148
|
|
|
$retencionIva ? $retencionIva->getImporte() : null, |
149
|
|
|
$retencionIsr ? $retencionIsr->getImporte() : null, |
150
|
|
|
$retencionIeps ? $retencionIeps->getImporte() : null, |
151
|
|
|
$trasladoIva16 ? $trasladoIva16->getBase() : null, |
152
|
|
|
$trasladoIva16 ? $trasladoIva16->getImporte() : null, |
153
|
|
|
$trasladoIva08 ? $trasladoIva08->getBase() : null, |
154
|
|
|
$trasladoIva08 ? $trasladoIva08->getImporte() : null, |
155
|
|
|
$trasladoIva00 ? $trasladoIva00->getBase() : null, |
156
|
|
|
$trasladoIva00 ? $trasladoIva00->getImporte() : null, |
157
|
|
|
$trasladoIvaEx ? $trasladoIvaEx->getBase() : null, |
158
|
|
|
$total->round(2) // MXN |
159
|
|
|
); |
160
|
|
|
} |
161
|
|
|
} |
162
|
|
|
|
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths