Passed
Push — master ( c02ff8...8cf6c5 )
by Esteban De La Fuente
06:05
created

DocumentoNormalizer   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 353
Duplicated Lines 0 %

Test Coverage

Coverage 89.64%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 178
c 1
b 0
f 0
dl 0
loc 353
ccs 173
cts 193
cp 0.8964
rs 9.52
wmc 36

6 Methods

Rating   Name   Duplication   Size   Complexity  
A applyDocumentNormalization() 0 7 2
A __construct() 0 6 1
C applyInitialNormalization() 0 124 9
D applyFinalNormalization() 0 118 21
A applyProNormalization() 0 11 2
A normalize() 0 8 1
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * LibreDTE: Biblioteca PHP (Núcleo).
7
 * Copyright (C) LibreDTE <https://www.libredte.cl>
8
 *
9
 * Este programa es software libre: usted puede redistribuirlo y/o modificarlo
10
 * bajo los términos de la Licencia Pública General Affero de GNU publicada
11
 * por la Fundación para el Software Libre, ya sea la versión 3 de la Licencia,
12
 * o (a su elección) cualquier versión posterior de la misma.
13
 *
14
 * Este programa se distribuye con la esperanza de que sea útil, pero SIN
15
 * GARANTÍA ALGUNA; ni siquiera la garantía implícita MERCANTIL o de APTITUD
16
 * PARA UN PROPÓSITO DETERMINADO. Consulte los detalles de la Licencia Pública
17
 * General Affero de GNU para obtener una información más detallada.
18
 *
19
 * Debería haber recibido una copia de la Licencia Pública General Affero de
20
 * GNU junto a este programa.
21
 *
22
 * En caso contrario, consulte <http://www.gnu.org/licenses/agpl.html>.
23
 */
24
25
namespace libredte\lib\Core\Sii\Dte\Documento\Normalization;
26
27
use libredte\lib\Core\Helper\Arr;
28
use libredte\lib\Core\Sii\Dte\Documento\DocumentoTipo;
29
30
/**
31
 * Clase que maneja la normalización de los datos de un documento.
32
 *
33
 * En el contexto de los documentos tributarios y LibreDTE, el proceso de
34
 * normalización realiza 2 cosas en conjunto:
35
 *
36
 *   - Estandariza la estructura de los datos.
37
 *   - Realiza los cálculos de campos que no se hayan especificado y se puedan
38
 *     calcular a partir de los datos de otros campos.
39
 *
40
 * Esta clase incluye los métodos de normalización generales (initial, final y
41
 * extra). Otros métodos son provistos como traits y deberán ser incluídos en
42
 * cada documento que necesite aplicar dichas normalizaciones auxiliares.
43
 */
