Test Failed
Push — master ( 59e870...659f9a )
by Esteban De La Fuente
04:33
created

DispatcherWorker::ensureCaratula()   B

Complexity

Conditions 7
Paths 3

Size

Total Lines 43
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 27.6718

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 25
nc 3
nop 1
dl 0
loc 43
ccs 7
cts 28
cp 0.25
crap 27.6718
rs 8.5866
c 1
b 0
f 0
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 5
    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 5
        parent::__construct(
55 5
            jobs: $jobs,
56 5
            handlers: $handlers,
57 5
            strategies: $strategies
58 5
        );
59
    }
60
61
    /**
62
     * {@inheritdoc}
63
     */
64 5
    public function normalize(
65
        DocumentEnvelopeInterface $envelope
66
    ): DocumentEnvelopeInterface {
67 5
        $this->ensureSobreEnvio($envelope);
68 5
        $this->ensureDocuments($envelope);
69 5
        $this->ensureEmisor($envelope);
70 5
        $this->ensureMandatario($envelope);
71 5
        $this->ensureReceptor($envelope);
72 5
        $this->ensureCaratula($envelope);
73 5
        $this->ensureXmlDocument($envelope);
74
75 5
        return $envelope;
76
    }
77
78
    /**
79
     * {@inheritdoc}
80
     */
81 5
    public function loadXml(string $xml): DocumentEnvelopeInterface
82
    {
83 5
        $xmlDocument = new XmlDocument();
84 5
        $xmlDocument->loadXML($xml);
85 5
        $envelope = new DocumentEnvelope();
86 5
        $envelope->setXmlDocument($xmlDocument);
87
88 5
        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
    {
107
        // Obtener el documento XML.
108 1
        if ($source instanceof DocumentEnvelopeInterface) {
109 1
            $xmlDocument = $source->getXmlDocument();
110
        } elseif ($source instanceof XmlInterface) {
111
            $xmlDocument = $source;
112
        } else {
113
            $xmlDocument = new XmlDocument();
114
            $xmlDocument->loadXml($source);
115
        }
116
117
        // Validar esquema del sobre de documentos (EnvioDTE y EnvioBOLETA).
118 1
        $schema = sprintf(
119 1
            '%s/resources/schemas/%s',
120 1
            dirname(__DIR__, 6),
121 1
            $xmlDocument->getSchema()
122 1
        );
123 1
        $this->xmlComponent->getValidatorWorker()->validateSchema(
124 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

124
            /** @scrutinizer ignore-type */ $xmlDocument,
Loading history...
125 1
            $schema
126 1
        );
127
    }
128
129
    /**
130
     * {@inheritdoc}
131
     */
132
    public function validateSignature(
133
        DocumentEnvelopeInterface|XmlInterface|string $source
134
    ): void {
135
        $xml = $source instanceof DocumentEnvelopeInterface
136
            ? $source->getXmlDocument()
137
            : $source
138
        ;
139
140
        $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

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

388
            /** @scrutinizer ignore-type */ $xmlDocument,
Loading history...
389
            $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

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