Passed
Push — master ( c02ff8...8cf6c5 )
by Esteban De La Fuente
06:05
created

DocumentoFactory   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 212
Duplicated Lines 0 %

Test Coverage

Coverage 92.59%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 72
c 1
b 0
f 0
dl 0
loc 212
ccs 75
cts 81
cp 0.9259
rs 10
wmc 23

8 Methods

Rating   Name   Duplication   Size   Complexity  
A getDocumentoBuilder() 0 9 2
A loadFromXml() 0 20 1
A createFromArray() 0 8 1
C getDocumentoBuilderClass() 0 42 14
A __construct() 0 3 1
A createFromYaml() 0 5 1
A createFromJson() 0 5 1
A createFromXml() 0 25 2
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
11
 * por la Fundación para el Software Libre, ya sea la versión 3 de la Licencia,
12
 * o (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\Sii\Dte\Documento\Builder;
26
27
use libredte\lib\Core\Service\ArrayDataProvider;
28
use libredte\lib\Core\Service\DataProviderInterface;
29
use libredte\lib\Core\Sii\Dte\Documento\AbstractDocumento;
30
use libredte\lib\Core\Sii\Dte\Documento\DocumentoException;
31
use libredte\lib\Core\Xml\XmlConverter;
32
use libredte\lib\Core\Xml\XmlDocument;
33
use Symfony\Component\Yaml\Yaml;
34
35
/**
36
 * Fábrica de documentos tributarios electrónicos.
37
 *
38
 * Permite crear un documento tributario electrónico a partir de los datos de
39
 * un arreglo o de un string XML.
40
 *
41
 * La principal ventaja de usar esta fábrica es que abstrae todo lo que se debe
42
 * hacer para buscar el "builder" del documento tributario y crear un documento
43
 * fácilmente a partir de sus datos. Además se preocupa de instanciar solo una
44
 * vez cada "builder".
45
 */
46
class DocumentoFactory
47
{
48
    /**
49
     * Constructores ("builders") de documentos que están inicializados.
50
     *
51
     * Esta es la "caché" para evitar instanciar más de una vez un "builder".
52
     *
53
     * @var array
54
     */
55
    private array $builders = [];
56
57
    /**
58
     * Proveedor de datos.
59
     *
60
     * @var DataProviderInterface
61
     */
62
    protected DataProviderInterface $dataProvider;
63
64
    /**
65
     * Constructor de la clase.
66
     *
67
     * @param ?DataProviderInterface $dataProvider Proveedor de datos.
68
     */
69 120
    public function __construct(?DataProviderInterface $dataProvider = null)
70
    {
71 120
        $this->dataProvider = $dataProvider ?? new ArrayDataProvider();
72
    }
73
74
    /**
75
     * Construye un documento tributario electrónico a partir de los datos en
76
     * un arreglo.
77
     *
78
     * @param array $data Arreglo con los datos del documento.
79
     * @return AbstractDocumento Documento tributario construído.
80
     */
81 118
    public function createFromArray(array $data): AbstractDocumento
82
    {
83
        // Crear builder para el documento que se creará.
84 118
        $builderClass = $this->getDocumentoBuilderClass($data);
85 114
        $builder = $this->getDocumentoBuilder($builderClass);
86
87
        // Construir y retornar el documento tributario solicitado.
88 114
        return $builder->build($data);
89
    }
90
91
    /**
92
     * Construye un documento tributario electrónico a partir de los datos en
93
     * un string XML.
94
     *
95
     * @param string $data String XML con los datos del documento.
96
     * @return AbstractDocumento Documento tributario construído.
97
     */
98 1
    public function createFromXml(string $data): AbstractDocumento
99
    {
100 1
        $xmlDocument = new XmlDocument();
101 1
        $xmlDocument->loadXML($data);
102 1
        $array = XmlConverter::xmlToArray($xmlDocument);
103
104
        // Obtener los datos del documento a generar.
105 1
        $documentoData = $array['DTE']['Documento']
106 1
            ?? $array['DTE']['Exportaciones']
107 1
            ?? $array['DTE']['Liquidacion']
108
            ?? null
109 1
        ;
110
111
        // Si el XML no tiene los tags válidos se lanza una excepción.
112 1
        if ($documentoData === null) {
113
            throw new DocumentoException(
114
                'El nodo raíz del XML del documento debe ser el tag "DTE". Dentro de este nodo raíz debe existir un tag "Documento", "Exportaciones" o "Liquidacion". Este segundo nodo es el que debe contener los datos del documento.'
115
            );
116
        }
117
118
        // Quitar los atributos que tenga el tag encontrado.
119 1
        unset($documentoData['@attributes']);
120
121
        // Crear el documento a partir de los datos encontrados.
122 1
        return $this->createFromArray($documentoData);
123
    }
124
125
    /**
126
     * Construye un documento tributario electrónico a partir de los datos en
127
     * un string YAML.
128
     *
129
     * @param string $data String YAML con los datos del documento.
130
     * @return AbstractDocumento Documento tributario construído.
131
     */
132 1
    public function createFromYaml(string $data): AbstractDocumento
133
    {
134 1
        $array = Yaml::parse($data);
135
136 1
        return $this->createFromArray($array);
137
    }
138
139
    /**
140
     * Construye un documento tributario electrónico a partir de los datos en
141
     * un string JSON.
142
     *
143
     * @param string $data String JSON con los datos del documento.
144
     * @return AbstractDocumento Documento tributario construído.
145
     */
146 1
    public function createFromJson(string $data): AbstractDocumento
147
    {
148 1
        $array = json_decode($data, true);
149
150 1
        return $this->createFromArray($array);
151
    }
152
153
    /**
154
     * Carga un documento tributario electrónico a partir de los datos en
155
     * un string XML.
156
     *
157
     * NOTE: Este método de creación de un documento espera que el XML contenga
158
     * todos los nodos y datos necesarios del documento (ej: incluyendo firma).
159
     * Se debe utilizar solamente para construir los documentos que vienen de
160
     * un XML que ya está listo el DTE. Si se desea crear un documento a partir
161
     * de datos que están en un string XML, pero que no están normalizados,
162
     * timbrados o firmados, se debe utilizar createFromXml().
163
     *
164
     * @param string $xml String XML con los datos del documento.
165
     * @return AbstractDocumento Documento tributario construído.
166
     */
167 2
    public function loadFromXml(string $xml): AbstractDocumento
168
    {
169 2
        $xmlDocument = new XmlDocument();
170 2
        $xmlDocument->loadXML($xml);
171 2
        $array = XmlConverter::xmlToArray($xmlDocument);
172
173
        // Obtener los datos del documento a generar.
174 2
        $data = $array['DTE']['Documento']
175 2
            ?? $array['DTE']['Exportaciones']
176 2
            ?? $array['DTE']['Liquidacion']
177
            ?? null
178 2
        ;
179
180
        // Crear builder para el documento que se creará.
181 2
        $builderClass = $this->getDocumentoBuilderClass($data);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type null; however, parameter $data of libredte\lib\Core\Sii\Dt...DocumentoBuilderClass() does only seem to accept array, 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

181
        $builderClass = $this->getDocumentoBuilderClass(/** @scrutinizer ignore-type */ $data);
Loading history...
182 2
        $builder = $this->getDocumentoBuilder($builderClass);
183
184
        // Construir una instancia del documento con los datos del XML y
185
        // retornarla.
186 2
        return $builder->loadFromXml($xml);
187
    }
188
189
    /**
190
     * Entrega la instancia del "builder" según la clase que se ha indicado.
191
     *
192
     * Este método utiliza una "caché" para entregar siempre el mismo "builder"
193
     * para el mismo tipo de documento.
194
     *
195
     * @param string $class Clase del "builder" que se desea su instancia.
196
     * @return AbstractDocumentoBuilder Instancia del "builder" solicitado.
197
     */
198 116
    private function getDocumentoBuilder(string $class): AbstractDocumentoBuilder
199
    {
200 116
        if (!isset($this->builders[$class])) {
201 116
            $this->builders[$class] = new $class(
202 116
                $this->dataProvider
203 116
            );
204
        }
205
206 116
        return $this->builders[$class];
207
    }
208
209
    /**
210
     * Determina qué "builder" se debe utilizar según el código del documento
211
     * que viene en los datos del documento que se debe crear.
212
     *
213
     * @param array $data Arreglo con los datos del documento.
214
     * @return string Clase del "builder" a usar para crear el documento.
215
     */
216 120
    private function getDocumentoBuilderClass(array $data): string
217
    {
218
        // Obtener el código del tipo de documento que se debe generar.
219 120
        $TipoDTE = $data['Encabezado']['IdDoc']['TipoDTE'] ?? null;
220 120
        if ($TipoDTE === null) {
221 1
            throw new DocumentoException(sprintf(
222 1
                'No se encontró el campo %s en el documento, el cual es obligatorio.',
223 1
                'TipoDTE'
224 1
            ));
225
        }
226
227
        // Determinar clase del "builder" en base al tipo de documento.
228 119
        switch ((int) $TipoDTE) {
229 119
            case 33:
230 39
                return FacturaAfectaBuilder::class;
231 81
            case 34:
232 5
                return FacturaExentaBuilder::class;
233 77
            case 39:
234 9
                return BoletaAfectaBuilder::class;
235 69
            case 41:
236 9
                return BoletaExentaBuilder::class;
237 60
            case 43:
238
                return LiquidacionFacturaBuilder::class;
239 60
            case 46:
240 17
                return FacturaCompraBuilder::class;
241 44
            case 52:
242 9
                return GuiaDespachoBuilder::class;
243 36
            case 56:
244 5
                return NotaDebitoBuilder::class;
245 32
            case 61:
246 13
                return NotaCreditoBuilder::class;
247 20
            case 110:
248 9
                return FacturaExportacionBuilder::class;
249 12
            case 111:
250 5
                return NotaDebitoExportacionBuilder::class;
251 8
            case 112:
252 5
                return NotaCreditoExportacionBuilder::class;
253
            default:
254 3
                throw new DocumentoException(sprintf(
255 3
                    'El valor "%s" del campo %s del documento es inválido.',
256 3
                    $TipoDTE,
257 3
                    'TipoDTE'
258 3
                ));
259
        }
260
    }
261
}
262