DispatcherWorker   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 351
Duplicated Lines 0 %

Test Coverage

Coverage 40.46%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 141
c 1
b 0
f 0
dl 0
loc 351
ccs 70
cts 173
cp 0.4046
rs 8.96
wmc 43

15 Methods

Rating   Name   Duplication   Size   Complexity  
A normalize() 0 12 1
A loadXml() 0 8 1
A validate() 0 3 1
A __construct() 0 12 1
A getResumen() 0 23 4
A sign() 0 32 2
A ensureMandatario() 0 10 3
A ensureXmlDocument() 0 59 5
A ensureReceptor() 0 10 3
A ensureDocuments() 0 16 4
B ensureCaratula() 0 43 7
A ensureSobreEnvio() 0 11 3
A validateSchema() 0 22 3
A ensureEmisor() 0 10 3
A validateSignature() 0 9 2

How to fix   Complexity   

Complex Class

Complex classes like DispatcherWorker often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DispatcherWorker, and based on these observations, apply Extract Interface, too.

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 por
11
 * la Fundación para el Software Libre, ya sea la versión 3 de la Licencia, o
12
 * (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\Package\Billing\Component\Document\Worker;
26
27
use DateTime;
28
use Derafu\Lib\Core\Foundation\Abstract\AbstractWorker;
29
use Derafu\Lib\Core\Package\Prime\Component\Signature\Contract\SignatureComponentInterface;
30
use Derafu\Lib\Core\Package\Prime\Component\Xml\Contract\XmlComponentInterface;
31
use Derafu\Lib\Core\Package\Prime\Component\Xml\Contract\XmlInterface;
32
use Derafu\Lib\Core\Package\Prime\Component\Xml\Entity\Xml as XmlDocument;
33
use libredte\lib\Core\Package\Billing\Component\Document\Contract\DispatcherWorkerInterface;
34
use libredte\lib\Core\Package\Billing\Component\Document\Contract\DocumentBagManagerWorkerInterface;
35
use libredte\lib\Core\Package\Billing\Component\Document\Contract\DocumentEnvelopeInterface;
36
use libredte\lib\Core\Package\Billing\Component\Document\Entity\SobreEnvio;
37
use libredte\lib\Core\Package\Billing\Component\Document\Exception\DispatcherException;
38
use libredte\lib\Core\Package\Billing\Component\Document\Support\DocumentEnvelope;
39
use libredte\lib\Core\Package\Billing\Component\TradingParties\Entity\Mandatario;
40
41
/**
42
 * Clase para la gestión de sobres de documentos (para envíos/transferencias).
43
 */
44
class DispatcherWorker extends AbstractWorker implements DispatcherWorkerInterface
45
{
46 9
    public function __construct(
47
        private XmlComponentInterface $xmlComponent,
48
        private SignatureComponentInterface $signatureComponent,
49
        private DocumentBagManagerWorkerInterface $documentBagManagerWorker,
50
        iterable $jobs = [],
51
        iterable $handlers = [],
52
        iterable $strategies = []
53
    ) {
54 9
        parent::__construct(
55 9
            jobs: $jobs,
56 9
            handlers: $handlers,
57 9
            strategies: $strategies
58 9
        );
59
    }
60
61
    /**
62
     * {@inheritDoc}
63
     */
64 9
    public function normalize(
65
        DocumentEnvelopeInterface $envelope
66
    ): DocumentEnvelopeInterface {
67 9
        $this->ensureSobreEnvio($envelope);
68 9
        $this->ensureDocuments($envelope);
69 9
        $this->ensureEmisor($envelope);
70 9
        $this->ensureMandatario($envelope);
71 9
        $this->ensureReceptor($envelope);
72 9
        $this->ensureCaratula($envelope);
73 9
        $this->ensureXmlDocument($envelope);
74
75 9
        return $envelope;
76
    }
77
78
    /**
79
     * {@inheritDoc}
80
     */
81 9
    public function loadXml(string $xml): DocumentEnvelopeInterface
82
    {
83 9
        $xmlDocument = new XmlDocument();
84 9
        $xmlDocument->loadXML($xml);
85 9
        $envelope = new DocumentEnvelope();
86 9
        $envelope->setXmlDocument($xmlDocument);
87
88 9
        return $this->normalize($envelope);
89
    }
90
91
    /**
92
     * {@inheritDoc}
93
     */
94
    public function validate(
95
        DocumentEnvelopeInterface|XmlInterface|string $source
96
    ): void {
97
        // TODO: Agregar validaciones del sobre.
98
    }
99
100
    /**
101
     * {@inheritDoc}
102
     */
103 1
    public function validateSchema(
104
        DocumentEnvelopeInterface|XmlInterface|string $source
105
    ): void {
106
        // Obtener el documento XML.
107 1
        if ($source instanceof DocumentEnvelopeInterface) {
108 1
            $xmlDocument = $source->getXmlDocument();
109
        } elseif ($source instanceof XmlInterface) {
110
            $xmlDocument = $source;
111
        } else {
112
            $xmlDocument = new XmlDocument();
113
            $xmlDocument->loadXml($source);
114
        }
115
116
        // Validar esquema del sobre de documentos (EnvioDTE y EnvioBOLETA).
117 1
        $schema = sprintf(
118 1
            '%s/resources/schemas/%s',
119 1
            dirname(__DIR__, 6),
120 1
            $xmlDocument->getSchema()
121 1
        );
122 1
        $this->xmlComponent->getValidatorWorker()->validateSchema(
123 1
            $xmlDocument,
0 ignored issues
show
Bug introduced by
It seems like $xmlDocument can also be of type null; however, parameter $xml of Derafu\Lib\Core\Package\...rface::validateSchema() does only seem to accept Derafu\Lib\Core\Package\...l\Contract\XmlInterface, maybe add an additional type check? ( Ignorable by Annotation )

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

123
            /** @scrutinizer ignore-type */ $xmlDocument,
Loading history...
124 1
            $schema
125 1
        );
126
    }
127
128
    /**
129
     * {@inheritDoc}
130
     */
131 1
    public function validateSignature(
132
        DocumentEnvelopeInterface|XmlInterface|string $source
133
    ): void {
134 1
        $xml = $source instanceof DocumentEnvelopeInterface
135 1
            ? $source->getXmlDocument()
136
            : $source
137 1
        ;
138
139 1
        $this->signatureComponent->getValidatorWorker()->validateXml($xml);
0 ignored issues
show
Bug introduced by
It seems like $xml can also be of type null; however, parameter $xml of Derafu\Lib\Core\Package\...nterface::validateXml() does only seem to accept Derafu\Lib\Core\Package\...act\XmlInterface|string, maybe add an additional type check? ( Ignorable by Annotation )

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

139
        $this->signatureComponent->getValidatorWorker()->validateXml(/** @scrutinizer ignore-type */ $xml);
Loading history...
140
    }
141
142
    /**
143
     * Se asegura que exista un arreglo con las bolsas de los documentos
144
     * tributarios si no está definida y existe un documento XML en el sobre.
145
     *
146
     * @param DocumentEnvelopeInterface $envelope
147
     * @return void
148
     */
149 9
    protected function ensureDocuments(
150
        DocumentEnvelopeInterface $envelope
151
    ): void {
152
        // Verificar si es necesario, y se puede, asignar.
153 9
        if ($envelope->getDocuments() !== null || !$envelope->getSobreEnvio()) {
154
            return;
155
        }
156
157
        // Buscar documentos (DTE) en el XML del sobre y crear la bolsa de
158
        // cada documento para agregarla al listado de documentos que el sobre
159
        // gestiona.
160 9
        foreach ($envelope->getSobreEnvio()->getXmlDocumentos() as $xml) {
161 9
            $xmlDocument = new XmlDocument();
162 9
            $xmlDocument->loadXml($xml);
163 9
            $bag = $this->documentBagManagerWorker->create($xmlDocument);
164 9
            $envelope->addDocument($bag);
165
        }
166
    }
167
168 9
    protected function ensureEmisor(DocumentEnvelopeInterface $envelope): void
169
    {
170
        // Verificar si es necesario, y se puede, asignar.
171 9
        if ($envelope->getEmisor() || !$envelope->getDocuments()) {
172
            return;
173
        }
174
175
        // Asignar como emisor del sobre el emisor del primer documento.
176 9
        $emisor = $envelope->getDocuments()[0]->getEmisor();
177 9
        $envelope->setEmisor($emisor);
178
    }
179
180 9
    protected function ensureMandatario(DocumentEnvelopeInterface $envelope): void
181
    {
182
        // Verificar si es necesario, y se puede, asignar.
183 9
        if ($envelope->getMandatario() || !$envelope->getCertificate()) {
184 9
            return;
185
        }
186
187
        // Asignar como mandatario del sobre el usuario del certificado digital.
188
        $mandatario = new Mandatario($envelope->getCertificate()->getId());
189
        $envelope->setMandatario($mandatario);
190
    }
191
192 9
    protected function ensureReceptor(DocumentEnvelopeInterface $envelope): void
193
    {
194
        // Verificar si es necesario, y se puede, asignar.
195 9
        if ($envelope->getReceptor() || !$envelope->getDocuments()) {
196
            return;
197
        }
198
199
        // Asignar como receptor del sobre el receptor del primer documento.
200 9
        $receptor = $envelope->getDocuments()[0]->getReceptor();
201 9
        $envelope->setReceptor($receptor);
202
    }
203
204 9
    protected function ensureCaratula(DocumentEnvelopeInterface $envelope): void
205
    {
206
        // Verificar si es necesario, y se puede, asignar.
207
        if (
208 9
            $envelope->getCaratula()
209 9
            || !$envelope->getDocuments()
210 9
            || !$envelope->getEmisor()
211 9
            || !$envelope->getMandatario()
212 9
            || !$envelope->getReceptor()
213
        ) {
214 9
            return;
215
        }
216
217
        // Si se agregaron más tipos de documentos que los permitidos error.
218
        $SubTotDTE = $this->getResumen($envelope);
219
220
        $maximoTiposDocumentos = $envelope->getTipoSobre()->getMaximoTiposDocumentos();
221
        if (isset($SubTotDTE[$maximoTiposDocumentos])) {
222
            throw new DispatcherException(
223
                'Se agregaron más tipos de documentos de los que son permitidos en el sobre (%d).',
224
                $maximoTiposDocumentos
225
            );
226
        }
227
228
        // Timestamp de la firma del sobre que se generará.
229
        $timestamp = date('Y-m-d\TH:i:s');
230
231
        // Crear datos de la carátula.
232
        $caratula = [
233
            '@attributes' => [
234
                'version' => '1.0',
235
            ],
236
            'RutEmisor' => $envelope->getEmisor()->getRut(),
237
            'RutEnvia' => $envelope->getMandatario()->getRun(),
238
            'RutReceptor' => $envelope->getReceptor()->getRut(),
239
            'FchResol' => $envelope->getEmisor()->getAutorizacionDte()->getFechaResolucion(),
240
            'NroResol' => $envelope->getEmisor()->getAutorizacionDte()->getNumeroResolucion(),
241
            'TmstFirmaEnv' => $timestamp,
242
            'SubTotDTE' => $SubTotDTE,
243
        ];
244
245
        // Asignar carátula al sobre.
246
        $envelope->setCaratula($caratula);
247
    }
248
249 9
    protected function ensureXmlDocument(
250
        DocumentEnvelopeInterface $envelope
251
    ): void {
252
        // Verificar si es necesario, y se puede, asignar.
253 9
        if ($envelope->getXmlDocument() || !$envelope->getDocuments()) {
254 9
            return;
255
        }
256
257
        // Generar la estructura base del documento XML del sobre de documentos.
258
        $data = [
259
            $envelope->getTipoSobre()->getTagXml() => [
260
                '@attributes' => [
261
                    'xmlns' => 'http://www.sii.cl/SiiDte',
262
                    'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
263
                    'xsi:schemaLocation' => 'http://www.sii.cl/SiiDte '
264
                        . $envelope->getTipoSobre()->getSchema()
265
                    ,
266
                    'version' => '1.0',
267
                ],
268
                'SetDTE' => [
269
                    '@attributes' => [
270
                        'ID' => 'LibreDTE_SetDoc',
271
                    ],
272
                    'Caratula' => $envelope->getCaratula(),
273
                    'DTE' => '',
274
                ],
275
            ],
276
        ];
277
278
        // Generar el XML de los documentos que se deberán incorporar al sobre.
279
        $documents = [];
280
        foreach ($envelope->getDocuments() as $document) {
281
            $documents[] = trim(str_replace(
282
                [
283
                    '<?xml version="1.0" encoding="ISO-8859-1"?>',
284
                    '<?xml version="1.0"?>',
285
                ],
286
                '',
287
                $document->getDocument()->getXml()
288
            ));
289
        }
290
291
        // Crear el documento XML del sobre (estructura base, sin DTE).
292
        $xmlDocument = $this->xmlComponent->getEncoderWorker()->encode($data);
293
294
        // Agregar los DTE dentro de SetDTE reemplazando el tag vacio DTE.
295
        $xmlBaseSobre = $xmlDocument->saveXML();
296
        $xmlSobre = str_replace('<DTE/>', implode("\n", $documents), $xmlBaseSobre);
297
298
        // Reemplazar el documento XML del sobre del envío con el string XML que
299
        // contiene ahora los documentos del envío.
300
        $xmlDocument->loadXML($xmlSobre);
301
302
        // Asignar el documento XML al sobre de documentos.
303
        $envelope->setXmlDocument($xmlDocument);
304
305
        // Si existe un certificado en el sobre de documento se firma el sobre.
306
        if ($envelope->getCertificate()) {
307
            $this->sign($envelope);
308
        }
309
    }
310
311 9
    protected function ensureSobreEnvio(
312
        DocumentEnvelopeInterface $envelope
313
    ): void {
314
        // Verificar si es necesario, y se puede, asignar.
315 9
        if ($envelope->getSobreEnvio() || !$envelope->getXmlDocument()) {
316
            return;
317
        }
318
319
        // Asignar la entidad del sobre del envío.
320 9
        $sobreEnvio = new SobreEnvio($envelope->getXmlDocument());
321 9
        $envelope->setSobreEnvio($sobreEnvio);
322
    }
323
324
    /**
325
     * Obtiene el resumen de los documentos que hay en el sobre.
326
     *
327
     * Esto se usa para para generar los tags del XML `SubTotDTE`.
328
     *
329
     * @param DocumentEnvelopeInterface $envelope
330
     * @return array Arreglo con el resumen de documentos por tipo.
331
     */
332
    protected function getResumen(DocumentEnvelopeInterface $envelope): array
333
    {
334
        // Contar los documentos por cada tipo.
335
        $subtotales = [];
336
        foreach ($envelope->getDocuments() as $document) {
337
            $codigo = $document->getTipoDocumento()->getCodigo();
338
            if (!isset($subtotales[$codigo])) {
339
                $subtotales[$codigo] = 0;
340
            }
341
            $subtotales[$codigo]++;
342
        }
343
344
        // Crear el resumen con los datos en el formato del tag `SubTotDTE`.
345
        $SubTotDTE = [];
346
        foreach ($subtotales as $tipo => $subtotal) {
347
            $SubTotDTE[] = [
348
                'TpoDTE' => $tipo,
349
                'NroDTE' => $subtotal,
350
            ];
351
        }
352
353
        // Entregar el resumen.
354
        return $SubTotDTE;
355
    }
356
357
    /**
358
     * Firma el sobre de documentos.
359
     *
360
     * @param DocumentEnvelopeInterface $envelope
361
     * @return void
362
     */
363
    protected function sign(DocumentEnvelopeInterface $envelope): void
364
    {
365
        // El certificado digital para realizar la firma.
366
        $certificate = $envelope->getCertificate();
367
368
        // Asignar marca de tiempo si no se pasó una.
369
        $timestamp = $envelope->getCaratula()['TmstFirmaEnv'];
370
371
        // Corroborar que el certificado esté vigente según el timestamp usado.
372
        if (!$certificate->isActive($timestamp)) {
373
            throw new DispatcherException(sprintf(
374
                'El certificado digital de %s no está vigente en el tiempo %s, su rango de vigencia es del %s al %s.',
375
                $certificate->getID(),
376
                (new DateTime($timestamp))->format('d/m/Y H:i'),
377
                (new DateTime($certificate->getFrom()))->format('d/m/Y H:i'),
378
                (new DateTime($certificate->getTo()))->format('d/m/Y H:i'),
379
            ));
380
        }
381
382
        // Obtener el documento XML a firmar.
383
        $xmlDocument = $envelope->getXmlDocument();
384
385
        // Firmar el documento XML del sobre y retornar el XML firmado.
386
        $xmlSigned = $this->signatureComponent->getGeneratorWorker()->signXml(
387
            $xmlDocument,
0 ignored issues
show
Bug introduced by
It seems like $xmlDocument can also be of type null; however, parameter $xml of Derafu\Lib\Core\Package\...kerInterface::signXml() does only seem to accept Derafu\Lib\Core\Package\...act\XmlInterface|string, maybe add an additional type check? ( Ignorable by Annotation )

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

387
            /** @scrutinizer ignore-type */ $xmlDocument,
Loading history...
388
            $certificate,
0 ignored issues
show
Bug introduced by
It seems like $certificate can also be of type null; however, parameter $certificate of Derafu\Lib\Core\Package\...kerInterface::signXml() does only seem to accept Derafu\Lib\Core\Package\...ct\CertificateInterface, maybe add an additional type check? ( Ignorable by Annotation )

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

388
            /** @scrutinizer ignore-type */ $certificate,
Loading history...
389
            $envelope->getId()
390
        );
391
392
        // Cargar XML en el documento y luego en la bolsa.
393
        $xmlDocument->loadXml($xmlSigned);
394
        $envelope->setXmlDocument($xmlDocument);
395
    }
396
}
397