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

DocumentUploadStatusResponse::parseEstado()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 55
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 25
c 1
b 0
f 0
nc 5
nop 1
dl 0
loc 55
ccs 0
cts 28
cp 0
crap 56
rs 8.5866

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\HttpClient\WebService;
26
27
/**
28
 * Respuesta de la consulta de estado de un documento subido al SII.
29
 *
30
 * Referencia: https://www.sii.cl/factura_electronica/factura_mercado/estado_envio.pdf
31
 */
32
class DocumentUploadStatusResponse extends AbstractWebServiceResponse
33
{
34
    /**
35
     * Estados de salida.
36
     *
37
     * El resultado de la consulta al SII puede arrojar uno de estos estados.
38
     */
39
    private const STATUSES = [
40
        'RSC' => 'Rechazado por Error en Schema',
41
        'SOK' => 'Schema Validado',
42
        'CRT' => 'Carátula OK',
43
        'RFR' => 'Rechazado por Error en Firma',
44
        'FOK' => 'Firma de Envió Validada',
45
        'PDR' => 'Envió en Proceso',
46
        'RCT' => 'Rechazado por Error en Carátula',
47
        'EPR' => 'Envío Procesado',
48
    ];
49
50
    /**
51
     * Estados de salida por ERROR.
52
     *
53
     * El resultado de la consulta al SII puede arrojar uno de estos estados de
54
     * error.
55
     */
56
    private const ERRORS = [
57
        // Otros Errores.
58
        'ESTADO' => [
59
            '-1' => 'ERROR: RETORNO CAMPO ESTADO, NO EXISTE',
60
            '-2' => 'ERROR RETORNO',
61
            '-3' => 'ERROR: RUT USUARIO NO EXISTE',
62
            '-4' => 'ERROR OBTENCION DE DATOS',
63
            '-5' => 'ERROR RETORNO DATOS',
64
            '-6' => 'ERROR: USUARIO NO AUTORIZADO',
65
            '-7' => 'ERROR RETORNO DATOS',
66
            '-8' => 'ERROR: RETORNO DATOS',
67
            '-9' => 'ERROR: RETORNO DATOS',
68
            '-10' => 'ERROR: VALIDA RUT USUARIO',
69
            '-11' => 'ERR_CODE, SQL_CODE, SRV_CODE',
70
            '-12' => 'ERROR: RETORNO CONSULTA',
71
            '-13' => 'ERROR RUT USUARIO NULO',
72
            '-14' => 'ERROR XML RETORNO DATOS',
73
            //'OTRO' => 'No documentado.',
74
        ],
75
        // Errores por autenticación.
76
        'TOKEN' => [
77
            '001' => 'Cookie Inactivo (o Token Inactivo)',
78
            '002' => 'Token Inactivo',
79
            '003' => 'Token No Existe',
80
        ],
81
        // Errores de consulta.
82
        'SRV_CODE' => [
83
            0 => 'Todo Ok',
84
            1 => 'Error en Entrada',
85
            2 => 'Error SQL',
86
        ],
87
        'SQL_CODE' => [
88
            0 => 'Schema Validado',
89
            //'OTRO' => 'Código de Oracle',
90
        ],
91
        'ERR_CODE' => [
92
            0 => 'Se retorna el estado',
93
            1 => 'El envío no es de la Empresa, faltan parámetros de entrada.',
94
            2 => 'Error de Proceso',
95
        ],
96
    ];
97
98
    /**
99
     * Obtiene los datos normalizados de la respuesta.
100
     *
101
     * @return array Datos normalizados de la respuesta del SII.
102
     */
103
    public function getData(): array
104
    {
105
        // Si no existen los datos normalizados de la respuesta se normalizan.
106
        if (!isset($this->data)) {
107
            // Si hay número de atención se normaliza.
108
            [$number, $datetime] = !empty($this->headers['NUM_ATENCION'])
109
                ? $this->parseNumeroAtencion($this->headers['NUM_ATENCION'])
110
                : null
111
            ;
112
113
            // Determinar Track ID.
114
            $track_id = $this->headers['TRACKID']
115
                ?? $this->requestData['TrackId']
116
                ?? null
117
            ;
118
119
            // RUT de la empresa que realizó la consulta del envío.
120
            $company = isset($this->requestData['Rut']) && isset($this->requestData['Dv'])
121
                ? $this->requestData['Rut'] . '-' . $this->requestData['Dv']
122
                : null
123
            ;
124
125
            // Normalizar el estado de la consulta del envío.
126
            [$status, $error, $description] = $this->parseEstado($this->headers);
127
128
            // Normalizar los documentos.
129
            $documents = $this->parseDocumentos($this->body);
130
            $resume = $this->calculateResume($documents);
131
132
            // Armar los datos normalizados.
133
            $this->data = [
134
                'query_number' => $number ?? null,
135
                'query_datetime' => $datetime ?? null,
136
                'track_id' => $track_id,
137
                'company' => $company,
138
                'status' => $status,
139
                'error' => $error,
140
                'description' => $description,
141
                'resume' => $resume,
142
                'documents' => $documents,
143
                'token' => $this->requestData['Token'] ?? null,
144
            ];
145
        }
146
147
        // Entregar los datos de la respuesta del envío del documento al SII.
148
        return $this->data;
149
    }
150
151
    /**
152
     * Devuelve el estado de revisión del documento.
153
     *
154
     * @return string El estado de la revisión del documento.
155
     */
156
    public function getReviewStatus(): string
157
    {
158
        $data = $this->getData();
159
160
        if ($data['status'] === 'EPR') {
161
            if ($this->hasRejectedDocuments()) {
162
                return 'RCH - DTE Rechazado';
163
            }
164
165
            if ($this->hasRepairsDocuments()) {
166
                return 'RLV - DTE Aceptado con Reparos Leves';
167
            }
168
        }
169
170
        return $data['description']
171
            ? ($data['status'] . ' - ' . $data['description'])
172
            : $data['status']
173
        ;
174
    }
175
176
    /**
177
     * Devuelve el detalle de la revisión del documento.
178
     *
179
     * @return string|null El detalle de la revisión o `null` si no existe.
180
     */
181
    public function getReviewDetail(): ?string
182
    {
183
        $data = $this->getData();
184
185
        if ($data['status'] === 'EPR') {
186
            if ($this->allDocumentsAreAccepted()) {
187
                return 'DTE aceptado';
188
            }
189
        }
190
191
        return null;
192
    }
193
194
    /**
195
     * Verifica si todos los documentos fueron aceptados.
196
     *
197
     * @return bool True si todos los documentos fueron aceptados.
198
     */
199
    private function allDocumentsAreAccepted(): bool
200
    {
201
        $data = $this->getData();
202
203
        return $data['resume']['reported'] === $data['resume']['accepted'];
204
    }
205
206
    /**
207
     * Verifica si hay documentos rechazados.
208
     *
209
     * @return bool True si existen documentos rechazados.
210
     */
211
    private function hasRejectedDocuments(): bool
212
    {
213
        $data = $this->getData();
214
215
        return $data['resume']['rejected'] > 0;
216
    }
217
218
    /**
219
     * Verifica si hay documentos con reparos.
220
     *
221
     * @return bool True si existen documentos con reparos.
222
     */
223
    private function hasRepairsDocuments(): bool
224
    {
225
        $data = $this->getData();
226
227
        return $data['resume']['repairs'] > 0;
228
    }
229
230
    /**
231
     * Parsea el estado de la respuesta del SII.
232
     *
233
     * @param array $headers Encabezados de la respuesta.
234
     * @return array Arreglo con el estado, error y descripción.
235
     */
236
    private function parseEstado(array $headers): array
237
    {
238
        // Asignar el código del estado y asumir que hubo error en el proceso
239
        // de envío del documento al SII.
240
        $status = $headers['ESTADO'];
241
        $error = true;
242
        $description = null;
243
244
        // Verificar si el estado es uno de los estados "normales", o sea, no
245
        // es un estado de error en el envío. Se valida esto primero pues es lo
246
        // que normalmente debería entregar el SII en la respuesta.
247
        if (isset(self::STATUSES[$status])) {
248
            $error = $status[0] === 'R';
249
            $description = $headers['GLOSA'] ?? null;
250
        }
251
252
        // Si el estado es un error de token se asigna.
253
        elseif (isset(self::ERRORS['TOKEN'][$status])) {
254
            //$description = self::ERRORS['TOKEN'][$code];
255
            $description = $headers['GLOSA'] ?? null;
256
        }
257
258
        // El error es uno de los números negativos. Donde hay 3 opciones:
259
        // El error es -11 y puede ser uno de los 3 que detalla -11.
260
        elseif ($status == '-11') {
261
            $errors = [];
262
            foreach (['SRV_CODE', 'SQL_CODE', 'ERR_CODE'] as $errorType) {
263
                if (isset($headers[$errorType])) {
264
                    $errors[] = self::ERRORS[$errorType][$headers[$errorType]]
265
                        ?? sprintf(
266
                            'Error %s #%s',
267
                            $errorType,
268
                            $headers[$errorType]
269
                        )
270
                    ;
271
                }
272
            }
273
            $description = implode(' ', $errors);
274
        }
275
276
        // El error es uno de los definidos como error de estado (excepto -11).
277
        elseif (isset(self::ERRORS['ESTADO'][$status])) {
278
            $description = self::ERRORS['ESTADO'][$status];
279
        }
280
281
        // El error es de estado pero no se tiene el código de error registrado.
282
        else {
283
            $description = sprintf(
284
                'Error desconocido código #%s al subir el XML.',
285
                $status
286
            );
287
        }
288
289
        // Entregar valores determinados para el error.
290
        return [$status, $error, $description];
291
    }
292
293
    /**
294
     * Parsea los documentos de la respuesta del SII.
295
     *
296
     * @param array $body Cuerpo de la respuesta del SII.
297
     * @return array Arreglo con los documentos normalizados.
298
     */
299
    private function parseDocumentos(array $body): array
300
    {
301
        // No vienen documentos en el cuerpo.
302
        if (empty($body['TIPO_DOCTO'])) {
303
            return [];
304
        }
305
306
        // Índices donde vienen los datos de los documentos.
307
        $keysMap = [
308
            'TIPO_DOCTO'  => 'type',
309
            'INFORMADOS'  => 'reported',
310
            'ACEPTADOS'   => 'accepted',
311
            'RECHAZADOS'  => 'rejected',
312
            'REPAROS'     => 'repairs',
313
        ];
314
        $keys = array_keys($keysMap);
315
316
        // Normalizar a arreglo de documentos (y otros datos) si no lo son.
317
        if (!is_array($body[$keys[0]])) {
318
            foreach ($keys as $key) {
319
                $body[$key] = [$body[$key] ?? 0];
320
            }
321
        }
322
323
        // Iterar y armar estado de los documentos.
324
        $n_documents = count($body[$keys[0]]);
325
        $documents = [];
326
        for ($i = 0; $i < $n_documents; $i++) {
327
            $document = [];
328
            foreach ($keysMap as $source => $destination) {
329
                $document[$destination] = $body[$source][$i];
330
            }
331
            $documents[$body[$keys[0]][$i]] = $document;
332
        }
333
334
        // Entregar documentos encontrados.
335
        return $documents;
336
    }
337
338
    /**
339
     * Calcula el resumen de los documentos procesados.
340
     *
341
     * @param array $documents Arreglo con los documentos.
342
     * @return array Resumen con el total de reportados, aceptados, rechazados
343
     * y reparos.
344
     */
345
    private function calculateResume(array $documents): array
346
    {
347
        $resume = [
348
            'reported' => 0,
349
            'accepted' => 0,
350
            'rejected' => 0,
351
            'repairs' => 0,
352
        ];
353
        $keys = array_keys($resume);
354
355
        foreach ($documents as $document) {
356
            foreach ($keys as $key) {
357
                $resume[$key] += ($document[$key] ?? 0);
358
            }
359
        }
360
361
        return $resume;
362
    }
363
}
364