DocumentBagManagerWorker::ensureXmlDocument()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 32
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 4.0092

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 18
nc 4
nop 1
dl 0
loc 32
ccs 22
cts 24
cp 0.9167
crap 4.0092
rs 9.6666
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 Derafu\Lib\Core\Foundation\Abstract\AbstractWorker;
28
use Derafu\Lib\Core\Package\Prime\Component\Entity\Contract\EntityComponentInterface;
29
use Derafu\Lib\Core\Package\Prime\Component\Xml\Contract\XmlComponentInterface;
30
use Derafu\Lib\Core\Package\Prime\Component\Xml\Contract\XmlInterface;
31
use libredte\lib\Core\Package\Billing\Component\Document\Contract\BuilderWorkerInterface;
32
use libredte\lib\Core\Package\Billing\Component\Document\Contract\DocumentBagInterface;
33
use libredte\lib\Core\Package\Billing\Component\Document\Contract\DocumentBagManagerWorkerInterface;
34
use libredte\lib\Core\Package\Billing\Component\Document\Contract\DocumentInterface;
35
use libredte\lib\Core\Package\Billing\Component\Document\Contract\NormalizerWorkerInterface;
36
use libredte\lib\Core\Package\Billing\Component\Document\Contract\ParserWorkerInterface;
37
use libredte\lib\Core\Package\Billing\Component\Document\Contract\SanitizerWorkerInterface;
38
use libredte\lib\Core\Package\Billing\Component\Document\Contract\TipoDocumentoInterface;
39
use libredte\lib\Core\Package\Billing\Component\Document\Contract\ValidatorWorkerInterface;
40
use libredte\lib\Core\Package\Billing\Component\Document\Exception\DocumentBagManagerException;
41
use libredte\lib\Core\Package\Billing\Component\Document\Support\DocumentBag;
42
use libredte\lib\Core\Package\Billing\Component\TradingParties\Contract\EmisorFactoryInterface;
43
use libredte\lib\Core\Package\Billing\Component\TradingParties\Contract\ReceptorFactoryInterface;
44
45
/**
46
 * Clase para el administrador de la bolsa con los datos de un DTE.
47
 */
