DispatcherWorker   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 348
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 43
eloc 140
dl 0
loc 348
ccs 0
cts 170
cp 0
rs 8.96
c 1
b 0
f 0

15 Methods

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

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

121
            /** @scrutinizer ignore-type */ $xmlDocument,
Loading history...
122
            $schema
123
        );
124
    }
125
126
    /**
127
     * {@inheritDoc}
128
     */
129
    public function validateSignature(
130
        DocumentEnvelopeInterface|XmlDocumentInterface|string $source
131
    ): void {
132
        $xml = $source instanceof DocumentEnvelopeInterface
133
            ? $source->getXmlDocument()
134
            : $source
135
        ;
136
137
        $this->signatureService->validateXml($xml);
0 ignored issues
show
Bug introduced by
It seems like $xml can also be of type null; however, parameter $xml of Derafu\Signature\Contrac...nterface::validateXml() does only seem to accept Derafu\Xml\Contract\XmlDocumentInterface|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

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

385
            /** @scrutinizer ignore-type */ $xmlDocument,
Loading history...
386
            $certificate,
0 ignored issues
show
Bug introduced by
It seems like $certificate can also be of type null; however, parameter $certificate of Derafu\Signature\Contrac...torInterface::signXml() does only seem to accept Derafu\Certificate\Contract\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

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