DocumentBagManagerWorker::ensureNormalizedData()   A
last analyzed

Complexity

Conditions 6
Paths 6

Size

Total Lines 41
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 27.4977

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 16
dl 0
loc 41
ccs 3
cts 19
cp 0.1579
rs 9.1111
c 1
b 0
f 0
cc 6
nc 6
nop 1
crap 27.4977
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\Backbone\Abstract\AbstractWorker;
28
use Derafu\Backbone\Attribute\Worker;
29
use Derafu\Repository\Contract\RepositoryManagerInterface;
30
use Derafu\Xml\Contract\XmlDocumentInterface;
31
use Derafu\Xml\Contract\XmlServiceInterface;
32
use libredte\lib\Core\Package\Billing\Component\Document\Contract\BuilderWorkerInterface;
33
use libredte\lib\Core\Package\Billing\Component\Document\Contract\DocumentBagInterface;
34
use libredte\lib\Core\Package\Billing\Component\Document\Contract\DocumentBagManagerWorkerInterface;
35
use libredte\lib\Core\Package\Billing\Component\Document\Contract\DocumentInterface;
36
use libredte\lib\Core\Package\Billing\Component\Document\Contract\NormalizerWorkerInterface;
37
use libredte\lib\Core\Package\Billing\Component\Document\Contract\ParserWorkerInterface;
38
use libredte\lib\Core\Package\Billing\Component\Document\Contract\SanitizerWorkerInterface;
39
use libredte\lib\Core\Package\Billing\Component\Document\Contract\TipoDocumentoInterface;
40
use libredte\lib\Core\Package\Billing\Component\Document\Contract\ValidatorWorkerInterface;
41
use libredte\lib\Core\Package\Billing\Component\Document\Exception\DocumentBagManagerException;
42
use libredte\lib\Core\Package\Billing\Component\Document\Support\DocumentBag;
43
use libredte\lib\Core\Package\Billing\Component\TradingParties\Contract\EmisorFactoryInterface;
44
use libredte\lib\Core\Package\Billing\Component\TradingParties\Contract\ReceptorFactoryInterface;
45
46
/**
47
 * Clase para el administrador de la bolsa con los datos de un DTE.
48
 */
49
#[Worker(name: 'document_bag_manager', component: 'document', package: 'billing')]
50
class DocumentBagManagerWorker extends AbstractWorker implements DocumentBagManagerWorkerInterface
51
{
52
    protected $documentBagClass = DocumentBag::class;
53
54 56
    public function __construct(
55
        private XmlServiceInterface $xmlService,
56
        private BuilderWorkerInterface $builderWorker,
57
        private ParserWorkerInterface $parserWorker,
58
        private NormalizerWorkerInterface $normalizerWorker,
59
        private SanitizerWorkerInterface $sanitizerWorker,
60
        private ValidatorWorkerInterface $validatorWorker,
61
        private RepositoryManagerInterface $repositoryManager,
62
        private EmisorFactoryInterface $emisorFactory,
63
        private ReceptorFactoryInterface $receptorFactory
64
    ) {
65 56
    }
66
67
    /**
68
     * {@inheritDoc}
69
     */
70 53
    public function create(
71
        string|array|XmlDocumentInterface|DocumentInterface $source,
72
        bool $normalizeAll = true
73
    ): DocumentBagInterface {
74
        // Asignar (si se pasó) o crear la bolsa.
75 53
        $class = $this->documentBagClass;
76 53
        $bag = new $class();
77
78
        // Si los datos vienen como string se deben parsear para asignar.
79
        // Además se normalizarán.
80 53
        if (is_string($source)) {
0 ignored issues
show
introduced by
The condition is_string($source) is always false.
Loading history...
81
            $aux = explode(':', $source, 1);
82
            $parserStrategy = str_replace('parser.strategy.', '', $aux[0]);
83
            $inputData = $aux[1] ?? '';
84
            $bag->setInputData($inputData);
85
            $bag->getOptions()->set('parser.strategy', $parserStrategy);
86
            $$this->parserWorker->parse($bag);
87
        }
88
89
        // Si los datos vienen como arreglo son los datos normalizados.
90 53
        if (is_array($source)) {
0 ignored issues
show
introduced by
The condition is_array($source) is always true.
Loading history...
91 1
            $bag->setNormalizedData($source);
92
        }
93
94
        // Si los datos vienen como documento XML es un XML cargado desde un
95
        // string XML (carga realizada por LoaderWorker). Ya viene normalizado.
96 53
        if ($source instanceof XmlDocumentInterface) {
0 ignored issues
show
introduced by
$source is never a sub-type of Derafu\Xml\Contract\XmlDocumentInterface.
Loading history...
97 53
            $bag->setXmlDocument($source);
98
        }
99
100
        // Si los datos vienen como documento tributario entonces es un
101
        // documento que ya está creado. Puede estar o no timbrado y firmado,
102
        // eso no se determina ni valida acá. Si debe estará normalizado.
103 53
        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...
104
            $bag->setDocument($source);
105
        }
106
107
        // Normalizar los datos de la bolsa según otros datos que contenga.
108 53
        $bag = $this->normalize($bag, all: $normalizeAll);
109
110
        // Entregar la bolsa creada.
111 53
        return $bag;
112
    }
