Este módulo contem a estrutura de dados do Manifesto Eletrônico de Documentos Fiscais (CT-e).
+
Este módulo contem a estrutura de dados do Manifesto Eletrônico de Documentos Fiscais (MDF-e).
Este módulo não faz nada sozinho, ele precisaria de um modulo l10n_br_mdfe que mapearia esses mixins
nos documentos fiscais Odoo de forma semlhante a forma como o módulo l10n_br_nfe faz como o módulo l10n_br_nfe_spec.
Maintainers
This module is maintained by the OCA.
-
+
+
+
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
diff --git a/l10n_br_mdfe_spec/tests/test_mdfe_import.py b/l10n_br_mdfe_spec/tests/test_mdfe_import.py
index fd324dde943a..be02bd7d05b4 100644
--- a/l10n_br_mdfe_spec/tests/test_mdfe_import.py
+++ b/l10n_br_mdfe_spec/tests/test_mdfe_import.py
@@ -6,13 +6,12 @@
import nfelib
import pkg_resources
-from nfelib.mdfe.bindings.v3_0.mdfe_v3_00 import Mdfe
-from xsdata.formats.dataclass.parsers import XmlParser
+from nfelib.mdfe.bindings.v3_0.mdfe_v3_00 import Tmdfe
from odoo import api
-from odoo.tests import TransactionCase
+from odoo.tests import SavepointCase
-from ..models import spec_models
+from ..models import spec_mixin
tz_datetime = re.compile(r".*[-+]0[0-9]:00$")
@@ -33,15 +32,12 @@ def build_attrs_fake(self, node, create_m2o=False):
fields = self.fields_get()
vals = self.default_get(fields.keys())
for fname, fspec in node.__dataclass_fields__.items():
- if fname == "any_element":
+ if fname == "any_element": # FIXME in spec_driven_model
continue
value = getattr(node, fname)
if value is None:
continue
- key = "%s%s" % (
- self._field_prefix,
- fspec.metadata.get("name", fname),
- )
+ key = f"mdfe30_{fspec.metadata.get('name', fname)}"
if (
fspec.type == str or not any(["." in str(i) for i in fspec.type.__args__])
) and not str(fspec.type).startswith("typing.List"):
@@ -66,12 +62,8 @@ def build_attrs_fake(self, node, create_m2o=False):
key = fields[key]["related"][0]
comodel_name = fields[key]["relation"]
else:
- clean_type = binding_type.lower() # TODO double check
- comodel_name = "%s.%s.%s" % (
- self._schema_name,
- self._schema_version.replace(".", "")[0:2],
- clean_type.split(".")[-1],
- )
+ clean_type = binding_type.lower()
+ comodel_name = f"mdfe.30.{clean_type.split('.')[-1]}"
comodel = self.env.get(comodel_name)
if comodel is None: # example skip ICMS100 class
continue
@@ -114,13 +106,12 @@ def match_or_create_m2o_fake(self, comodel, new_value, create_m2o=False):
return comodel.new(new_value).id
-# spec_models.CteSpecMixin._update_cache = _update_cache
-spec_models.MdfeSpecMixin.build_fake = build_fake
-spec_models.MdfeSpecMixin.build_attrs_fake = build_attrs_fake
-spec_models.MdfeSpecMixin.match_or_create_m2o_fake = match_or_create_m2o_fake
+spec_mixin.MdfeSpecMixin.build_fake = build_fake
+spec_mixin.MdfeSpecMixin.build_attrs_fake = build_attrs_fake
+spec_mixin.MdfeSpecMixin.match_or_create_m2o_fake = match_or_create_m2o_fake
-class MdfeImportTest(TransactionCase):
+class MdfeImportTest(SavepointCase):
def test_import_mdfe(self):
res_items = (
"mdfe",
@@ -129,13 +120,14 @@ def test_import_mdfe(self):
"41190876676436000167580010000500001000437558-mdfe.xml",
)
resource_path = "/".join(res_items)
- nfe_stream = pkg_resources.resource_stream(nfelib.__name__, resource_path)
- parser = XmlParser()
- binding = parser.from_string(nfe_stream.read().decode(), Mdfe)
- inf_mdfe = self.env["mdfe.30.tmdfe_infmdfe"].build_fake(
- binding.infMDFe, create=False
+ mdfe_stream = pkg_resources.resource_stream(nfelib.__name__, resource_path)
+ binding = Tmdfe.from_xml(mdfe_stream.read().decode())
+ mdfe = (
+ self.env["mdfe.30.tmdfe_infmdfe"]
+ .with_context(tracking_disable=True, edoc_type="in", lang="pt_BR")
+ .build_fake(binding.infMDFe, create=False)
)
- self.assertEqual(inf_mdfe.mdfe30_emit.mdfe30_CNPJ, "76676436000167")
+ self.assertEqual(mdfe.mdfe30_emit.mdfe30_CNPJ, "76676436000167")
def test_import_mdfe2(self):
res_items = (
@@ -145,10 +137,11 @@ def test_import_mdfe2(self):
"50170876063965000276580010000011311421039568-mdfe.xml",
)
resource_path = "/".join(res_items)
- nfe_stream = pkg_resources.resource_stream(nfelib.__name__, resource_path)
- parser = XmlParser()
- binding = parser.from_string(nfe_stream.read().decode(), Mdfe)
- inf_mdfe = self.env["mdfe.30.tmdfe_infmdfe"].build_fake(
- binding.infMDFe, create=False
+ mdfe_stream = pkg_resources.resource_stream(nfelib.__name__, resource_path)
+ binding = Tmdfe.from_xml(mdfe_stream.read().decode())
+ mdfe = (
+ self.env["mdfe.30.tmdfe_infmdfe"]
+ .with_context(tracking_disable=True, edoc_type="in", lang="pt_BR")
+ .build_fake(binding.infMDFe, create=False)
)
- self.assertEqual(inf_mdfe.mdfe30_emit.mdfe30_xNome, "TESTE")
+ self.assertEqual(mdfe.mdfe30_emit.mdfe30_xNome, "TESTE")
diff --git a/l10n_br_nfe/README.rst b/l10n_br_nfe/README.rst
index 1383b66d73e6..51da6f1371bb 100644
--- a/l10n_br_nfe/README.rst
+++ b/l10n_br_nfe/README.rst
@@ -52,6 +52,12 @@ Usage
O menu de NF-e se encontra dentro do menu fiscal.
+Known issues / Roadmap
+======================
+
+* Implementar a transmissão do MDF-e
+* Implementar o cancelamento do MDF-e
+
Bug Tracker
===========
diff --git a/l10n_br_nfe/__manifest__.py b/l10n_br_nfe/__manifest__.py
index d9293aa80765..356ffeeb2af0 100644
--- a/l10n_br_nfe/__manifest__.py
+++ b/l10n_br_nfe/__manifest__.py
@@ -11,7 +11,7 @@
"maintainers": ["rvalyi", "renatonlima"],
"website": "https://github.com/OCA/l10n-brazil",
"development_status": "Beta",
- "version": "14.0.19.0.0",
+ "version": "14.0.19.1.0",
"depends": [
"l10n_br_fiscal_edi",
"l10n_br_fiscal_certificate",
diff --git a/l10n_br_nfe/models/document.py b/l10n_br_nfe/models/document.py
index c854ae327ae8..dda45af340f6 100644
--- a/l10n_br_nfe/models/document.py
+++ b/l10n_br_nfe/models/document.py
@@ -906,7 +906,10 @@ def _serialize(self, edocs):
edocs.append(nfe)
return edocs
- def _processador(self):
+ def _edoc_processor(self):
+ if not self.filtered(filter_processador_edoc_nfe):
+ return super()._edoc_processor()
+
self._check_nfe_environment()
certificado = self.company_id._get_br_ecertificate()
session = Session()
@@ -949,7 +952,7 @@ def _document_export(self, pretty_print=True):
result = super()._document_export()
for record in self.filtered(filter_processador_edoc_nfe):
edoc = record.serialize()[0]
- processador = record._processador()
+ processador = record._edoc_processor()
xml_file = processador.render_edoc_xsdata(edoc, pretty_print=pretty_print)[
0
]
@@ -971,7 +974,7 @@ def _document_export(self, pretty_print=True):
)
record.authorization_event_id = event_id
xml_assinado = processador.assina_raiz(edoc, edoc.infNFe.Id)
- self._valida_xml(xml_assinado)
+ self._validate_xml(xml_assinado)
return result
def _nfe_update_status_and_save_data(self, process):
@@ -1045,8 +1048,12 @@ def _nfe_save_protocol(self, inf_prot, nfe_proc_xml=None):
file_response_xml=nfe_proc_xml,
)
- def _valida_xml(self, xml_file):
+ def _validate_xml(self, xml_file):
self.ensure_one()
+
+ if not self.filtered(filter_processador_edoc_nfe):
+ return super()._validate_xml(xml_file)
+
erros = Nfe.schema_validation(xml_file)
erros = "\n".join(erros)
self.write({"xml_error_message": erros or False})
@@ -1069,7 +1076,12 @@ def _exec_after_SITUACAO_EDOC_AUTORIZADA(self, old_state, new_state):
return super()._exec_after_SITUACAO_EDOC_AUTORIZADA(old_state, new_state)
def _generate_key(self):
- super()._generate_key()
+ if self.document_type_id.code not in [
+ MODELO_FISCAL_NFE,
+ MODELO_FISCAL_NFCE,
+ ]:
+ return super()._generate_key()
+
for record in self.filtered(filter_processador_edoc_nfe):
required_fields_gen_edoc = []
if not record.company_cnpj_cpf:
@@ -1105,7 +1117,7 @@ def _generate_key(self):
def _nfe_consult_receipt(self):
self.ensure_one()
- processor = self._processador()
+ processor = self._edoc_processor()
# Consult receipt and process the response
rec_num = self.authorization_event_id.lot_receipt_number
receipt_process = processor.consulta_recibo(numero=rec_num)
@@ -1158,7 +1170,7 @@ def _nfe_create_proc(self, prot_nfe_element):
)
return None
- processor = self._processador()
+ processor = self._edoc_processor()
# Extract the
tag from the `enviNFe` message, which represents the NF-e
nfe_send_xml = base64.b64decode(self.send_file_id.datas)
@@ -1197,7 +1209,7 @@ def _is_nfe_found(c_stat):
"""
return c_stat in ["100", "101", "110"]
- nfe_manager = self._processador()
+ nfe_manager = self._edoc_processor()
check_response = nfe_manager.consulta_documento(chave=self.document_key)
status = check_response.resposta.xMotivo
@@ -1239,7 +1251,7 @@ def _nfe_send_for_authorization(self):
Serialize and send a NFe for authorizaion
"""
serialized_nfe = self.serialize()[0]
- nfe_manager = self._processador()
+ nfe_manager = self._edoc_processor()
authorization_response = None
for service_response in nfe_manager.processar_documento(serialized_nfe):
if service_response.webservice not in [
@@ -1384,7 +1396,7 @@ def _need_compute_nfe_tags(self):
def _nfe_cancel(self):
self.ensure_one()
- processador = self._processador()
+ processador = self._edoc_processor()
if not self.authorization_protocol:
raise UserError(_("Authorization Protocol Not Found!"))
@@ -1440,7 +1452,7 @@ def _document_correction(self, justificative):
def _nfe_correction(self, justificative):
self.ensure_one()
- processador = self._processador()
+ processador = self._edoc_processor()
numeros = self.event_ids.filtered(
lambda e: e.type == "14" and e.state == "done"
@@ -1500,7 +1512,7 @@ def _update_nfce_for_offline_contingency(self):
def get_nfce_qrcode(self):
if self.document_type != MODELO_FISCAL_NFCE:
return
- processador = self._processador()
+ processador = self._edoc_processor()
if self.nfe_transmission == "1":
return processador.monta_qrcode(self.document_key)
@@ -1512,7 +1524,7 @@ def get_nfce_qrcode_url(self):
if self.document_type != MODELO_FISCAL_NFCE:
return
- return self._processador().consulta_qrcode_url
+ return self._edoc_processor().consulta_qrcode_url
def _prepare_payments_for_nfce(self):
for rec in self.filtered(lambda d: d.document_type == MODELO_FISCAL_NFCE):
diff --git a/l10n_br_nfe/models/document_supplement.py b/l10n_br_nfe/models/document_supplement.py
index a11ded954257..ace5a084b5a5 100644
--- a/l10n_br_nfe/models/document_supplement.py
+++ b/l10n_br_nfe/models/document_supplement.py
@@ -7,10 +7,13 @@
from odoo.addons.spec_driven_model.models import spec_models
-class NFeSupplement(spec_models.SpecModel):
+class NFeSupplement(spec_models.StackedModel):
_name = "l10n_br_fiscal.document.supplement"
_inherit = ["l10n_br_fiscal.document.supplement", "nfe.40.infnfesupl"]
+ _nfe40_odoo_module = "odoo.addons.l10n_br_nfe_spec.models.v4_0.leiaute_nfe_v4_00"
+ _nfe40_stacking_mixin = "nfe.40.infnfesupl"
+
nfe40_qrCode = fields.Char(related="qrcode")
nfe40_urlChave = fields.Char(related="url_key")
diff --git a/l10n_br_nfe/models/invalidate_number.py b/l10n_br_nfe/models/invalidate_number.py
index e4f55023a759..fe45271ae024 100644
--- a/l10n_br_nfe/models/invalidate_number.py
+++ b/l10n_br_nfe/models/invalidate_number.py
@@ -17,7 +17,7 @@
class InvalidateNumber(models.Model):
_inherit = "l10n_br_fiscal.invalidate.number"
- def _processador(self):
+ def _edoc_processor(self):
certificado = self.env.company._get_br_ecertificate()
session = Session()
session.verify = False
@@ -38,7 +38,7 @@ def _processador(self):
return edoc_nfe(**params)
def _invalidate(self, document_id=False):
- processador = self._processador()
+ processador = self._edoc_processor()
evento = processador.inutilizacao(
cnpj=punctuation_rm(self.company_id.cnpj_cpf),
mod=self.document_type_id.code,
diff --git a/l10n_br_nfe/models/product_product.py b/l10n_br_nfe/models/product_product.py
index fff8573fe260..d2f52f5378b0 100644
--- a/l10n_br_nfe/models/product_product.py
+++ b/l10n_br_nfe/models/product_product.py
@@ -7,6 +7,7 @@
class ProductProduct(models.Model):
_inherit = "product.product"
+ _nfe40_odoo_module = "odoo.addons.l10n_br_nfe_spec.models.v4_0.leiaute_nfe_v4_00"
_nfe_search_keys = ["default_code", "barcode"]
def match_or_create_m2o(self, rec_dict, parent_dict, model=None):
diff --git a/l10n_br_nfe/readme/ROADMAP.rst b/l10n_br_nfe/readme/ROADMAP.rst
index e69de29bb2d1..228ab3230f08 100644
--- a/l10n_br_nfe/readme/ROADMAP.rst
+++ b/l10n_br_nfe/readme/ROADMAP.rst
@@ -0,0 +1,2 @@
+* Implementar a transmissão do MDF-e
+* Implementar o cancelamento do MDF-e
diff --git a/l10n_br_nfe/static/description/index.html b/l10n_br_nfe/static/description/index.html
index ed908c0f4856..5bb6f46d27b0 100644
--- a/l10n_br_nfe/static/description/index.html
+++ b/l10n_br_nfe/static/description/index.html
@@ -392,11 +392,12 @@ NF-e
+
+
+
+- Implementar a transmissão do MDF-e
+- Implementar o cancelamento do MDF-e
+
+
-
+
Bugs are tracked on GitHub Issues.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
@@ -414,16 +422,16 @@
Do not contact contributors directly about support or help with technical issues.
-
+
-
+
This module is maintained by the OCA.
diff --git a/l10n_br_nfse_focus/__manifest__.py b/l10n_br_nfse_focus/__manifest__.py
index 3d72e52c9d4b..8df79efd9820 100644
--- a/l10n_br_nfse_focus/__manifest__.py
+++ b/l10n_br_nfse_focus/__manifest__.py
@@ -5,7 +5,7 @@
"name": "NFS-e (FocusNFE)",
"summary": """
NFS-e (FocusNFE)""",
- "version": "14.0.1.2.1",
+ "version": "14.0.1.2.2",
"license": "AGPL-3",
"author": "KMEE, Escodoo, Odoo Community Association (OCA)",
"maintainers": [
diff --git a/l10n_br_nfse_focus/tests/test_l10n_br_nfse_focus.py b/l10n_br_nfse_focus/tests/test_l10n_br_nfse_focus.py
index e0346143eeda..285b15d8fc59 100644
--- a/l10n_br_nfse_focus/tests/test_l10n_br_nfse_focus.py
+++ b/l10n_br_nfse_focus/tests/test_l10n_br_nfse_focus.py
@@ -380,38 +380,27 @@ def test_serialize(self):
def test_document_export(self):
"""Tests export of document data."""
- record = self.nfse_demo
- record.processador_edoc = PROCESSADOR_OCA # Setting document processor to OCA
- record.document_type_id.code = MODELO_FISCAL_NFE # Setting document type to NFe
# Testing with non-filtered conditions
record = self.nfse_demo
- record.company_id.provedor_nfse = None # Resetting NFSe provider
+ record.processador_edoc = PROCESSADOR_OCA
+ record.company_id.provedor_nfse = None
- record._document_export() # Exporting document data
+ record.action_document_confirm()
- self.assertFalse(
- record.company_id.provedor_nfse
- ) # Asserting NFSe provider not set
+ self.assertFalse(record.company_id.provedor_nfse)
+
+ self.assertEqual(record.state, SITUACAO_EDOC_A_ENVIAR)
# Testing with filtered conditions
record = self.nfse_demo
- record.company_id.provedor_nfse = (
- "focusnfe" # Setting NFSe provider to focusnfe
- )
- record.processador_edoc = PROCESSADOR_OCA # Setting processor to OCA
- record.document_type_id.code = (
- MODELO_FISCAL_NFSE # Setting document type to NFSe
- )
+ record.company_id.provedor_nfse = "focusnfe"
+
+ record._document_export()
- record._document_export() # Exporting document data again
+ self.assertTrue(record.company_id.provedor_nfse)
- self.assertTrue(
- record.company_id.provedor_nfse
- ) # Asserting NFSe provider is set
- self.assertTrue(
- record.authorization_event_id
- ) # Asserting authorization event is set
+ self.assertTrue(record.authorization_event_id)
@patch(
"odoo.addons.l10n_br_nfse_focus.models.document.FocusnfeNfse.query_focus_nfse_by_rps"
diff --git a/setup/l10n_br_mdfe/odoo/addons/l10n_br_mdfe b/setup/l10n_br_mdfe/odoo/addons/l10n_br_mdfe
new file mode 120000
index 000000000000..4b3c90b7dcb2
--- /dev/null
+++ b/setup/l10n_br_mdfe/odoo/addons/l10n_br_mdfe
@@ -0,0 +1 @@
+../../../../l10n_br_mdfe
\ No newline at end of file
diff --git a/setup/l10n_br_mdfe/setup.py b/setup/l10n_br_mdfe/setup.py
new file mode 100644
index 000000000000..28c57bb64031
--- /dev/null
+++ b/setup/l10n_br_mdfe/setup.py
@@ -0,0 +1,6 @@
+import setuptools
+
+setuptools.setup(
+ setup_requires=['setuptools-odoo'],
+ odoo_addon=True,
+)