44
class DocumentoNormalizer
45
{
46
    /**
47
     * Tipo de documento que normalizará esta instancia del normalizador.
48
     *
49
     * @var DocumentoTipo
50
     */
51
    private DocumentoTipo $tipoDocumento;
52
53
    /**
54
     * Undocumented variable
55
     *
56
     * @var ?callable
57
     */
58
    private $documentNormalizationCallback;
59
60
    /**
61
     * Constructor de la clase de normalización.
62
     *
63
     * @param DocumentoTipo $tipoDocumento Tipo de documento a normalizar.
64
     * @param ?callable $documentNormalizationCallback Callback al método del
65
     * "builder" del documento que aplicará las reglas específicas según el
66
     * tipo de documento.
67
     */
68 116
    public function __construct(
69
        DocumentoTipo $tipoDocumento,
70
        callable $documentNormalizationCallback = null
71
    ) {
72 116
        $this->tipoDocumento = $tipoDocumento;
73 116
        $this->documentNormalizationCallback = $documentNormalizationCallback;
74
    }
75
76
    /**
77
     * Ejecuta la normalización de los datos.
78
     *
79
     * @param array $data Arreglo con los datos del documento a normalizar.
80
     * @return array Arreglo con los datos normalizados.
81
     */
82 114
    public function normalize(array $data): array
83
    {
84 114
        $data = $this->applyInitialNormalization($data);
85 114
        $data = $this->applyDocumentNormalization($data);
86 114
        $data = $this->applyFinalNormalization($data);
87 114
        $data = $this->applyProNormalization($data);
88
89 114
        return $data;
90
    }
91
92
    /**
93
     * Aplica la normalización inicial de los datos de un documento tributario
94
     * electrónico.
95
     *
96
     * Esta normalización se ejecuta antes de ejecutar la normalización
97
     * específica del tipo de documento tributario.
98
     *
99
     * @param array $data Arreglo con los datos del documento a normalizar.
100
     * @return array Arreglo con los datos normalizados.
101
     */
102 114
    private function applyInitialNormalization(array $data): array
103
    {
104
        // Completar con campos por defecto.
105 114
        $data = Arr::mergeRecursiveDistinct([
106 114
            'Encabezado' => [
107 114
                'IdDoc' => [
108 114
                    'TipoDTE' => false,
109 114
                    'Folio' => false,
110 114
                    'FchEmis' => date('Y-m-d'),
111 114
                    'IndNoRebaja' => false,
112 114
                    'TipoDespacho' => false,
113 114
                    'IndTraslado' => false,
114 114
                    'TpoImpresion' => false,
115 114
                    'IndServicio' => $this->tipoDocumento->getDefaultIndServicio(),
116 114
                    'MntBruto' => false,
117 114
                    'TpoTranCompra' => false,
118 114
                    'TpoTranVenta' => false,
119 114
                    'FmaPago' => false,
120 114
                    'FmaPagExp' => false,
121 114
                    'MntCancel' => false,
122 114
                    'SaldoInsol' => false,
123 114
                    'FchCancel' => false,
124 114
                    'MntPagos' => false,
125 114
                    'PeriodoDesde' => false,
126 114
                    'PeriodoHasta' => false,
127 114
                    'MedioPago' => false,
128 114
                    'TpoCtaPago' => false,
129 114
                    'NumCtaPago' => false,
130 114
                    'BcoPago' => false,
131 114
                    'TermPagoCdg' => false,
132 114
                    'TermPagoGlosa' => false,
133 114
                    'TermPagoDias' => false,
134 114
                    'FchVenc' => false,
135 114
                ],
136 114
                'Emisor' => [
137 114
                    'RUTEmisor' => false,
138 114
                    'RznSoc' => false,
139 114
                    'GiroEmis' => false,
140 114
                    'Telefono' => false,
141 114
                    'CorreoEmisor' => false,
142 114
                    'Acteco' => false,
143 114
                    'GuiaExport' => false,
144 114
                    'Sucursal' => false,
145 114
                    'CdgSIISucur' => false,
146 114
                    'DirOrigen' => false,
147 114
                    'CmnaOrigen' => false,
148 114
                    'CiudadOrigen' => false,
149 114
                    'CdgVendedor' => false,
150 114
                    'IdAdicEmisor' => false,
151 114
                ],
152 114
                'Receptor' => [
153 114
                    'RUTRecep' => false,
154 114
                    'CdgIntRecep' => false,
155 114
                    'RznSocRecep' => false,
156 114
                    'Extranjero' => false,
157 114
                    'GiroRecep' => false,
158 114
                    'Contacto' => false,
159 114
                    'CorreoRecep' => false,
160 114
                    'DirRecep' => false,
161 114
                    'CmnaRecep' => false,
162 114
                    'CiudadRecep' => false,
163 114
                    'DirPostal' => false,
164 114
                    'CmnaPostal' => false,
165 114
                    'CiudadPostal' => false,
166 114
                ],
167 114
                'Totales' => [
168 114
                    'TpoMoneda' => false,
169 114
                ],
170 114
            ],
171 114
            'Detalle' => false,
172 114
            'SubTotInfo' => false,
173 114
            'DscRcgGlobal' => false,
174 114
            'Referencia' => false,
175 114
            'Comisiones' => false,
176 114
        ], $data);
177
178
        // Si existe descuento o recargo global se normalizan.
179 114
        if (!empty($data['DscRcgGlobal'])) {
180 14
            if (!isset($data['DscRcgGlobal'][0])) {
181 14
                $data['DscRcgGlobal'] = [
182 14
                    $data['DscRcgGlobal'],
183 14
                ];
184
            }
185 14
            $NroLinDR = 1;
186 14
            foreach ($data['DscRcgGlobal'] as &$dr) {
187 14
                $dr = array_merge([
188 14
                    'NroLinDR' => $NroLinDR++,
189 14
                ], $dr);
190
            }
191
        }
192
193
        // Si existe una o más referencias se normalizan.
194 114
        if (!empty($data['Referencia'])) {
195 37
            if (!isset($data['Referencia'][0])) {
196
                $data['Referencia'] = [
197
                    $data['Referencia'],
198
                ];
199
            }
200 37
            $NroLinRef = 1;
201 37
            foreach ($data['Referencia'] as &$r) {
202 37
                $r = array_merge([
203 37
                    'NroLinRef' => $NroLinRef++,
204 37
                    'TpoDocRef' => false,
205 37
                    'IndGlobal' => false,
206 37
                    'FolioRef' => false,
207 37
                    'RUTOtr' => false,
208 37
                    'FchRef' => date('Y-m-d'),
209 37
                    'CodRef' => false,
210 37
                    'RazonRef' => false,
211 37
                ], $r);
212
            }
213
        }
214
215
        // Verificar que exista TpoTranVenta.
216
        if (
217 114
            $this->tipoDocumento->requiereTpoTranVenta()
218 114
            && empty($data['Encabezado']['IdDoc']['TpoTranVenta'])
219
        ) {
220
            // Se asigna "Ventas del giro" como valor por defecto.
221 81
            $data['Encabezado']['IdDoc']['TpoTranVenta'] = 1;
222
        }
223
224
        // Entregar los datos normalizados.
225 114
        return $data;
226
    }
227
228
    /**
229
     * Aplica la normalización de los datos específica de un tipo de documento
230
     * tributario electrónico.
231
     *
232
     * Esta normalización se ejecuta utilizando el callback provisto al
233
     * instanciar este objeto de normalización.
234
     *
235
     * @param array $data Arreglo con los datos del documento a normalizar.
236
     * @return array Arreglo con los datos normalizados.
237
     */
238 114
    private function applyDocumentNormalization(array $data): array
239
    {
240 114
        if (!isset($this->documentNormalizationCallback)) {
241
            return $data;
242
        }
243
244 114
        return ($this->documentNormalizationCallback)($data);
245
    }
246
247
    /**
248
     * Aplica la normalización final de los datos de un documento tributario
249
     * electrónico.
250
     *
251
     * Esta normalización se ejecuta después de ejecutar la normalización
252
     * específica del tipo de documento tributario.
253
     *
254
     * @param array $data Arreglo con los datos del documento a normalizar.
255
     * @return array Arreglo con los datos normalizados.
256
     */
257 114
    private function applyFinalNormalization(array $data): array
258
    {
259
        // Normalizar montos de pagos programados.
260 114
        if (is_array($data['Encabezado']['IdDoc']['MntPagos'])) {
261
            if (!isset($data['Encabezado']['IdDoc']['MntPagos'][0])) {
262
                $data['Encabezado']['IdDoc']['MntPagos'] = [
263
                    $data['Encabezado']['IdDoc']['MntPagos'],
264
                ];
265
            }
266
            foreach ($data['Encabezado']['IdDoc']['MntPagos'] as &$MntPagos) {
267
                $MntPagos = array_merge([
268
                    'FchPago' => null,
269
                    'MntPago' => null,
270
                    'GlosaPagos' => false,
271
                ], $MntPagos);
272
                if ($MntPagos['MntPago'] === null) {
273
                    $MntPagos['MntPago'] = $data['Encabezado']['Totales']['MntTotal'];
274
                }
275
            }
276
        }
277
278
        // Si existe OtraMoneda se verifican los tipos de cambio y totales.
279 114
        if (!empty($data['Encabezado']['OtraMoneda'])) {
280 5
            if (!isset($data['Encabezado']['OtraMoneda'][0])) {
281 5
                $data['Encabezado']['OtraMoneda'] = [
282 5
                    $data['Encabezado']['OtraMoneda'],
283 5
                ];
284
            }
285 5
            foreach ($data['Encabezado']['OtraMoneda'] as &$OtraMoneda) {
286
                // Colocar campos por defecto.
287 5
                $OtraMoneda = array_merge([
288 5
                    'TpoMoneda' => false,
289 5
                    'TpoCambio' => false,
290 5
                    'MntNetoOtrMnda' => false,
291 5
                    'MntExeOtrMnda' => false,
292 5
                    'MntFaeCarneOtrMnda' => false,
293 5
                    'MntMargComOtrMnda' => false,
294 5
                    'IVAOtrMnda' => false,
295 5
                    'ImpRetOtrMnda' => false,
296 5
                    'IVANoRetOtrMnda' => false,
297 5
                    'MntTotOtrMnda' => false,
298 5
                ], $OtraMoneda);
299
300
                // Si no hay tipo de cambio no seguir.
301 5
                if (!isset($OtraMoneda['TpoCambio'])) {
302
                    continue;
303
                }
304
305
                // Buscar si los valores están asignados, si no lo están se
306
                // asignan usando el tipo de cambio que exista para la moneda.
307 5
                foreach (['MntNeto', 'MntExe', 'IVA', 'IVANoRet'] as $monto) {
308
                    if (
309 5
                        empty($OtraMoneda[$monto.'OtrMnda'])
310 5
                        && !empty($data['Encabezado']['Totales'][$monto])
311
                    ) {
312 5
                        $OtraMoneda[$monto.'OtrMnda'] = round(
313 5
                            $data['Encabezado']['Totales'][$monto]
314 5
                                * $OtraMoneda['TpoCambio'],
315 5
                            4
316 5
                        );
317
                    }
318
                }
319
320
                // Calcular MntFaeCarneOtrMnda, MntMargComOtrMnda y
321
                // ImpRetOtrMnda.
322 5
                if (empty($OtraMoneda['MntFaeCarneOtrMnda'])) {
323
                    // TODO: Implementar cálculo de MntFaeCarneOtrMnda.
324 5
                    $OtraMoneda['MntFaeCarneOtrMnda'] = false;
325
                }
326 5
                if (empty($OtraMoneda['MntMargComOtrMnda'])) {
327
                    // TODO: Implementar cálculo de MntMargComOtrMnda.
328 5
                    $OtraMoneda['MntMargComOtrMnda'] = false;
329
                }
330 5
                if (empty($OtraMoneda['ImpRetOtrMnda'])) {
331
                    // TODO: Implementar cálculo de ImpRetOtrMnda.
332 5
                    $OtraMoneda['ImpRetOtrMnda'] = false;
333
                }
334
335
                // Calcular el monto total.
336 5
                if (empty($OtraMoneda['MntTotOtrMnda'])) {
337 5
                    $OtraMoneda['MntTotOtrMnda'] = 0;
338 5
                    $cols = [
339 5
                        'MntNetoOtrMnda',
340 5
                        'MntExeOtrMnda',
341 5
                        'MntFaeCarneOtrMnda',
342 5
                        'MntMargComOtrMnda',
343 5
                        'IVAOtrMnda',
344 5
                        'IVANoRetOtrMnda',
345 5
                    ];
346 5
                    foreach ($cols as $monto) {
347 5
                        if (!empty($OtraMoneda[$monto])) {
348 5
                            $OtraMoneda['MntTotOtrMnda'] += $OtraMoneda[$monto];
349
                        }
350
                    }
351
352
                    // Agregar el total de impuesto retenido de otra moneda.
353 5
                    if (!empty($OtraMoneda['ImpRetOtrMnda'])) {
354
                        // TODO: Agregar el total de impuesto retenido de ImpRetOtrMnda.
355
                    }
356
357
                    // Aproximar el total si es en pesos chilenos.
358 5
                    if ($OtraMoneda['TpoMoneda'] === 'PESO CL') {
359 5
                        $OtraMoneda['MntTotOtrMnda'] = round(
360 5
                            $OtraMoneda['MntTotOtrMnda'],
361 5
                            0
362 5
                        );
363
                    }
364
                }
365
366
                // Si el tipo de cambio es 0, se quita.
367 5
                if ($OtraMoneda['TpoCambio'] == 0) {
368
                    $OtraMoneda['TpoCambio'] = false;
369
                }
370
            }
371
        }
372
373
        // Entregar los datos normalizados.
374 114
        return $data;
375
    }
376
377
    /**
378
     * Normaliza los datos del documento utilizando funcionalidades Pro.
379
     *
380
     * Esta normalización se ejecuta después de ejecutar la normalización final
381
     * del documento tributario (es la última normalización).
382
     *
383
     * @param array $data Arreglo con los datos del documento a normalizar.
384
     * @return array Arreglo con los datos normalizados.
385
     */
386 114
    private function applyProNormalization(array $data): array
387
    {
388
        // Normalizar los datos con las funcionalidades Pro de la biblioteca.
389 114
        $class = '\libredte\lib\Pro\Sii\Dte\Documento\Normalization\DocumentoNormalizer';
390 114
        if (class_exists($class)) {
391
            $normalizer = $class::create($this);
392
            $data = $normalizer->normalize($data);
393
        }
394
395
        // Entregar los datos normalizados.
396 114
        return $data;
397
    }
398
}
399