Test Failed
Push — master ( 5bd0ed...9eafef )
by Esteban De La Fuente
08:45
created

DocumentBag::setReceptor()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
ccs 3
cts 3
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 72
    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 72
        $this
243 72
            ->setInputData($inputData)
244 72
            ->setParsedData($parsedData)
245 72
            ->setNormalizedData($normalizedData)
246 72
            ->setOptions($options)
247 72
            ->setXmlDocument($xmlDocument)
248 72
            ->setCaf($caf)
249 72
            ->setCertificate($certificate)
250 72
            ->setDocument($document)
251 72
            ->setDocumentType($documentType)
252 72
            ->setEmisor($emisor)
253 72
            ->setReceptor($receptor)
254 72
        ;
255
    }
256
257
    /**
258
     * {@inheritDoc}
259
     */
260 72
    public function setInputData(string|array|stdClass|null $inputData): static
261
    {
262 72
        if ($inputData === null) {
0 ignored issues
show
introduced by
The condition $inputData === null is always false.
Loading history...
263 63
            $this->inputData = null;
264
265 63
            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 72
    public function getInputData(): ?string
281
    {
282 72
        return $this->inputData;
283
    }
284
285
    /**
286
     * {@inheritDoc}
287
     */
288 72
    public function setParsedData(?array $parsedData): static
289
    {
290 72
        $this->parsedData = $parsedData;
291
292 72
        return $this;
293
    }
294
295
    /**
296
     * {@inheritDoc}
297
     */
298 71
    public function getParsedData(): ?array
299
    {
300 71
        return $this->parsedData;
301
    }
302
303
    /**
304
     * {@inheritDoc}
305
     */
306 72
    public function setNormalizedData(?array $normalizedData): static
307
    {
308 72
        $this->normalizedData = $normalizedData;
309
310 72
        return $this;
311
    }
312
313
    /**
314
     * {@inheritDoc}
315
     */
316 67
    public function getNormalizedData(): ?array
317
    {
318 67
        return $this->normalizedData;
319
    }
320
321
    /**
322
     * {@inheritDoc}
323
     */
324 72
    public function setOptions(array|DataContainerInterface|null $options): static
325
    {
326 72
        if ($options === null) {
0 ignored issues
show
introduced by
The condition $options === null is always false.
Loading history...
327 70
            $options = [];
328
        }
329
330 72
        if (is_array($options)) {
0 ignored issues
show
introduced by
The condition is_array($options) is always true.
Loading history...
331 70
            $options = new DataContainer($options, $this->optionsSchema);
332
        }
333
334 72
        $this->options = $options;
335
336 72
        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 54
    public function getBuilderOptions(): array
359
    {
360 54
        return (array) $this->options?->get('builder');
361
    }
362
363
    /**
364
     * {@inheritDoc}
365
     */
366 58
    public function getNormalizerOptions(): array
367
    {
368 58
        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 54
    public function getRendererOptions(): array
391
    {
392 54
        return (array) $this->options?->get('renderer');
393
    }
394
395
    /**
396
     * {@inheritDoc}
397
     */
398 72
    public function setXmlDocument(?XmlInterface $xmlDocument): static
399
    {
400 72
        $this->xmlDocument = $xmlDocument;
401
402 72
        return $this;
403
    }
404
405
    /**
406
     * {@inheritDoc}
407
     */
408 67
    public function getXmlDocument(): ?XmlInterface
409
    {
410 67
        return $this->xmlDocument;
411
    }
412
413
    /**
414
     * {@inheritDoc}
415
     */
416 72
    public function setCaf(?CafInterface $caf): static
417
    {
418 72
        $this->caf = $caf;
419
420 72
        return $this;
421
    }
422
423
    /**
424
     * {@inheritDoc}
425
     */
426 58
    public function getCaf(): ?CafInterface
427
    {
428 58
        return $this->caf;
429
    }
430
431
    /**
432
     * {@inheritDoc}
433
     */
434 72
    public function setCertificate(?CertificateInterface $certificate): static
435
    {
436 72
        $this->certificate = $certificate;
437
438 72
        return $this;
439
    }
440
441
    /**
442
     * {@inheritDoc}
443
     */
444 58
    public function getCertificate(): ?CertificateInterface
445
    {
446 58
        return $this->certificate;
447
    }
448
449
    /**
450
     * {@inheritDoc}
451
     */
452 72
    public function setDocument(?DocumentInterface $document): static
453
    {
454 72
        $this->document = $document;
455
456 72
        return $this;
457
    }
458
459
    /**
460
     * {@inheritDoc}
461
     */
462 67
    public function getDocument(): ?DocumentInterface
463
    {
464 67
        return $this->document;
465
    }
466
467
    /**
468
     * {@inheritDoc}
469
     */
470 72
    public function setDocumentType(?TipoDocumentoInterface $documentType): static
471
    {
472 72
        $this->documentType = $documentType;
473
474 72
        return $this;
475
    }
476
477
    /**
478
     * {@inheritDoc}
479
     */
480 68
    public function setTipoDocumento(?TipoDocumentoInterface $tipoDocumento): static
481
    {
482 68
        return $this->setDocumentType($tipoDocumento);
483
    }
484
485
    /**
486
     * {@inheritDoc}
487
     */
488 71
    public function getDocumentType(): ?TipoDocumentoInterface
489
    {
490 71
        return $this->documentType;
491
    }
492
493
    /**
494
     * {@inheritDoc}
495
     */
496 71
    public function getTipoDocumento(): ?TipoDocumentoInterface
497
    {
498 71
        return $this->getDocumentType();
499
    }
500
501
    /**
502
     * {@inheritDoc}
503
     */
504 71
    public function getDocumentTypeId(): ?int
505
    {
506 71
        $TipoDTE = $this->parsedData['Encabezado']['IdDoc']['TipoDTE']
507 71
            ?? $this->normalizedData['Encabezado']['IdDoc']['TipoDTE']
508 71
            ?? $this->xmlDocument?->query('//Encabezado/IdDoc/TipoDTE')
509 71
            ?? $this->document?->getCodigo()
510 64
            ?? null
511 71
        ;
512
513 71
        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 69
        return (int) $TipoDTE;
520
    }
521
522
    /**
523
     * {@inheritDoc}
524
     */
525 71
    public function getCodigoTipoDocumento(): ?int
526
    {
527 71
        return $this->getDocumentTypeId();
528
    }
529
530
    /**
531
     * {@inheritDoc}
532
     */
533 72
    public function setEmisor(?EmisorInterface $emisor): static
534
    {
535 72
        $this->emisor = $emisor;
536
537 72
        return $this;
538
    }
539
540
    /**
541
     * {@inheritDoc}
542
     */
543 67
    public function getEmisor(): ?EmisorInterface
544
    {
545 67
        return $this->emisor;
546
    }
547
548
    /**
549
     * {@inheritDoc}
550
     */
551 72
    public function setReceptor(?ReceptorInterface $receptor): static
552
    {
553 72
        $this->receptor = $receptor;
554
555 72
        return $this;
556
    }
557
558
    /**
559
     * {@inheritDoc}
560
     */
561 67
    public function getReceptor(): ?ReceptorInterface
562
    {
563 67
        return $this->receptor;
564
    }
565
566
    /**
567
     * {@inheritDoc}
568
     */
569 54
    public function setTimbre(?array $timbre): static
570
    {
571 54
        $this->timbre = $timbre;
572
573 54
        return $this;
574
    }
575
576
    /**
577
     * {@inheritDoc}
578
     */
579 58
    public function getTimbre(): ?array
580
    {
581 58
        return $this->timbre;
582
    }
583
584
    /**
585
     * {@inheritDoc}
586
     */
587 58
    public function getData(): ?array
588
    {
589
        // Si los datos ya estaban generados se entregan.
590 58
        if ($this->data !== null) {
591
            return $this->data;
592
        }
593
594
        // Si no hay datos normalizados se entrega `null`.
595 58
        if (!$this->getNormalizedData()) {
596
            return null;
597
        }
598
599
        // Se arma la estructura del nodo Documento.
600 58
        $tagXml = $this->getTipoDocumento()->getTagXml()->getNombre();
601 58
        $this->data = [
602 58
            'DTE' => [
603 58
                '@attributes' => [
604 58
                    'version' => '1.0',
605 58
                    'xmlns' => 'http://www.sii.cl/SiiDte',
606 58
                ],
607 58
                $tagXml => array_merge(
608 58
                    [
609 58
                        '@attributes' => [
610 58
                            'ID' => $this->getId(),
611 58
                        ],
612 58
                    ],
613 58
                    $this->getNormalizedData(),
614 58
                    (array) $this->getTimbre(),
615 58
                ),
616 58
                //'Signature' => '', // Se agrega al firmar (NO INCLUIR ACÁ).
617 58
            ],
618 58
        ];
619
620
        // Se entrega la estructura con los datos.
621 58
        return $this->data;
622
    }
623
624
    /**
625
     * {@inheritDoc}
626
     */
627 58
    public function getId(): string
628
    {
629 58
        $folio = $this->getFolio();
630
631 58
        if (is_int($folio)) {
632 53
            return sprintf(
633 53
                'LibreDTE_%s_T%03dF%09d',
634 53
                $this->getNormalizedData()['Encabezado']['Emisor']['RUTEmisor'],
635 53
                $this->getNormalizedData()['Encabezado']['IdDoc']['TipoDTE'],
636 53
                $folio
637 53
            );
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
        $parsedData = $this->getParsedData();
660
        $normalizedData = $this->getNormalizedData();
661
662
        if ($parsedData === null && $normalizedData === null) {
663
            throw new LogicException(
664
                'No es posible asignar el folio si no existen datos parseados o normalizados.'
665
            );
666
        }
667
668
        if ($parsedData !== null) {
669
            $parsedData['Encabezado']['IdDoc']['Folio'] = $folio;
670
            $this->setParsedData($parsedData);
671
        }
672
673
        if ($normalizedData !== null) {
674
            $normalizedData['Encabezado']['IdDoc']['Folio'] = $folio;
675
            $this->setNormalizedData($normalizedData);
676
        }
677
678
        return $this;
679 58
    }
680
681 58
    /**
682
     * {@inheritDoc}
683 58
     */
684
    public function getFolio(): int|string|null
685 58
    {
686 5
        $data = $this->getNormalizedData() ?? $this->getParsedData();
687
688
        $folio = $data['Encabezado']['IdDoc']['Folio'];
689 53
690
        if (!$folio) {
691
            return null;
692
        }
693
694
        return is_numeric($folio) ? (int) $folio : (string) $folio;
695 4
    }
696
697 4
    /**
698
     * {@inheritDoc}
699 4
     */
700 4
    public function withCaf(CafInterface $caf): DocumentBagInterface
701 4
    {
702 4
        $class = static::class;
703 4
704 4
        return new $class(
705 4
            inputData: $this->getInputData(),
706 4
            parsedData: $this->getParsedData(),
707 4
            normalizedData: $this->getNormalizedData(),
708 4
            options: $this->getOptions(),
709 4
            caf: $caf,
710
            certificate: $this->getCertificate(),
711
            documentType: $this->getDocumentType(),
712
            emisor: $this->getEmisor(),
713
            receptor: $this->getReceptor()
714
        );
715 4
    }
716
717
    /**
718 4
     * {@inheritDoc}
719
     */
720 4
    public function withCertificate(
721 4
        CertificateInterface $certificate
722 4
    ): DocumentBagInterface {
723 4
        $class = static::class;
724 4
725 4
        return new $class(
726 4
            inputData: $this->getInputData(),
727 4
            parsedData: $this->getParsedData(),
728 4
            normalizedData: $this->getNormalizedData(),
729 4
            options: $this->getOptions(),
730 4
            caf: $this->getCaf(),
731
            certificate: $certificate,
732
            documentType: $this->getDocumentType(),
733
            emisor: $this->getEmisor(),
734
            receptor: $this->getReceptor()
735
        );
736 60
    }
737
738 60
    /**
739 60
     * {@inheritDoc}
740 60
     */
741 1
    public function getAlias(): string
742 2
    {
743 60
        return $this->getTipoDocumento()?->getAlias()
744 60
            ?? (
745 60
                $this->getTipoDocumento()?->getCodigo()
746 60
                    ? 'documento_' .  $this->getTipoDocumento()->getCodigo()
747
                    : null
748
            )
749
            ?? $this->getParsedData()['Encabezado']['IdDoc']['TipoDTE']
750
            ?? 'documento_desconocido'
751
        ;
752
    }
753
}
754