Passed
Push — master ( c54296...9f2d90 )
by Esteban De La Fuente
06:57
created

DocumentBag::getTimbre()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
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\Support;
26
27
use Derafu\Lib\Core\Package\Prime\Component\Certificate\Contract\CertificateInterface;
28
use Derafu\Lib\Core\Package\Prime\Component\Xml\Contract\XmlInterface;
29
use Derafu\Lib\Core\Support\Store\Contract\DataContainerInterface;
30
use Derafu\Lib\Core\Support\Store\DataContainer;
31
use libredte\lib\Core\Package\Billing\Component\Document\Contract\DocumentBagInterface;
32
use libredte\lib\Core\Package\Billing\Component\Document\Contract\DocumentInterface;
33
use libredte\lib\Core\Package\Billing\Component\Document\Contract\TipoDocumentoInterface;
34
use libredte\lib\Core\Package\Billing\Component\Document\Exception\DocumentException;
35
use libredte\lib\Core\Package\Billing\Component\Identifier\Contract\CafInterface;
36
use libredte\lib\Core\Package\Billing\Component\TradingParties\Contract\EmisorInterface;
37
use libredte\lib\Core\Package\Billing\Component\TradingParties\Contract\ReceptorInterface;
38
use LogicException;
39
use stdClass;
40
41
/**
42
 * Contenedor de datos del documento tributario electrónico.
43
 *
44
 * Permite "mover" un documento, junto a otros datos asociados, por métodos de
45
 * manera sencilla y, sobre todo, extensible.
46
 */
