Skip to content

Commit

Permalink
[IMP] l10n_br_nfe: Various enhancements and fixes
Browse files Browse the repository at this point in the history
- Added support for contingency mode (SVC)
- Added support for synchronous transmission
- Added NFe status check functionality
- Improved transmission process to ensure information received from SEFAZ is not lost on failure
- Various performance optimizations and bug fixes

nfe
  • Loading branch information
antoniospneto committed May 31, 2024
1 parent 2d1502a commit 57d9d97
Show file tree
Hide file tree
Showing 13 changed files with 565 additions and 94 deletions.
377 changes: 286 additions & 91 deletions l10n_br_nfe/models/document.py

Large diffs are not rendered by default.

31 changes: 31 additions & 0 deletions l10n_br_nfe/models/res_company.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,37 @@ class ResCompany(spec_models.SpecModel):
default=NFE_ENVIRONMENT_DEFAULT,
)

nfe_enable_sync_transmission = fields.Boolean(
help=(
"When enabled, this option configures the system to transmit the "
"NFe (Electronic Invoice) using a synchronous method instead of an "
"asynchronous one. This means that the system will wait for an immediate "
"response from the tax authority's system (SEFAZ) upon submission of the "
"NFe, providing quicker feedback on the submission status. Before "
"activating this option, please ensure that the SEFAZ in your state "
"supports synchronous processing for NFe submissions. Failure to verify "
"compatibility may result in transmission errors or rejections."
),
)

nfe_separate_async_process = fields.Boolean(
string="Separate NF-e Send and Consult",
help=(
"If enabled, the system will send the NF-e and store the receipt without "
"immediately consulting it. The user must manually consult the receipt "
"later. This option is valid only in asynchronous mode."
),
)

nfe_enable_contingency_ws = fields.Boolean(
help=(
"When enabled, all NFe-related services will be accessed using the "
"contingencyweb services. This ensures that operations such as issuing, "
"canceling, and consulting NFe will use the contingency web services "
"instead of the primary web services."
),
)

nfe_transmission = fields.Selection(
selection=NFE_TRANSMISSIONS,
string="Transmission Type",
Expand Down
5 changes: 5 additions & 0 deletions l10n_br_nfe/models/res_config_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ class ResConfigSettings(models.TransientModel):
readonly=False,
)

nfe_enable_sync_transmission = fields.Boolean(
related="company_id.nfe_enable_sync_transmission",
readonly=False,
)