113
114
    /**
115
     * {@inheritDoc}
116
     */
117 56
    public function normalize(
118
        DocumentBagInterface $bag,
119
        bool $all = false
120
    ): DocumentBagInterface {
121
        // Datos esenciales que se normalizan.
122 56
        $this->ensureParsedData($bag);
123 56
        $this->ensureTipoDocumento($bag);
124
125
        // Datos extras que se pueden normalizar si se solicitó normalizar todo.
126 54
        if ($all === true) {
127 53
            $this->ensureNormalizedData($bag);
128 53
            $this->ensureXmlDocument($bag);
129 53
            $this->ensureDocument($bag);
130 53
            $this->ensureEmisor($bag);
131 53
            $this->ensureReceptor($bag);
132
        }
133
134
        // Entregar la bolsa normalizada.
135 54
        return $bag;
136
    }
137
138
    /**
139
     * Asegura que existan datos parseados en la bolsa si existen datos de
140
     * entrada para poder determinarlo y no está asignado previamente.
141
     *
142
     * Requiere: $bag->getInputData().
143
     *
144
     * Además, si los datos no usan la estrategia por defecto de parseo se debe
145
     * indicar en las opciones de la bolsa.
146
     *
147
     * @param DocumentBagInterface $bag
148
     * @return void
149
     */
150 56
    protected function ensureParsedData(DocumentBagInterface $bag): void
151
    {
152
        // Verificar si es necesario, y se puede, asignar.
153 56
        if ($bag->getParsedData() || !$bag->getInputData()) {
154 53
            return;
155
        }
156
157
        // Parsear los datos.
158 55
        $this->parserWorker->parse($bag);
159
    }
160
161
    /**
162
     * Asegura que exista un tipo de documento en la bolsa si existen datos para
163
     * poder determinarlo y no está asignado previamente.
164
     *
165
     * Requiere: $bag->getParsedData().
166
     *
167
     * @param DocumentBagInterface $bag
168
     * @return void
169
     */
170 56
    protected function ensureTipoDocumento(DocumentBagInterface $bag): void
171
    {
172
        // Verificar si es necesario, y se puede, asignar.
173 56
        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...
174 53
            return;
175
        }
176
177
        // Buscar el tipo de documento tributario que se desea construir.
178 54
        $codigoTipoDocumento = $bag->getCodigoTipoDocumento();
179 54
        $tipoDocumento = $this->repositoryManager
180 54
            ->getRepository(TipoDocumentoInterface::class)
181 54
            ->find($codigoTipoDocumento)
182 54
        ;
183
184
        // Si el documento no existe error.
185 54
        if (!$tipoDocumento) {
186
            throw new DocumentBagManagerException(sprintf(
187
                'No se encontró un código de documento tributario válido en los datos del DTE.'
188
            ));
189
        }
190
191
        // Asignar el tipo documento a la bolsa.
192 54
        $bag->setTipoDocumento($tipoDocumento);
193
    }
194
195
    /**
196
     * Asegura que existan los datos normalizados en la bolsa si existen datos
197
     * parseados para poder determinarlo y no está asignado previamente.
198
     *
199
     * Los datos normalizados también se pueden crear si existe un XmlDocument
200
     * o un DTE.
201
     *
202
     * Requiere: $bag->getParsedData() o $bag->getXmlDocument() o $bag->getDocument().
203
     *
204
     * @param DocumentBagInterface $bag
205
     * @return void
206
     */
207 53
    protected function ensureNormalizedData(DocumentBagInterface $bag): void