48
class DocumentBagManagerWorker extends AbstractWorker implements DocumentBagManagerWorkerInterface
49
{
50
    protected $documentBagClass = DocumentBag::class;
51
52 120
    public function __construct(
53
        private XmlComponentInterface $xmlComponent,
54
        private BuilderWorkerInterface $builderWorker,
55
        private ParserWorkerInterface $parserWorker,
56
        private NormalizerWorkerInterface $normalizerWorker,
57
        private SanitizerWorkerInterface $sanitizerWorker,
58
        private ValidatorWorkerInterface $validatorWorker,
59
        private EntityComponentInterface $entityComponent,
60
        private EmisorFactoryInterface $emisorFactory,
61
        private ReceptorFactoryInterface $receptorFactory
62
    ) {
63 120
    }
64
65
    /**
66
     * {@inheritDoc}
67
     */
68 112
    public function create(
69
        string|array|XmlInterface|DocumentInterface $source,
70
        bool $normalizeAll = true
71
    ): DocumentBagInterface {
72
        // Asignar (si se pasó) o crear la bolsa.
73 112
        $class = $this->documentBagClass;
74 112
        $bag = new $class();
75
76
        // Si los datos vienen como string se deben parsear para asignar.
77
        // Además se normalizarán.
78 112
        if (is_string($source)) {
0 ignored issues
show
introduced by
The condition is_string($source) is always false.
Loading history...
79
            $aux = explode(':', $source, 1);
80
            $parserStrategy = str_replace('parser.strategy.', '', $aux[0]);
81
            $inputData = $aux[1] ?? '';
82
            $bag->setInputData($inputData);
83
            $bag->getOptions()->set('parser.strategy', $parserStrategy);
84
            $$this->parserWorker->parse($bag);
85
        }
86
87
        // Si los datos vienen como arreglo son los datos normalizados.
88 112
        if (is_array($source)) {
0 ignored issues
show
introduced by
The condition is_array($source) is always true.
Loading history...
89 1
            $bag->setNormalizedData($source);
90
        }
91
92
        // Si los datos vienen como documento XML es un XML cargado desde un
93
        // string XML (carga realizada por LoaderWorker). Ya viene normalizado.
94 112
        if ($source instanceof XmlInterface) {
0 ignored issues
show
introduced by
$source is never a sub-type of Derafu\Lib\Core\Package\...l\Contract\XmlInterface.
Loading history...
95 112
            $bag->setXmlDocument($source);
96
        }
97
98
        // Si los datos vienen como documento tributario entonces es un
99
        // documento que ya está creado. Puede estar o no timbrado y firmado,
100
        // eso no se determina ni valida acá. Si debe estará normalizado.
101 112
        if ($source instanceof DocumentInterface) {
0 ignored issues
show
introduced by
$source is never a sub-type of libredte\lib\Core\Packag...tract\DocumentInterface.
Loading history...
102
            $bag->setDocument($source);
103
        }
104
105
        // Normalizar los datos de la bolsa según otros datos que contenga.
106 112
        $bag = $this->normalize($bag, all: $normalizeAll);
107
108
        // Entregar la bolsa creada.
109 112
        return $bag;
110
    }
111
112
    /**
113
     * {@inheritDoc}
114
     */
115 120
    public function normalize(
116
        DocumentBagInterface $bag,
117
        bool $all = false
118
    ): DocumentBagInterface {
119
        // Datos esenciales que se normalizan.
120 120
        $this->ensureParsedData($bag);
121 120
        $this->ensureTipoDocumento($bag);
122
123
        // Datos extras que se pueden normalizar si se solicitó normalizar todo.
124 118
        if ($all === true) {
125 116
            $this->ensureNormalizedData($bag);
126 116
            $this->ensureXmlDocument($bag);
127 116
            $this->ensureDocument($bag);
128 116
            $this->ensureEmisor($bag);
129 116
            $this->ensureReceptor($bag);
130
        }
131
132
        // Entregar la bolsa normalizada.
133 118
        return $bag;
134
    }
135
136
    /**
137
     * Asegura que existan datos parseados en la bolsa si existen datos de
138
     * entrada para poder determinarlo y no está asignado previamente.
139
     *
140
     * Requiere: $bag->getInputData().
141
     *
142
     * Además, si los datos no usan la estrategia por defecto de parseo se debe
143
     * indicar en las opciones de la bolsa.
144
     *
145
     * @param DocumentBagInterface $bag
146
     * @return void
147
     */
148 120
    protected function ensureParsedData(DocumentBagInterface $bag): void
149
    {
150
        // Verificar si es necesario, y se puede, asignar.
151 120
        if ($bag->getParsedData() || !$bag->getInputData()) {
152 116
            return;
153
        }
154
155
        // Parsear los datos.
156 60
        $this->parserWorker->parse($bag);
157
    }
158
159
    /**
160
     * Asegura que exista un tipo de documento en la bolsa si existen datos para
161
     * poder determinarlo y no está asignado previamente.
162
     *
163
     * Requiere: $bag->getParsedData().
164
     *
165
     * @param DocumentBagInterface $bag
166
     * @return void
167
     */
168 120
    protected function ensureTipoDocumento(DocumentBagInterface $bag): void
169
    {
170
        // Verificar si es necesario, y se puede, asignar.
171 120
        if ($bag->getTipoDocumento() || !$bag->getCodigoTipoDocumento()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $bag->getCodigoTipoDocumento() of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
172 108
            return;
173
        }
174
175
        // Buscar el tipo de documento tributario que se desea construir.
176 117
        $codigoTipoDocumento = $bag->getCodigoTipoDocumento();
177 117
        $tipoDocumento = $this->entityComponent
178 117
            ->getRepository(TipoDocumentoInterface::class)
179 117
            ->find($codigoTipoDocumento)
180 117
        ;
181
182
        // Si el documento no existe error.
183 117
        if (!$tipoDocumento) {
184
            throw new DocumentBagManagerException(sprintf(
185
                'No se encontró un código de documento tributario válido en los datos del DTE.'
186
            ));
187
        }
188
189
        // Asignar el tipo documento a la bolsa.
190 117
        $bag->setTipoDocumento($tipoDocumento);
191
    }
192
193
    /**
194
     * Asegura que existan los datos normalizados en la bolsa si existen datos
195
     * parseados para poder determinarlo y no está asignado previamente.
196
     *
197
     * Los datos normalizados también se pueden crear si existe un XmlDocument
198
     * o un DTE.
199
     *
200
     * Requiere: $bag->getParsedData() o $bag->getXmlDocument() o $bag->getDocument().
201
     *
202
     * @param DocumentBagInterface $bag
203
     * @return void
204
     */
205 116
    protected function ensureNormalizedData(DocumentBagInterface $bag): void
206
    {
207
        // Verificar si es necesario, y se puede, asignar.
208 116
        if ($bag->getNormalizedData()) {
209 107
            return;
210
        }
211
212
        // Construir los datos normalizados a partir de los datos parseados.
213 9
        if ($bag->getParsedData()) {
214
            // Normalizar los datos del documento de la bolsa si corresponde.
215
            $normalize = $bag->getNormalizerOptions()['normalize'] ?? true;
216
            if ($normalize) {
217
                // Normalizar los datos parseados de la bolsa.
218
                $this->normalizerWorker->normalize($bag);
219
220
                // Sanitizar los datos normalizados de la bolsa.
221
                $this->sanitizerWorker->sanitize($bag);
222
223
                // Validar los datos normalizados y sanitizados de la bolsa.
224
                $this->validatorWorker->validate($bag);
225
            }
226
        }
227
228
        // Construir los datos normalizados a partir del documento XML.
229
        // Importante: se asume que el documento XML se cargó desde un XML que
230
        // es válido y por ende normalizado.
231 9
        elseif ($bag->getXmlDocument()) {
232 9
            $normalizedData = $this->xmlComponent->getDecoderWorker()->decode(
233 9
                $bag->getXmlDocument()
234 9
            );
235 9
            $bag->setNormalizedData($normalizedData);
236
        }
237
238
        // Construir los datos normalizados a partir del DTE.
239
        // Importante: se asume que el DTE se cargó desde un XML que
240
        // es válido y por ende normalizado.
241
        elseif ($bag->getDocument()) {
242
            $normalizedData = $this->xmlComponent->getDecoderWorker()->decode(
243
                $bag->getDocument()->getXmlDocument()
244
            );
245
            $bag->setNormalizedData($normalizedData);
246
        }
247
    }
248
249
    /**
250
     * Asegura que existan un documento XML en la bolsa si existen datos
251
     * normalizados para poder determinarlo y no está asignado previamente.
252
     *
253
     * El XmlDocument también se puede crear si existe DTE.
254
     *
255
     * Requiere: $bag->getNormalizedData() o $bag->getDocument().
256
     *
257
     * @param DocumentBagInterface $bag
258
     * @return void
259
     */
260 116
    protected function ensureXmlDocument(DocumentBagInterface $bag): void
261
    {
262
        // Verificar si es necesario, y se puede, asignar.
263 116
        if ($bag->getXmlDocument()) {
264 116
            return;
265
        }
266
267
        // Construir el XmlDocument a partir de los datos normalizados.
268 1
        if ($bag->getNormalizedData()) {
269 1
            $tagXml = $bag->getTipoDocumento()->getTagXml()->getNombre();
270 1
            $xmlDocumentData = [
271 1
                'DTE' => [
272 1
                    '@attributes' => [
273 1
                        'version' => '1.0',
274 1
                        'xmlns' => 'http://www.sii.cl/SiiDte',
275 1
                    ],
276 1
                    $tagXml => array_merge([
277 1
                        '@attributes' => [
278 1
                            'ID' => $bag->getId(),
279 1
                        ],
280 1
                    ], $bag->getNormalizedData()),
281 1
                ],
282 1
            ];
283 1
            $xmlDocument = $this->xmlComponent->getEncoderWorker()->encode(
284 1
                $xmlDocumentData
285 1
            );
286 1
            $bag->setXmlDocument($xmlDocument);
287
        }
288
289
        // Asignar el XmlDocument a partir del DTE.
290
        elseif ($bag->getDocument()) {
291
            $bag->setXmlDocument($bag->getDocument()->getXmlDocument());
292
        }
293
    }
294
295
    /**
296
     * Asegura que existan un DTE en la bolsa si existe un XmlDocument
297
     * para poder crearlo y no está asignado previamente.
298
     *
299
     * Requiere: $bag->getXmlDocument() y $bag->getTipoDocumento().
300
     *
301
     * @param DocumentBagInterface $bag
302
     * @return void
303
     */
304 116
    protected function ensureDocument(DocumentBagInterface $bag): void
305
    {
306
        // Verificar si es necesario, y se puede, asignar.
307 116
        if ($bag->getDocument() || !$bag->getXmlDocument() || !$bag->getTipoDocumento()) {
308 107
            return;
309
        }
310
311
        // Crear el DTE.
312 10
        $document = $this->builderWorker->create($bag);
313 10
        $bag->setDocument($document);
314
    }
315
316
    /**
317
     * Asegura que existan el emisor en la bolsa si existe el dTE
318
     * para poder determinarlo y no está asignado previamente.
319
     *
320
     * El emisor también se pueden crear si existen un XmlDocument, datos
321
     * normalizados o datos parseados.
322
     *
323
     * Requiere: $bag->getDocument(), $bag->getXmlDocument(),
324
     * $bag->getNormalizedData() o $bag->getParsedData().
325
     *
326
     * @param DocumentBagInterface $bag
327
     * @return void
328
     */
329 116
    protected function ensureEmisor(DocumentBagInterface $bag): void
330
    {
331
        // Verificar si es necesario, y se puede, asignar.
332 116
        if ($bag->getEmisor()) {
333 53
            return;
334
        }
335
336
        // Crear a partir de los datos del DTE (sería lo más normal).
337 115
        if ($bag->getDocument()) {
338 115
            $emisor = $this->emisorFactory->create(
339 115
                $bag->getDocument()->getEmisor()
340 115
            );
341 115
            $bag->setEmisor($emisor);
342
        }
343
344
        // Crear a partir de los datos del XmlDocument.
345
        elseif ($bag->getXmlDocument()) {
346
            $emisor = $this->emisorFactory->create(
347
                $bag->getXmlDocument()->query(
0 ignored issues
show
Bug introduced by
It seems like $bag->getXmlDocument()->...y('/Encabezado/Emisor') can also be of type null and string; however, parameter $data of libredte\lib\Core\Packag...toryInterface::create() 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

347
                /** @scrutinizer ignore-type */ $bag->getXmlDocument()->query(
Loading history...
348
                    '/Encabezado/Emisor'
349
                )
350
            );
351
            $bag->setEmisor($emisor);
352
        }
353
354
        // Crear a partir de los datos normalizados.
355
        elseif ($bag->getNormalizedData()) {
356
            $emisor = $this->emisorFactory->create(
357
                $bag->getNormalizedData()['Encabezado']['Emisor']
358
            );
359
            $bag->setEmisor($emisor);
360
        }
361
362
        // Crear a partir de los datos parseados.
363
        elseif ($bag->getParsedData()) {
364
            $emisor = $this->emisorFactory->create(
365
                $bag->getParsedData()['Encabezado']['Emisor']
366
            );
367
            $bag->setEmisor($emisor);
368
        }
369
    }
370
371
    /**
372
     * Asegura que existan el emisor en la bolsa si existen datos
373
     * normalizados para poder determinarlo y no está asignado previamente.
374
     *
375
     * El emisor también se pueden crear si existe un XmlDocument o un DTE.
376
     *
377
     * Requiere: $bag->getNormalizedData() o $bag->getXmlDocument() o $bag->getDocument().
378
     *
379
     * @param DocumentBagInterface $bag
380
     * @return void
381
     */
382 116
    protected function ensureReceptor(DocumentBagInterface $bag): void
383
    {
384
        // Si ya está asignado el receptor no se hace nada.
385 116
        if ($bag->getReceptor()) {
386 52
            return;
387
        }
388
389
        // Crear a partir de los datos del DTE (sería lo más normal).
390 116
        if ($bag->getDocument()) {
391 116
            $emisor = $this->receptorFactory->create(
392 116
                $bag->getDocument()->getReceptor()
393 116
            );
394 116
            $bag->setReceptor($emisor);
395
        }
396
397
        // Crear a partir de los datos del XmlDocument.
398
        elseif ($bag->getXmlDocument()) {
399
            $emisor = $this->receptorFactory->create(
400
                $bag->getXmlDocument()->query(
0 ignored issues
show
Bug introduced by
It seems like $bag->getXmlDocument()->...'/Encabezado/Receptor') can also be of type null and string; however, parameter $data of libredte\lib\Core\Packag...toryInterface::create() 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

400
                /** @scrutinizer ignore-type */ $bag->getXmlDocument()->query(
Loading history...
401
                    '/Encabezado/Receptor'
402
                )
403
            );
404
            $bag->setReceptor($emisor);
405
        }
406
407
        // Crear a partir de los datos normalizados.
408
        elseif ($bag->getNormalizedData()) {
409
            $emisor = $this->receptorFactory->create(
410
                $bag->getNormalizedData()['Encabezado']['Receptor']
411
            );
412
            $bag->setReceptor($emisor);
413
        }
414
415
        // Crear a partir de los datos parseados.
416
        elseif ($bag->getParsedData()) {
417
            $emisor = $this->receptorFactory->create(
418
                $bag->getParsedData()['Encabezado']['Receptor']
419
            );
420
            $bag->setReceptor($emisor);
421
        }
422
    }
423
}
424