nfe_danfe_layout = fields.Selection(
string="NFe Layout",
related="company_id.nfe_danfe_layout",
Expand Down
2 changes: 1 addition & 1 deletion l10n_br_nfe/tests/mocks/retConsReciNFe/autorizada.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<tpAmb>2</tpAmb>
<verAplic>sefaz_mocked</verAplic>
<nRec>423002202113232</nRec>
<cStat>100</cStat>
<cStat>104</cStat>
<xMotivo>Lote processado</xMotivo>
<cUF>42</cUF>
<dhRecbto>2023-06-11T01:18:19-03:00</dhRecbto>
Expand Down
35 changes: 35 additions & 0 deletions l10n_br_nfe/tests/mocks/retConsReciNFe/uso_denegado.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8" ?>
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
>
<soap:Body>
<nfeResultMsg
xmlns="http://www.portalfiscal.inf.br/nfe/wsdl/NFeRetAutorizacao4"
>
<retConsReciNFe versao="4.00" xmlns="http://www.portalfiscal.inf.br/nfe">
<tpAmb>2</tpAmb>
<verAplic>sefaz_mocked</verAplic>
<nRec>423002202113232</nRec>
<cStat>104</cStat>
<xMotivo>Lote processado</xMotivo>
<cUF>42</cUF>
<dhRecbto>2023-06-11T01:18:19-03:00</dhRecbto>
<protNFe versao="4.00">
<infProt>
<tpAmb>2</tpAmb>
<verAplic>SVRSnfce202307311112</verAplic>
<chNFe>33230807984267003800650040000000321935136447</chNFe>
<dhRecbto>2023-08-07T11:09:08-03:00</dhRecbto>
<nProt>333230000396082</nProt>
<digVal>zwJzbq4FXks09tlHU1GEWRI7t/A=</digVal>
<cStat>303</cStat>
<xMotivo
>Uso Denegado: Destinatário não habilitado a operar na UF</xMotivo>
</infProt>
</protNFe>
</retConsReciNFe>
</nfeResultMsg>
</soap:Body>
</soap:Envelope>
34 changes: 34 additions & 0 deletions l10n_br_nfe/tests/mocks/retConsSitNFe/autorizado.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8" ?>
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
>
<soap:Body>
<nfeResultMsg
xmlns="http://www.portalfiscal.inf.br/nfe/wsdl/NFeConsultaProtocolo4"
>
<retConsSitNFe versao="4.00" xmlns="http://www.portalfiscal.inf.br/nfe">
<tpAmb>2</tpAmb>
<verAplic>sefaz_mocked</verAplic>
<cStat>100</cStat>
<xMotivo>Autorizado o uso da NF-e</xMotivo>
<cUF>26</cUF>
<dhRecbto>2020-02-03T10:31:52-03:00</dhRecbto>
<chNFe>26200124494200000106550010000010111352744151</chNFe>
<protNFe versao="4.00">
<infProt Id="ID26200124494200000106550010000010111352744151">
<tpAmb>2</tpAmb>
<verAplic>sefaz_mocked.00.07.211</verAplic>
<chNFe>26200124494200000106550010000010111352744151</chNFe>
<dhRecbto>2020-01-13T14:20:52-03:00</dhRecbto>
<nProt>126200000020426</nProt>
<digVal>YRn2TFuteCw8/KW0mwxQBQGurlI=</digVal>
<cStat>100</cStat>
<xMotivo>Autorizado o uso da NF-e</xMotivo>
</infProt>
</protNFe>
</retConsSitNFe>
</nfeResultMsg>
</soap:Body>
</soap:Envelope>
34 changes: 34 additions & 0 deletions l10n_br_nfe/tests/mocks/retConsSitNFe/cancelado.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8" ?>
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
>
<soap:Body>
<nfeResultMsg
xmlns="http://www.portalfiscal.inf.br/nfe/wsdl/NFeConsultaProtocolo4"
>
<retConsSitNFe versao="4.00" xmlns="http://www.portalfiscal.inf.br/nfe">
<tpAmb>2</tpAmb>
<verAplic>sefaz_mocked</verAplic>
<cStat>101</cStat>
<xMotivo>Cancelamento de NF-e homologado</xMotivo>
<cUF>26</cUF>
<dhRecbto>2020-02-03T10:31:52-03:00</dhRecbto>
<chNFe>26200124494200000106550010000010111352744151</chNFe>
<retCancNFe versao="4.00">
<infCanc Id="ID43060992665611012850550070000081711388781007">
<tpAmb>2</tpAmb>
<verAplic>2.2.21</verAplic>
<cStat>103</cStat>
<xMotivo>Lote recebido com sucesso</xMotivo>
<cUF>12</cUF>
<chNFe>43060992665611012850550070000081711388781007</chNFe>
<dhRecbto>1969-12-31T21:00:01.000-03:00</dhRecbto>
<nProt>143060000295038</nProt>
</infCanc>
</retCancNFe>
</retConsSitNFe>
</nfeResultMsg>
</soap:Body>
</soap:Envelope>
34 changes: 34 additions & 0 deletions l10n_br_nfe/tests/mocks/retConsSitNFe/uso_denegado.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<soap:Envelope
xmlns:soap="http://www.w3.org/2003/05/soap-envelope"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
>
<soap:Body>
<nfeResultMsg
xmlns="http://www.portalfiscal.inf.br/nfe/wsdl/NFeConsultaProtocolo4"
>
<retConsSitNFe xmlns="http://www.portalfiscal.inf.br/nfe" versao="4.00">
<tpAmb>1</tpAmb>
<verAplic>sefaz_mocked</verAplic>
<cStat>110</cStat>
<xMotivo>Uso Denegado</xMotivo>
<cUF>26</cUF>
<dhRecbto>2020-02-13T14:20:52-03:00</dhRecbto>
<chNFe>26200124494200000106550010000010111352744151</chNFe>
<protNFe versao="4.00">
<infProt>
<tpAmb>1</tpAmb>
<verAplic>sefaz_mocked</verAplic>
<chNFe>26200124494200000106550010000010111352744151</chNFe>
<dhRecbto>2020-01-13T14:20:52-03:00</dhRecbto>
<nProt>135180772366783</nProt>
<digVal>4A20WByjTePGOz+LoGy1uVk2gaY=</digVal>
<cStat>302</cStat>
<xMotivo
>Uso Denegado: Irregularidade fiscal do destinatário</xMotivo>
</infProt>
</protNFe>
</retConsSitNFe>
</nfeResultMsg>
</soap:Body>
</soap:Envelope>
4 changes: 3 additions & 1 deletion l10n_br_nfe/tests/test_nfce.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,9 @@ def test_atualiza_status_nfce(self):
mock_autorizada.protocolo.infProt.xMotivo = "TESTE AUTORIZADO"
mock_autorizada.protocolo.infProt.dhRecbto = datetime.now()
mock_autorizada.processo_xml = b"dummy"
self.document_id.atualiza_status_nfe(mock_autorizada)
mock_autorizada.resposta = mock.MagicMock()
mock_autorizada.webservice = "dummy_service"
self.document_id._nfe_update_status_and_save_data(mock_autorizada)

self.assertEqual(self.document_id.state_edoc, SITUACAO_EDOC_AUTORIZADA)
self.assertEqual(self.document_id.status_code, AUTORIZADO[0])
Expand Down
76 changes: 75 additions & 1 deletion l10n_br_nfe/tests/test_nfe_webservices.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
# Copyright (C) 2023 - TODAY Raphaël Valyi - Akretion
# Copyright (C) 2024 - TODAY Antônio S. P. Neto - Engenere
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).