47
class DocumentBag implements DocumentBagInterface
48
{
49
    /**
50
     * Datos originales de entrada que se utilizarán para construir el
51
     * documento tributario.
52
     *
53
     * El formato de estos datos puede ser cualquiera soportado por los parsers.
54
     *
55
     * @var string|null
56
     */
57
    private ?string $inputData;
58
59
    /**
60
     * Datos de entrada procesados (parseados).
61
     *
62
     * Están en el formato estándar de LibreDTE. Que es básicamente el oficial
63
     * del SII. Con algunas extensiones, como los datos "extras".
64
     *
65
     * Estos son los datos que se usarán para construir el documento. Estos
66
     * datos no están normaliados, solo parseados.
67
     *
68
     * @var array|null
69
     */
70
    private ?array $parsedData;
71
72
    /**
73
     * Datos normalizados del documento tributario.
74
     *
75
     * Son los datos con todos sus campos necesarios ya determinados, calculados
76
     * y validados.
77
     *
78
     * La estructura de estos datos depende de los normalizadores.
79
     *
80
     * Importante: si se desactiva la normalización este arreglo contendrá lo
81
     * mismo que $parsedData pues no se tocarán los datos de entrada procesados.
82
     *
83
     * @var array|null
84
     */
85
    private ?array $normalizedData;
86
87
    /**
88
     * Opciones para los workers asociados al documento.
89
     *
90
     * Se definen los siguientes índices para las opciones:
91
     *
92
     *   - `builder`: Opciones para los constructores.
93
     *   - `normalizer`: Opciones para los normalizadores.
94
     *   - `parser`: Opciones para los analizadores sintácticos.
95
     *   - `renderer`: Opciones para los renderizadores.
96
     *   - `sanitizer`: Opciones para los sanitizadores.
97
     *   - `validator`: Opciones para los validadores.
98
     *
99
     * Se usarán las opciones por defecto en cada worker si no se indican los
100
     * índices en el arreglo $options.
101
     *
102
     * @var DataContainerInterface|null
103
     */
104
    private ?DataContainerInterface $options;
105
106
    /**
107
     * Reglas de esquema de las opciones del documento.
108
     *
109
     * El formato del esquema es el utilizado por
110
     * Symfony\Component\OptionsResolver\OptionsResolver.
111
     *
112
     * Acá solo se indicarán los índices que deben pueden existir en las
113
     * opciones. No se define el esquema de cada opción pues cada clase que
114
     * utilice estas opciones deberá resolver y validar sus propias opciones.
115
     *
116
     * @var array
117
     */
118
    protected array $optionsSchema = [
119
        'builder' => [
120
            'types' => 'array',
121
            'default' => [],
122
        ],
123
        'normalizer' => [
124
            'types' => 'array',
125
            'default' => [],
126
        ],
127
        'parser' => [
128
            'types' => 'array',
129
            'default' => [],
130
        ],
131
        'renderer' => [
132
            'types' => 'array',
133
            'default' => [],
134
        ],
135
        'sanitizer' => [
136
            'types' => 'array',
137
            'default' => [],
138
        ],
139
        'validator' => [
140
            'types' => 'array',
141
            'default' => [],
142
        ],
143
    ];
144
145
    /**
146
     * Instancia del documento XML asociada al DTE.
147
     *
148
     * @var XmlInterface|null
149
     */
150
    private ?XmlInterface $xmlDocument;
151
152
    /**
153
     * Código de Asignación de Folios (CAF) para timbrar el Documento Tributario
154
     * Electrónico (DTE) que se generará.
155
     *
156
     * @var CafInterface|null
157
     */
158
    private ?CafInterface $caf;
159
160
    /**
161
     * Certificado digital (firma electrónica) para la firma del documento.
162
     *
163
     * @var CertificateInterface|null
164
     */
165
    private ?CertificateInterface $certificate;
166
167
    /**
168
     * Entidad con el documento tributario electrónico generado.
169
     *
170
     * @var DocumentInterface|null
171
     */
172
    private ?DocumentInterface $document;
173
174
    /**
175
     * Entidad que representa al tipo de documento tributario que está contenido
176
     * en esta bolsa.
177
     *
178
     * @var TipoDocumentoInterface|null
179
     */
180
    private ?TipoDocumentoInterface $documentType = null;
181
182
    /**
183
     * Emisor del documento tributario.
184
     *
185
     * @var EmisorInterface|null
186
     */
187
    private ?EmisorInterface $emisor = null;
188
189
    /**
190
     * Receptor del documento tributario.
191
     *
192
     * @var ReceptorInterface|null
193
     */
194
    private ?ReceptorInterface $receptor = null;
195
196
    /**
197
     * Arreglo con la estructura del nodo TED del documento.
198
     *
199
     * @var array|null
200
     */
201
    private ?array $timbre = null;
202
203
    /**
204
     * Arreglo con los datos normalizados consolidados con el timbre y la firma
205
     * si existen en la bolsa.
206
     *
207
     * @var array|null
208
     */
209
    private ?array $data = null;
210
211
    /**
212
     * Constructor del contenedor.
213
     *
214
     * Recibe los datos en diferentes formatos para pasarlos a los setters que
215
     * los normalizan y asignan al contenedor.
216
     *
217
     * @param string|array|stdClass|null $inputData
218
     * @param array|null $parsedData
219
     * @param array|null $normalizedData
220
     * @param array|DataContainerInterface|null $options
221
     * @param XmlInterface|null $xmlDocument
222
     * @param CafInterface|null $caf
223
     * @param CertificateInterface|null $certificate
224
     * @param DocumentInterface|null $document
225
     * @param TipoDocumentoInterface|null $documentType
226
     * @param EmisorInterface|null $emisor
227
     * @param ReceptorInterface|null $receptor
228
     */
229 74
    public function __construct(
230
        string|array|stdClass $inputData = null,
231
        array $parsedData = null,
232
        array $normalizedData = null,
233
        array|DataContainerInterface $options = null,
234
        XmlInterface $xmlDocument = null,
235
        CafInterface $caf = null,
236
        CertificateInterface $certificate = null,
237
        DocumentInterface $document = null,
238
        TipoDocumentoInterface $documentType = null,
239
        EmisorInterface $emisor = null,
240
        ReceptorInterface $receptor = null
241
    ) {
242 74
        $this
243 74
            ->setInputData($inputData)
244 74
            ->setParsedData($parsedData)
245 74
            ->setNormalizedData($normalizedData)
246 74
            ->setOptions($options)
247 74
            ->setXmlDocument($xmlDocument)
248 74
            ->setCaf($caf)
249 74
            ->setCertificate($certificate)
250 74
            ->setDocument($document)
251 74
            ->setDocumentType($documentType)
252 74
            ->setEmisor($emisor)
253 74
            ->setReceptor($receptor)
254 74
        ;
255
    }
256
257
    /**
258
     * {@inheritDoc}
259
     */
260 74
    public function setInputData(string|array|stdClass|null $inputData): static
261
    {
262 74
        if ($inputData === null) {
0 ignored issues
show
introduced by
The condition $inputData === null is always false.
Loading history...
263 65
            $this->inputData = null;
264
265 65
            return $this;
266
        }
267
268 14
        if (!is_string($inputData)) {
0 ignored issues
show
introduced by
The condition is_string($inputData) is always false.
Loading history...
269 6
            $inputData = json_encode($inputData);
270
        }
271
272 14
        $this->inputData = $inputData;
273
274 14
        return $this;
275
    }
276
277
    /**
278
     * {@inheritDoc}
279
     */
280 74
    public function getInputData(): ?string
281
    {
282 74
        return $this->inputData;
283
    }
284
285
    /**
286
     * {@inheritDoc}
287
     */
288 74
    public function setParsedData(?array $parsedData): static
289
    {
290 74
        $this->parsedData = $parsedData;
291
292 74
        return $this;
293
    }
294
295
    /**
296
     * {@inheritDoc}
297
     */
298 73
    public function getParsedData(): ?array
299
    {
300 73
        return $this->parsedData;
301
    }
302
303
    /**
304
     * {@inheritDoc}
305
     */
306 74
    public function setNormalizedData(?array $normalizedData): static
307
    {
308 74
        $this->normalizedData = $normalizedData;
309
310 74
        return $this;
311
    }
312
313
    /**
314
     * {@inheritDoc}
315
     */
316 69
    public function getNormalizedData(): ?array
317
    {
318 69
        return $this->normalizedData;
319
    }
320
321
    /**
322
     * {@inheritDoc}
323
     */
324 74
    public function setOptions(array|DataContainerInterface|null $options): static
325
    {
326 74
        if ($options === null) {
0 ignored issues
show
introduced by
The condition $options === null is always false.
Loading history...
327 72
            $options = [];
328
        }
329
330 74
        if (is_array($options)) {
0 ignored issues
show
introduced by
The condition is_array($options) is always true.
Loading history...
331 72
            $options = new DataContainer($options, $this->optionsSchema);
332
        }
333
334 74
        $this->options = $options;
335
336 74
        return $this;
337
    }
338
339
    /**
340
     * {@inheritDoc}
341
     */
342 4
    public function getOptions(): ?DataContainerInterface
343
    {
344 4
        return $this->options;
345
    }
346
347
    /**
348
     * {@inheritDoc}
349
     */
350 14
    public function getParserOptions(): array
351
    {
352 14
        return (array) $this->options?->get('parser');
353
    }
354
355
    /**
356
     * {@inheritDoc}
357
     */
358 56
    public function getBuilderOptions(): array
359
    {
360 56
        return (array) $this->options?->get('builder');
361
    }
362
363
    /**
364
     * {@inheritDoc}
365
     */
366 60
    public function getNormalizerOptions(): array
367
    {
368 60
        return (array) $this->options?->get('normalizer');
369
    }
370
371
    /**
372
     * {@inheritDoc}
373
     */
374
    public function getSanitizerOptions(): array
375
    {
376
        return (array) $this->options?->get('sanitizer');
377
    }
378
379
    /**
380
     * {@inheritDoc}
381
     */
382
    public function getValidatorOptions(): array
383
    {
384
        return (array) $this->options?->get('validator');
385
    }
386
387
    /**
388
     * {@inheritDoc}
389
     */
390 56
    public function getRendererOptions(): array
391
    {
392 56
        return (array) $this->options?->get('renderer');
393
    }
394
395
    /**
396
     * {@inheritDoc}
397
     */
398 74
    public function setXmlDocument(?XmlInterface $xmlDocument): static
399
    {
400 74
        $this->xmlDocument = $xmlDocument;
401
402 74
        return $this;
403
    }
404
405
    /**
406
     * {@inheritDoc}
407
     */
408 69
    public function getXmlDocument(): ?XmlInterface
409
    {
410 69
        return $this->xmlDocument;
411
    }
412
413
    /**
414
     * {@inheritDoc}
415
     */
416 74
    public function setCaf(?CafInterface $caf): static
417
    {
418 74
        $this->caf = $caf;
419
420 74
        return $this;
421
    }
422
423
    /**
424
     * {@inheritDoc}
425
     */
426 60
    public function getCaf(): ?CafInterface
427
    {
428 60
        return $this->caf;
429
    }
430
431
    /**
432
     * {@inheritDoc}
433
     */
434 74
    public function setCertificate(?CertificateInterface $certificate): static
435
    {
436 74
        $this->certificate = $certificate;
437
438 74
        return $this;
439
    }
440
441
    /**
442
     * {@inheritDoc}
443
     */
444 60
    public function getCertificate(): ?CertificateInterface
445
    {
446 60
        return $this->certificate;
447
    }
448
449
    /**
450
     * {@inheritDoc}
451
     */
452 74
    public function setDocument(?DocumentInterface $document): static
453
    {
454 74
        $this->document = $document;
455
456 74
        return $this;
457
    }
458
459
    /**
460
     * {@inheritDoc}
461
     */
462 69
    public function getDocument(): ?DocumentInterface
463
    {
464 69
        return $this->document;
465
    }
466
467
    /**
468
     * {@inheritDoc}
469
     */
470 74
    public function setDocumentType(?TipoDocumentoInterface $documentType): static
471
    {
472 74
        $this->documentType = $documentType;
473
474 74
        return $this;
475
    }
476
477
    /**
478
     * {@inheritDoc}
479
     */
480 70
    public function setTipoDocumento(?TipoDocumentoInterface $tipoDocumento): static
481
    {
482 70
        return $this->setDocumentType($tipoDocumento);
483
    }
484
485
    /**
486
     * {@inheritDoc}
487
     */
488 73
    public function getDocumentType(): ?TipoDocumentoInterface
489
    {
490 73
        return $this->documentType;
491
    }
492
493
    /**
494
     * {@inheritDoc}
495
     */
496 73
    public function getTipoDocumento(): ?TipoDocumentoInterface
497
    {
498 73
        return $this->getDocumentType();
499
    }
500
501
    /**
502
     * {@inheritDoc}
503
     */
504 73
    public function getDocumentTypeId(): ?int
505
    {
506 73
        $TipoDTE = $this->parsedData['Encabezado']['IdDoc']['TipoDTE']
507 73
            ?? $this->normalizedData['Encabezado']['IdDoc']['TipoDTE']
508 73
            ?? $this->xmlDocument?->query('//Encabezado/IdDoc/TipoDTE')
509 73
            ?? $this->document?->getCodigo()
510 66
            ?? null
511 73
        ;
512
513 73
        if (!$TipoDTE) {
514 2
            throw new DocumentException(
515 2
                'Falta indicar el tipo de documento (TipoDTE) en los datos del DTE.'
516 2
            );
517
        }
518
519 71
        return (int) $TipoDTE;
520
    }
521
522
    /**
523
     * {@inheritDoc}
524
     */
525 73
    public function getCodigoTipoDocumento(): ?int
526
    {
527 73
        return $this->getDocumentTypeId();
528
    }
529
530
    /**
531
     * {@inheritDoc}
532
     */
533 74
    public function setEmisor(?EmisorInterface $emisor): static
534
    {
535 74
        $this->emisor = $emisor;
536
537 74
        return $this;
538
    }
539
540
    /**
541
     * {@inheritDoc}
542
     */
543 69
    public function getEmisor(): ?EmisorInterface
544
    {
545 69
        return $this->emisor;
546
    }
547
548
    /**
549
     * {@inheritDoc}
550
     */
551 74
    public function setReceptor(?ReceptorInterface $receptor): static
552
    {
553 74
        $this->receptor = $receptor;
554
555 74
        return $this;
556
    }
557
558
    /**
559
     * {@inheritDoc}
560
     */
561 69
    public function getReceptor(): ?ReceptorInterface
562
    {
563 69
        return $this->receptor;
564
    }
565
566
    /**
567
     * {@inheritDoc}
568
     */
569 56
    public function setTimbre(?array $timbre): static
570
    {
571 56
        $this->timbre = $timbre;
572
573 56
        return $this;
574
    }
575
576
    /**
577
     * {@inheritDoc}
578
     */
579 60
    public function getTimbre(): ?array
580
    {
581 60
        return $this->timbre;
582
    }
583
584
    /**
585
     * {@inheritDoc}
586
     */
587 60
    public function getData(): ?array
588
    {
589
        // Si los datos ya estaban generados se entregan.
590 60
        if ($this->data !== null) {
591
            return $this->data;
592
        }
593
594
        // Si no hay datos normalizados se entrega `null`.
595 60
        if (!$this->getNormalizedData()) {
596
            return null;
597
        }
598
599
        // Se arma la estructura del nodo Documento.
600 60
        $tagXml = $this->getTipoDocumento()->getTagXml()->getNombre();
601 60
        $this->data = [
602 60
            'DTE' => [
603 60
                '@attributes' => [
604 60
                    'version' => '1.0',
605 60
                    'xmlns' => 'http://www.sii.cl/SiiDte',
606 60
                ],
607 60
                $tagXml => array_merge(
608 60
                    [
609 60
                        '@attributes' => [
610 60
                            'ID' => $this->getId(),
611 60
                        ],
612 60
                    ],
613 60
                    $this->getNormalizedData(),
614 60
                    (array) $this->getTimbre(),
615 60
                ),
616 60
                //'Signature' => '', // Se agrega al firmar (NO INCLUIR ACÁ).
617 60
            ],
618 60
        ];
619
620
        // Se entrega la estructura con los datos.
621 60
        return $this->data;
622
    }
623
624
    /**
625
     * {@inheritDoc}
626
     */
627 60
    public function getId(): string
628
    {
629 60
        $folio = $this->getFolio();
630
631 60
        if (is_int($folio)) {
632 55
            return sprintf(
633 55
                'LibreDTE_%s_T%03dF%09d',
634 55
                $this->getNormalizedData()['Encabezado']['Emisor']['RUTEmisor'],
635 55
                $this->getNormalizedData()['Encabezado']['IdDoc']['TipoDTE'],
636 55
                $folio
637 55
            );
638
        } else {
639 5
            return sprintf(
640 5
                'LibreDTE_%s_%03d-%s',
641 5
                $this->getNormalizedData()['Encabezado']['Emisor']['RUTEmisor'],
642 5
                $this->getNormalizedData()['Encabezado']['IdDoc']['TipoDTE'],
643 5
                $folio
644 5
            );
645
        }
646
    }
647
648
    /**
649
     * {@inheritDoc}
650
     */
651
    public function setFolio(int $folio): static
652
    {
653
        if ($this->getXmlDocument()) {
654
            throw new LogicException(
655
                'No es posible asignar el folio si ya se generó el documento XML.'
656
            );
657
        }
658
659
        $data = $this->getNormalizedData() ?? $this->getParsedData();
660
661
        if ($data === null) {
662
            throw new LogicException(
663
                'No es posible asignar el folio si no existen datos normalizados o parseados.'
664
            );
665
        }
666
667
        $data['Encabezado']['IdDoc']['Folio'] = $folio;
668
669
        if ($this->getNormalizedData()) {
670
            return $this->setNormalizedData($data);
671
        }
672
673
        return $this->setParsedData($data);
674
    }
675
676
    /**
677
     * {@inheritDoc}
678
     */
679 60
    public function getFolio(): int|string|null
680
    {
681 60
        $data = $this->getNormalizedData() ?? $this->getParsedData();
682
683 60
        $folio = $data['Encabezado']['IdDoc']['Folio'];
684
685 60
        if (!$folio) {
686 5
            return null;
687
        }
688
689 55
        return is_numeric($folio) ? (int) $folio : (string) $folio;
690
    }
691
692
    /**
693
     * {@inheritDoc}
694
     */
695 4
    public function withCaf(CafInterface $caf): DocumentBagInterface
696
    {
697 4
        $class = static::class;
698
699 4
        return new $class(
700 4
            inputData: $this->getInputData(),
701 4
            parsedData: $this->getParsedData(),
702 4
            normalizedData: $this->getNormalizedData(),
703 4
            options: $this->getOptions(),
704 4
            caf: $caf,
705 4
            certificate: $this->getCertificate(),
706 4
            documentType: $this->getDocumentType(),
707 4
            emisor: $this->getEmisor(),
708 4
            receptor: $this->getReceptor()
709 4
        );
710
    }
711
712
    /**
713
     * {@inheritDoc}
714
     */
715 4
    public function withCertificate(
716
        CertificateInterface $certificate
717
    ): DocumentBagInterface {
718 4
        $class = static::class;
719
720 4
        return new $class(
721 4
            inputData: $this->getInputData(),
722 4
            parsedData: $this->getParsedData(),
723 4
            normalizedData: $this->getNormalizedData(),
724 4
            options: $this->getOptions(),
725 4
            caf: $this->getCaf(),
726 4
            certificate: $certificate,
727 4
            documentType: $this->getDocumentType(),
728 4
            emisor: $this->getEmisor(),
729 4
            receptor: $this->getReceptor()
730 4
        );
731
    }
732
733
    /**
734
     * {@inheritDoc}
735
     */
736 62
    public function getAlias(): string
737
    {
738 62
        return $this->getTipoDocumento()?->getAlias()
739 62
            ?? (
740 62
                $this->getTipoDocumento()?->getCodigo()
741 1
                    ? 'documento_' .  $this->getTipoDocumento()->getCodigo()
742 2
                    : null
743 62
            )
744 62
            ?? $this->getParsedData()['Encabezado']['IdDoc']['TipoDTE']
745 62
            ?? 'documento_desconocido'
746 62
        ;
747
    }
748
}
749