208
    {
209
        // Verificar si es necesario, y se puede, asignar.
210 53
        if ($bag->getNormalizedData()) {
211 53
            return;
212
        }
213
214
        // Construir los datos normalizados a partir de los datos parseados.
215
        if ($bag->getParsedData()) {
216
            // Normalizar los datos del documento de la bolsa si corresponde.
217
            $normalize = $bag->getNormalizerOptions()['normalize'] ?? true;
218
            if ($normalize) {
219
                // Normalizar los datos parseados de la bolsa.
220
                $this->normalizerWorker->normalize($bag);
221
222
                // Sanitizar los datos normalizados de la bolsa.
223
                $this->sanitizerWorker->sanitize($bag);
224
225
                // Validar los datos normalizados y sanitizados de la bolsa.
226
                $this->validatorWorker->validate($bag);
227
            }
228
        }
229
230
        // Construir los datos normalizados a partir del documento XML.
231
        // Importante: se asume que el documento XML se cargó desde un XML que
232
        // es válido y por ende normalizado.
233
        elseif ($bag->getXmlDocument()) {
234
            $normalizedData = $this->xmlService->decode(
235
                $bag->getXmlDocument()
236
            );
237
            $bag->setNormalizedData($normalizedData);
238
        }
239
240
        // Construir los datos normalizados a partir del DTE.
241
        // Importante: se asume que el DTE se cargó desde un XML que
242
        // es válido y por ende normalizado.
243
        elseif ($bag->getDocument()) {
244
            $normalizedData = $this->xmlService->decode(
245
                $bag->getDocument()->getXmlDocument()
246
            );
247
            $bag->setNormalizedData($normalizedData);
248
        }
249
    }
250
251
    /**
252
     * Asegura que existan un documento XML en la bolsa si existen datos
253
     * normalizados para poder determinarlo y no está asignado previamente.
254
     *
255
     * El XmlDocument también se puede crear si existe DTE.
256
     *
257
     * Requiere: $bag->getNormalizedData() o $bag->getDocument().
258
     *
259
     * @param DocumentBagInterface $bag
260
     * @return void
261
     */
262 53
    protected function ensureXmlDocument(DocumentBagInterface $bag): void
263
    {
264
        // Verificar si es necesario, y se puede, asignar.
265 53
        if ($bag->getXmlDocument()) {
266 53
            return;
267
        }
268
269
        // Construir el XmlDocument a partir de los datos normalizados.
270 1
        if ($bag->getNormalizedData()) {
271 1
            $tagXml = $bag->getTipoDocumento()->getTagXml()->getNombre();
272 1
            $xmlDocumentData = [
273 1
                'DTE' => [
274 1
                    '@attributes' => [
275 1
                        'version' => '1.0',
276 1
                        'xmlns' => 'http://www.sii.cl/SiiDte',
277 1
                    ],
278 1
                    $tagXml => array_merge([
279 1
                        '@attributes' => [
280 1
                            'ID' => $bag->getId(),
281 1
                        ],
282 1
                    ], $bag->getNormalizedData()),
283 1
                ],
284 1
            ];
285 1
            $xmlDocument = $this->xmlService->encode(
286 1
                $xmlDocumentData
287 1
            );
288 1
            $bag->setXmlDocument($xmlDocument);
289
        }
290
291
        // Asignar el XmlDocument a partir del DTE.
292
        elseif ($bag->getDocument()) {
293
            $bag->setXmlDocument($bag->getDocument()->getXmlDocument());
294
        }
295
    }
296
297
    /**
298
     * Asegura que existan un DTE en la bolsa si existe un XmlDocument
299
     * para poder crearlo y no está asignado previamente.
300
     *
301
     * Requiere: $bag->getXmlDocument() y $bag->getTipoDocumento().
302
     *
303
     * @param DocumentBagInterface $bag
304
     * @return void
305
     */
306 53
    protected function ensureDocument(DocumentBagInterface $bag): void
307
    {
308
        // Verificar si es necesario, y se puede, asignar.
309 53
        if ($bag->getDocument() || !$bag->getXmlDocument() || !$bag->getTipoDocumento()) {
310 53
            return;
311
        }
312
313
        // Crear el DTE.
314 1
        $document = $this->builderWorker->create($bag);
315 1
        $bag->setDocument($document);
316
    }
317
318
    /**
319
     * Asegura que existan el emisor en la bolsa si existe el dTE
320
     * para poder determinarlo y no está asignado previamente.
321
     *
322
     * El emisor también se pueden crear si existen un XmlDocument, datos
323
     * normalizados o datos parseados.
324
     *
325
     * Requiere: $bag->getDocument(), $bag->getXmlDocument(),
326
     * $bag->getNormalizedData() o $bag->getParsedData().
327
     *
328
     * @param DocumentBagInterface $bag
329
     * @return void
330
     */
331 53
    protected function ensureEmisor(DocumentBagInterface $bag): void
332
    {
333
        // Verificar si es necesario, y se puede, asignar.
334 53
        if ($bag->getEmisor()) {
335 52
            return;
336
        }
337
338
        // Crear a partir de los datos del DTE (sería lo más normal).
339 53
        if ($bag->getDocument()) {
340 53
            $emisor = $this->emisorFactory->create(
341 53
                $bag->getDocument()->getEmisor()
342 53
            );
343 53
            $bag->setEmisor($emisor);
344
        }
345
346
        // Crear a partir de los datos del XmlDocument.
347
        elseif ($bag->getXmlDocument()) {
348
            $emisor = $this->emisorFactory->create(
349
                $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

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

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