import logging
import subprocess
from unittest import mock

from odoo.fields import Datetime

from odoo.addons.l10n_br_fiscal.constants.fiscal import (
SITUACAO_EDOC_A_ENVIAR,
SITUACAO_EDOC_AUTORIZADA,
SITUACAO_EDOC_CANCELADA,
SITUACAO_EDOC_ENVIADA,
)
from odoo.addons.l10n_br_nfe.models.document import NFe

from .mock_utils import nfe_mock
from .test_nfe_serialize import TestNFeExport

_logger = logging.getLogger(__name__)


def is_libreoffice_command_available():
try:
Expand All @@ -26,7 +33,7 @@ def is_libreoffice_command_available():
return False


class TestNFeWebservices(TestNFeExport):
class TestNFeWebServices(TestNFeExport):
def setUp(self):
nfe_list = [
{
Expand Down Expand Up @@ -91,3 +98,70 @@ def test_inutilizar(self):
.create({"document_id": nfe.id, "justification": "Era apenas um teste."})
)
inutilizar_wizard.doit()

@nfe_mock(
{
"nfeAutorizacaoLote": "retEnviNFe/lote_recebido.xml",
"nfeRetAutorizacaoLote": "retConsReciNFe/autorizada.xml",
}
)
def test_nfe_consult_receipt(self):
"""
Tests the asynchronous NFe transmission, separating the sending and
the consultation into two distinct steps.
"""
for nfe_data in self.nfe_list:
nfe = nfe_data["nfe"]
self.assertEqual(nfe.state_edoc, SITUACAO_EDOC_A_ENVIAR)
with mock.patch.object(NFe, "make_pdf"):
# enable skip receipt consultation during the send action
self.env.company.nfe_separate_async_process = True
nfe.action_document_send()
# Document has been sent, but the receipt has not been consulted yet,
# meaning the authorization protocol has not been received.
self.assertEqual(nfe.state_edoc, SITUACAO_EDOC_ENVIADA)
# Consult the receipt to receive the usage authorization.
nfe._nfe_consult_receipt()
self.assertEqual(nfe.state_edoc, SITUACAO_EDOC_AUTORIZADA)

@nfe_mock(
{
"nfeAutorizacaoLote": "retEnviNFe/lote_recebido.xml",
"nfeRetAutorizacaoLote": "retConsReciNFe/autorizada.xml",
}
)
@mock.patch("odoo.addons.l10n_br_nfe.models.document._logger")
def test_nfe_consult_receipt_without_nfe_saved(self, mock_logger):
"""
Tests the NF-e processing result query after deleting the sent nfe xml.
"""
for nfe_data in self.nfe_list:
nfe = nfe_data["nfe"]
self.assertEqual(nfe.state_edoc, SITUACAO_EDOC_A_ENVIAR)
with mock.patch.object(NFe, "make_pdf"):
# enable skip receipt consultation during the send action
self.env.company.nfe_separate_async_process = True
nfe.action_document_send()
# Document has been sent, but the receipt has not been consulted yet,
# meaning the authorization protocol has not been received.
self.assertEqual(nfe.state_edoc, SITUACAO_EDOC_ENVIADA)
# Consult the receipt to receive the usage authorization.

# Erase the sending_file
nfe.send_file_id = False
self.assertFalse(nfe.send_file_id)

nfe._nfe_consult_receipt()
mock_logger.info.assert_called_with(
"NF-e data not found when trying to assemble the "
"xml with the authorization protocol (nfeProc)"
)
self.assertEqual(nfe.state_edoc, SITUACAO_EDOC_AUTORIZADA)

@nfe_mock({"nfeConsultaNF": "retConsSitNFe/autorizado.xml"})
def test_nfe_consult(self):
for nfe_data in self.nfe_list:
nfe = nfe_data["nfe"]
self.assertEqual(nfe.state_edoc, SITUACAO_EDOC_A_ENVIAR)
nfe._document_status()
self.assertEqual(nfe.state_edoc, SITUACAO_EDOC_AUTORIZADA)
1 change: 1 addition & 0 deletions l10n_br_nfe/views/nfe_document_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@
position="after"
>
<field name="nfe_environment" />
<field name="nfe_transmission" />
</xpath>
</field>
</record>
Expand Down
12 changes: 12 additions & 0 deletions l10n_br_nfe/views/res_company_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@
<field name="nfe_version" required="1" />
<field name="nfe_environment" required="1" />
<field name="nfe_transmission" required="1" />
<field
name="nfe_enable_sync_transmission"
string="Enable Sync Transmission"
/>
<field
name="nfe_separate_async_process"
string="Separate NF-e Send and Consult"
/>
<field
name="nfe_enable_contingency_ws"
string="Enable contingency web services"
/>
<field name="nfe_danfe_layout" required="1" />
<field name="nfce_danfe_layout" required="1" />
<field
Expand Down
14 changes: 14 additions & 0 deletions l10n_br_nfe/views/res_config_settings_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,20 @@
</div>
</div>
</div>
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="nfe_enable_sync_transmission" />
</div>
<div class="o_setting_right_pane">
<label
for="nfe_enable_sync_transmission"
string="Synchronous Processing"
/>
<div class="text-muted">
Enables synchronous processing in the transmission of the NFe.
</div>
</div>
</div>
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_right_pane">
<span class="o_form_label">DANFE Print Layout</span>
Expand Down

0 comments on commit 57d9d97

Please sign in to comment.