Test Failed
Push — master ( f8eb03...8feb3a )
by Francimar
06:03
created

SEFAZ::inutiliza()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 9
cts 9
cp 1
rs 9.8333
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
1
<?php
2
/**
3
 * MIT License
4
 *
5
 * Copyright (c) 2016 MZ Desenvolvimento de Sistemas LTDA
6
 *
7
 * @author Francimar Alves <[email protected]>
8
 *
9
 * Permission is hereby granted, free of charge, to any person obtaining a copy
10
 * of this software and associated documentation files (the "Software"), to deal
11
 * in the Software without restriction, including without limitation the rights
12
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
 * copies of the Software, and to permit persons to whom the Software is
14
 * furnished to do so, subject to the following conditions:
15
 *
16
 * The above copyright notice and this permission notice shall be included in all
17
 * copies or substantial portions of the Software.
18
 *
19
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
 * SOFTWARE.
26
 *
27
 */
28
namespace NFe\Core;
29
30
use NFe\Logger\Log;
31
use NFe\Task\Tarefa;
32
use NFe\Task\Autorizacao;
33
use NFe\Common\Ajuste;
34
35
/**
36
 * Classe que envia uma ou mais notas fiscais para os servidores da sefaz
37
 */
38
class SEFAZ
39
{
40
    /**
41
     * Lista de notas a serem processadas
42
     * @var Nota[]
43
     */
44
    private $notas;
45
46
    /**
47
     * Configurações a serem usadas
48
     * @var \NFe\Common\Configuracao
49
     */
50
    private $configuracao;
51
52
    /**
53
     * Instância global
54
     * @var self
55
     */
56
    private static $instance;
57
58
    /**
59
     * Constroi uma intência a partir de outra ou array
60
     * @param mixed $sefaz outra instância ou array
61
     */
62 101
    public function __construct($sefaz = [])
63
    {
64 101
        $this->fromArray($sefaz);
65 101
    }
66
67
    /**
68
     * Obtém a instância global dessa classe
69
     * @param bool $new cria uma nova instância
70
     * @return self default instance
71
     */
72 105
    public static function getInstance($new = false)
73
    {
74 105
        if (is_null(self::$instance) || $new) {
75 100
            self::$instance = new self();
76
        }
77 105
        return self::$instance;
78
    }
79
80
    /**
81
     * Lista de notas a serem processadas
82
     * @return Nota[]
83
     */
84 4
    public function getNotas()
85
    {
86 4
        return $this->notas;
87
    }
88
89
    /**
90
     * Informa a lista de notas a serem processadas
91
     * @param Nota[] $notas
92
     * @return self
93
     */
94 101
    public function setNotas($notas)
95
    {
96 101
        $this->notas = $notas;
97 101
        return $this;
98
    }
99
100
    /**
101
     * Adiciona uma nota ao processamento
102
     * @param Nota $nota
103
     * @return self
104
     */
105 3
    public function addNota($nota)
106
    {
107 3
        $this->notas[] = $nota;
108 3
        return $this;
109
    }
110
111
    /**
112
     * Configuração usada atualmente
113
     * @return \NFe\Common\Configuracao
114
     */
115 95
    public function getConfiguracao()
116
    {
117 95
        return $this->configuracao;
118
    }
119
120
    /**
121
     * Informa a nova configuração a ser usada
122
     * @param \NFe\Common\Configuracao $configuracao
123
     * @return self
124
     */
125 101
    public function setConfiguracao($configuracao)
126
    {
127 101
        $this->configuracao = $configuracao;
128 101
        return $this;
129
    }
130
131
    /**
132
     * Converte a instância atual em array
133
     * @param bool $recursive se deve converter os itens também em array
134
     * @return array
135
     */
136 1
    public function toArray($recursive = false)
137
    {
138 1
        $sefaz = [];
139 1
        if ($recursive) {
140 1
            $notas = [];
141 1
            $_notas = $this->getNotas();
142 1
            foreach ($_notas as $_nota) {
143 1
                $notas[] = $_nota->toArray($recursive);
144
            }
145 1
            $sefaz['notas'] = $notas;
146
        } else {
147 1
            $sefaz['notas'] = $this->getNotas();
148
        }
149 1
        if (!is_null($this->getConfiguracao()) && $recursive) {
150 1
            $sefaz['configuracao'] = $this->getConfiguracao()->toArray($recursive);
151
        } else {
152 1
            $sefaz['configuracao'] = $this->getConfiguracao();
153
        }
154 1
        return $sefaz;
155
    }
156
157
    /**
158
     * Preenche a instância atual com dados do array ou outra instância
159
     * @param mixed $sefaz outra instância ou array de dados
160
     * @return self
161
     */
162 101
    public function fromArray($sefaz = [])
163
    {
164 101
        if ($sefaz instanceof SEFAZ) {
165 1
            $sefaz = $sefaz->toArray();
166 101
        } elseif (!is_array($sefaz)) {
167 1
            return $this;
168
        }
169 101
        if (!isset($sefaz['notas'])) {
170 101
            $this->setNotas([]);
171
        } else {
172 1
            $this->setNotas($sefaz['notas']);
173
        }
174 101
        $this->setConfiguracao(new Ajuste(isset($sefaz['configuracao']) ? $sefaz['configuracao'] : []));
175 101
        return $this;
176
    }
177
178
    /**
179
     * Chama os eventos da nota lançando exceção em caso de rejeição
180
     * @param Nota $nota nota a ser despachada
181
     * @param \DOMDocument $dom xml da nota
182
     * @param \NFe\Task\Retorno $retorno resposta da SEFAZ
183
     */
184 1
    private function despacha($nota, $dom, $retorno)
185
    {
186 1
        $evento = $this->getConfiguracao()->getEvento();
187 1
        if ($retorno->isRecebido()) {
188
            Log::debug('SEFAZ.despacha - Recibo: '.$retorno->getNumero().' da '.$nota->getID(true));
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class NFe\Task\Retorno as the method getNumero() does only exist in the following sub-classes of NFe\Task\Retorno: NFe\Task\Evento, NFe\Task\Inutilizacao, NFe\Task\Protocolo, NFe\Task\Recibo. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
189
            $evento->onNotaProcessando($nota, $dom, $retorno);
190 1
        } elseif ($retorno->isAutorizado()) {
191 1
            $dom = $nota->addProtocolo($dom);
192 1
            Log::debug('SEFAZ.despacha('.$retorno->getStatus().') - '.$retorno->getMotivo().
193 1
                ', Protocolo: '.$retorno->getNumero().' - '.$nota->getID(true));
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class NFe\Task\Retorno as the method getNumero() does only exist in the following sub-classes of NFe\Task\Retorno: NFe\Task\Evento, NFe\Task\Inutilizacao, NFe\Task\Protocolo, NFe\Task\Recibo. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
194 1
            $evento->onNotaAutorizada($nota, $dom, $retorno);
195
        } elseif ($retorno->isDenegada()) {
196
            $evento->onNotaDenegada($nota, $dom, $retorno);
197
        } elseif ($retorno->isCancelado()) {
198
            $evento->onNotaCancelada($nota, $dom, $retorno);
199
        } else {
200
            $evento->onNotaRejeitada($nota, $dom, $retorno);
201
            throw new \Exception($retorno->getMotivo(), $retorno->getStatus());
202
        }
203 1
    }
204
205
    /**
206
     * Envia as notas adicionadas para a SEFAZ, caso não consiga, torna-as em contingência
207
     * todos os status são informados no evento da configuração, caso não possua evento,
208
     * uma \Exception será lançada na primeira falha
209
     * @return int quantidade de notas processadas ou que entraram em contingência
210
     */
211 3
    public function autoriza()
212
    {
213 3
        $i = 0;
214 3
        $evento = $this->getConfiguracao()->getEvento();
215 3
        foreach ($this->getNotas() as $nota) {
216
            try {
217 2
                $envia = true;
218
                do {
219 2
                    $dom = $nota->getNode()->ownerDocument;
220 2
                    $evento->onNotaGerada($nota, $dom);
221 2
                    $dom = $nota->assinar($dom);
222 2
                    $evento->onNotaAssinada($nota, $dom);
223 2
                    $dom = $nota->validar($dom); // valida o XML da nota
224 2
                    $evento->onNotaValidada($nota, $dom);
225 2
                    if (!$envia) {
226 1
                        break;
227
                    }
228 2
                    $evento->onNotaEnviando($nota, $dom);
229 2
                    $autorizacao = new Autorizacao();
230
                    try {
231 2
                        $retorno = $autorizacao->envia($nota, $dom);
232 1
                    } catch (\Exception $e) {
233 1
                        $partial_response = $e instanceof \NFe\Exception\IncompleteRequestException;
234 1
                        if ($partial_response) {
235 1
                            $evento->onNotaPendente($nota, $dom, $e);
236
                        }
237 1
                        if ($nota->getEmissao() == Nota::EMISSAO_CONTINGENCIA) {
238
                            throw $e;
239
                        }
240 1
                        Log::debug('SEFAZ.autoriza('.$e->getCode().') - Mudando emissão para contingência: '.
241 1
                            $e->getMessage().' - '.$nota->getID(true));
242 1
                        $msg = substr('Falha no envio da nota: '.$e->getMessage(), 0, 256);
243 1
                        $nota->setEmissao(Nota::EMISSAO_CONTINGENCIA);
244 1
                        $nota->setDataContingencia(time());
245 1
                        $nota->setJustificativa($msg);
246 1
                        $evento->onNotaContingencia($nota, !$partial_response, $e);
247 1
                        $envia = false;
248 1
                        continue;
249
                    }
250 1
                    Log::debug('SEFAZ.autoriza('.$retorno->getStatus().') - '.
251 1
                        $retorno->getMotivo().' - '.$nota->getID(true));
252 1
                    $this->despacha($nota, $dom, $retorno);
253 1
                    break;
254 1
                } while (true);
255 2
                $evento->onNotaCompleto($nota, $dom);
256 2
                $i++;
257
            } catch (\Exception $e) {
258
                Log::error('SEFAZ.autoriza('.$e->getCode().') - '.$e->getMessage());
259 2
                $evento->onNotaErro($nota, $e);
260
            }
261
        }
262 3
        return $i;
263
    }
264
265
    /**
266
     * Consulta o status das notas em processamento
267
     * @return int quantidade de consultas executadas com sucesso
268
     */
269 2
    public function consulta($pendencias)
270
    {
271 2
        $i = 0;
272 2
        $evento = $this->getConfiguracao()->getEvento();
273 2
        foreach ($pendencias as $pendencia) {
274
            $nota = $pendencia->getNota();
275
            try {
276
                $retorno = $pendencia->executa();
277
                $dom = $pendencia->getDocumento();
278
                Log::debug('SEFAZ.consulta('.$retorno->getStatus().') - '.
279
                    $retorno->getMotivo().' - '.$nota->getID(true));
280
                $this->despacha($nota, $dom, $retorno);
281
                $evento->onNotaCompleto($nota, $dom);
282
                $pendencia->setDocumento($dom);
283
                $evento->onTarefaExecutada($pendencia, $retorno);
284
                $i++;
285
            } catch (\Exception $e) {
286
                Log::error('SEFAZ.consulta('.$e->getCode().') - '.$e->getMessage());
287
                $evento->onNotaErro($nota, $e);
288
            }
289
        }
290 2
        return $i;
291
    }
292
293
    /**
294
     * Consulta se as notas existem e cancela ou inutiliza seus números
295
     * Também processa pedido de inutilização e cancelamento de notas
296
     * @return int quantidade de tarefas executadas com sucesso
297
     */
298 4
    public function executa($tarefas)
299
    {
300 4
        $i = 0;
301 4
        $evento = $this->getConfiguracao()->getEvento();
302 4
        foreach ($tarefas as $tarefa) {
303
            try {
304 2
                $save_dom = $tarefa->getDocumento();
305 2
                $retorno = $tarefa->executa();
306 1
                $dom = $tarefa->getDocumento();
307 1
                Log::debug('SEFAZ.executa('.$retorno->getStatus().') - '.$retorno->getMotivo().
308 1
                    ' - Tarefa: '.$tarefa->getID());
309 1
                switch ($tarefa->getAcao()) {
310 1
                    case Tarefa::ACAO_INUTILIZAR:
311 1
                        $inutilizacao = $tarefa->getAgente();
312 1
                        Log::debug('SEFAZ.executa[inutiliza]('.$inutilizacao->getStatus().') - '.
313 1
                            $inutilizacao->getMotivo().' - '.$inutilizacao->getID(true));
314 1
                        $evento->onInutilizado($inutilizacao, $dom);
315 1
                        break;
316
                    default:
317
                        $nota = $tarefa->getNota();
318
                        $this->despacha($nota, $save_dom, $retorno);
319
                        $evento->onNotaCompleto($nota, $save_dom);
320
                }
321 1
                $evento->onTarefaExecutada($tarefa, $retorno);
322 1
                $i++;
323 1
            } catch (\Exception $e) {
324 1
                Log::error('SEFAZ.executa('.$e->getCode().') - '.$e->getMessage());
325 2
                $evento->onTarefaErro($tarefa, $e);
326
            }
327
        }
328 3
        return $i;
329
    }
330
331
    /**
332
     * Inutiliza um intervalo de números de notas fiscais e insere o resultado no
333
     * próprio objeto de inutilização
334
     * @param \NFe\Task\Inutilizacao $inutilizacao tarefa a ser inutilizada
335
     * @return bool se a inutilização foi realizada com sucesso
336
     */
337 2
    public function inutiliza($inutilizacao)
338
    {
339 2
        $tarefa = new Tarefa();
340 2
        $tarefa->setAcao(Tarefa::ACAO_INUTILIZAR);
341 2
        $tarefa->setAgente($inutilizacao);
342
        try {
343 2
            $this->executa([$tarefa]);
344 1
        } catch (\Exception $e) {
345 1
            Log::error('SEFAZ.inutiliza('.$e->getCode().') - '.$e->getMessage());
346 1
            return false;
347
        }
348 1
        return true;
349
    }
350
351
    /**
352
     * Obtém as notas pendentes de autorização e envia para a SEFAZ
353
     * @return int quantidade de tarefas e notas processadas
354
     */
355 1
    public function processa()
356
    {
357 1
        $i = 0;
358
        /* Envia as notas não enviadas, rejeitadas e em contingência */
359
        try {
360 1
            $db = $this->getConfiguracao()->getBanco();
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $db. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
361 1
            $notas = $db->getNotasAbertas();
362 1
            $this->setNotas($notas);
363 1
            $i += $this->autoriza();
364
        } catch (\Exception $e) {
365
            Log::error('SEFAZ.processa[autoriza]('.$e->getCode().') - '.$e->getMessage());
366
        }
367
        /* Consulta o status das notas em processamento */
368
        try {
369 1
            $db = $this->getConfiguracao()->getBanco();
370 1
            $pendencias = $db->getNotasPendentes();
371 1
            $i += $this->consulta($pendencias);
372
        } catch (\Exception $e) {
373
            Log::error('SEFAZ.processa[pendentes]('.$e->getCode().') - '.$e->getMessage());
374
        }
375
        /* Consulta se as notas existem e cancela ou inutiliza seus números
376
         * Também processa pedido de inutilização e cancelamento de notas
377
         */
378
        try {
379 1
            $db = $this->getConfiguracao()->getBanco();
380 1
            $tarefas = $db->getNotasTarefas();
381 1
            $i += $this->executa($tarefas);
382
        } catch (\Exception $e) {
383
            Log::error('SEFAZ.processa[tarefas]('.$e->getCode().') - '.$e->getMessage());
384
        }
385 1
        return $i;
386
    }
387
}
388