diff --git a/l10n_br_cte/README.rst b/l10n_br_cte/README.rst new file mode 100644 index 000000000000..caafff26c57b --- /dev/null +++ b/l10n_br_cte/README.rst @@ -0,0 +1,113 @@ +==== +CT-e +==== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:c91616235e33e68d0115aa3807f25142a45f5013a23492f240cf507a41d41340 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fl10n--brazil-lightgray.png?logo=github + :target: https://github.com/OCA/l10n-brazil/tree/14.0/l10n_br_cte + :alt: OCA/l10n-brazil +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/l10n-brazil-14-0/l10n-brazil-14-0-l10n_br_cte + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/l10n-brazil&target_branch=14.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +[ This file must be max 2-3 paragraphs, and is required. ] + +This module extends the functionality of ... to support ... +and to allow you to ... + +.. IMPORTANT:: + This is an alpha version, the data model and design can change at any time without warning. + Only for development or testing purpose, do not use in production. + `More details on development status `_ + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +[ This file is optional, it should explain how to configure + the module before using it; it is aimed at advanced users. ] + +To configure this module, you need to: + +#. Go to ... + +.. figure:: https://raw.githubusercontent.com/OCA/l10n-brazil/14.0/l10n_br_cte/static/description/image.png + :alt: alternative description + :width: 600 px + +Usage +===== + +[ This file must be present and contains the usage instructions + for end-users. As all other rst files included in the README, + it MUST NOT contain reStructuredText sections + only body text (paragraphs, lists, tables, etc). Should you need + a more elaborate structure to explain the addon, please create a + Sphinx documentation (which may include this file as a "quick start" + section). ] + +To use this module, you need to: + +#. Go to ... + +Bug Tracker +=========== + +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 +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* KMEE + +Contributors +~~~~~~~~~~~~ + +* Ygor Carvalho + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +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. + +This module is part of the `OCA/l10n-brazil `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/l10n_br_cte/__init__.py b/l10n_br_cte/__init__.py new file mode 100644 index 000000000000..cc6b6354ad8f --- /dev/null +++ b/l10n_br_cte/__init__.py @@ -0,0 +1,2 @@ +from . import models +from .hooks import post_init_hook diff --git a/l10n_br_cte/__manifest__.py b/l10n_br_cte/__manifest__.py new file mode 100644 index 000000000000..285b92121a39 --- /dev/null +++ b/l10n_br_cte/__manifest__.py @@ -0,0 +1,43 @@ +# Copyright 2023 KMEE +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "CT-e", + "summary": """Brazilian Electronic Invoice CT-e""", + "version": "14.0.1.0.0", + "category": "Localisation", + "license": "AGPL-3", + "author": "KMEE, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/l10n-brazil", + "development_status": "Alpha", + "depends": [ + "l10n_br_fiscal_edi", + "l10n_br_cte_spec", + "l10n_br_fiscal_certificate", + "spec_driven_model", + ], + "data": [ + "security/ir.model.access.csv", + # "views/document_line.xml", + # 'views/document_related.xml', + # 'views/res_partner.xml', + "modal/modal_rodoviario.xml", + "modal/modal_aquaviario.xml", + "modal/modal_ferroviario.xml", + "modal/modal_aereo.xml", + "views/res_company.xml", + "views/cte_document.xml", + "wizards/document_correction_wizard.xml", + ], + # "post_init_hook": "post_init_hook", + "installable": True, + "auto_install": False, + "external_dependencies": { + "python": [ + "nfelib<=2.0.7", + "erpbrasil.assinatura>=1.7.0", + "erpbrasil.transmissao>=1.1.0", + "erpbrasil.edoc>=2.5.2", + ], + }, +} diff --git a/l10n_br_cte/constants/modal.py b/l10n_br_cte/constants/modal.py new file mode 100644 index 000000000000..42dcd1d85a53 --- /dev/null +++ b/l10n_br_cte/constants/modal.py @@ -0,0 +1,32 @@ +CTE_MODAL_VERSION_DEFAULT = "4.00" + +TUF = [ + ("AC", "AC"), + ("AL", "AL"), + ("AM", "AM"), + ("AP", "AP"), + ("BA", "BA"), + ("CE", "CE"), + ("DF", "DF"), + ("ES", "ES"), + ("GO", "GO"), + ("MA", "MA"), + ("MG", "MG"), + ("MS", "MS"), + ("MT", "MT"), + ("PA", "PA"), + ("PB", "PB"), + ("PE", "PE"), + ("PI", "PI"), + ("PR", "PR"), + ("RJ", "RJ"), + ("RN", "RN"), + ("RO", "RO"), + ("RR", "RR"), + ("RS", "RS"), + ("SC", "SC"), + ("SE", "SE"), + ("SP", "SP"), + ("TO", "TO"), + ("EX", "EX"), +] diff --git a/l10n_br_cte/hooks.py b/l10n_br_cte/hooks.py new file mode 100644 index 000000000000..5ea32d247dce --- /dev/null +++ b/l10n_br_cte/hooks.py @@ -0,0 +1,42 @@ +# Copyright (C) 2019-2020 - Raphael Valyi Akretion +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html +import logging + +import nfelib +import pkg_resources +from nfelib.cte.bindings.v4_0.cte_tipos_basico_v4_00 import Tcte + +from odoo import SUPERUSER_ID, api +from odoo.exceptions import ValidationError + +_logger = logging.getLogger(__name__) + + +def post_init_hook(cr, registry): + env = api.Environment(cr, SUPERUSER_ID, {}) + cr.execute("select demo from ir_module_module where name='l10n_br_cte';") + is_demo = cr.fetchone()[0] + if is_demo: + res_items = ( + "cte", + "samples", + "v4_0", + "43120178408960000182570010000000041000000047-cte.xml", + ) + resource_path = "/".join(res_items) + doc_stream = pkg_resources.resource_stream(nfelib.__name__, resource_path) + binding = Tcte.from_xml(doc_stream.read().decode()) + document_number = binding.infCte.ide.nCT + existing_docs = env["l10n_br_fiscal.document"].search( + [("document_number", "=", document_number)] + ) + try: + existing_docs.unlink() + doc = ( + env["cte.40.tcte_infcte"] + .with_context(tracking_disable=True, edoc_type="in") + .build_from_binding("cte", "40", binding.infCte) + ) + _logger.info(doc.cte40_emit.cte40_CNPJ) + except ValidationError: + _logger.info(f"CTE-e already {document_number} imported by hooks") diff --git a/l10n_br_cte/modal/modal_aereo.xml b/l10n_br_cte/modal/modal_aereo.xml new file mode 100644 index 000000000000..eaa86fccea46 --- /dev/null +++ b/l10n_br_cte/modal/modal_aereo.xml @@ -0,0 +1,21 @@ + + + + + modal.aereo.peri.form.view (in l10n_br_cte) + l10n_br_cte.modal.aereo.peri + +
+ + + + + + + + +
+
+
+
diff --git a/l10n_br_cte/modal/modal_aquaviario.xml b/l10n_br_cte/modal/modal_aquaviario.xml new file mode 100644 index 000000000000..28933d4a09eb --- /dev/null +++ b/l10n_br_cte/modal/modal_aquaviario.xml @@ -0,0 +1,18 @@ + + + + + modal.aquaviario.balsa.form.view (in l10n_br_cte) + l10n_br_cte.modal.aquav.balsa + +
+ + + + + +
+
+
+
diff --git a/l10n_br_cte/modal/modal_ferroviario.xml b/l10n_br_cte/modal/modal_ferroviario.xml new file mode 100644 index 000000000000..2c11907ab735 --- /dev/null +++ b/l10n_br_cte/modal/modal_ferroviario.xml @@ -0,0 +1,51 @@ + + + + + res.partner.ferroenv.form.view (in l10n_br_cte) + res.partner + +
+ + + + + + + + + +
+
+
+ + + res.partner.tenderfer.form.view + res.partner + +
+ + + + + + + + + + + + +
+
+
+
diff --git a/l10n_br_cte/modal/modal_rodoviario.xml b/l10n_br_cte/modal/modal_rodoviario.xml new file mode 100644 index 000000000000..338957199239 --- /dev/null +++ b/l10n_br_cte/modal/modal_rodoviario.xml @@ -0,0 +1,25 @@ + + + + + modal.rodoviario.occ.form.view (in l10n_br_cte) + l10n_br_cte.modal.rodo.occ + +
+ + + + + + + + + + + + +
+
+
+
diff --git a/l10n_br_cte/models/__init__.py b/l10n_br_cte/models/__init__.py new file mode 100644 index 000000000000..2082b680910c --- /dev/null +++ b/l10n_br_cte/models/__init__.py @@ -0,0 +1,19 @@ +from . import document +from . import res_company +from . import res_partner +from . import document_related +from . import document_line +from . import res_config_settings +from . import ferroviario +from . import rodoviario +from . import aereo +from . import dutoviario +from . import aquaviario +from . import document_cargo_quantity_infos +from . import document_supplement +from . import document_transported_vehicles +from . import normal_cte_infos +from . import document_comment + +spec_schema = "cte" +spec_version = "40" diff --git a/l10n_br_cte/models/aereo.py b/l10n_br_cte/models/aereo.py new file mode 100644 index 000000000000..f5f69c888dba --- /dev/null +++ b/l10n_br_cte/models/aereo.py @@ -0,0 +1,63 @@ +# Copyright 2023 KMEE INFORMATICA LTDA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + + +from odoo import fields + +from odoo.addons.spec_driven_model.models import spec_models + + +class Aereo(spec_models.StackedModel): + _name = "l10n_br_cte.modal.aereo" + _inherit = "cte.40.aereo" + _description = "Modal Aereo CTe" + + _cte40_stacking_mixin = "cte.40.aereo" + _cte40_odoo_module = ( + "odoo.addons.l10n_br_cte_spec.models.v4_0.cte_modal_aereo_v4_00" + ) + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + cte40_nMinu = fields.Char(related="document_id.cte40_nMinu") + + cte40_nOCA = fields.Char(related="document_id.cte40_nOCA") + + cte40_dPrevAereo = fields.Date(related="document_id.cte40_dPrevAereo") + + cte40_CL = fields.Char(related="document_id.cte40_CL") + + cte40_cTar = fields.Char(related="document_id.cte40_cTar") + + cte40_vTar = fields.Monetary(related="document_id.cte40_aereo_vTar") + + cte40_xDime = fields.Char(related="document_id.cte40_xDime") + + cte40_peri = fields.One2many(related="document_id.cte40_peri") + + def _prepare_dacte_values(self): + if not self: + return {} + + +class Peri(spec_models.StackedModel): + _name = "l10n_br_cte.modal.aereo.peri" + _inherit = "cte.40.peri" + _description = """Preenchido quando for transporte de produtos classificados pela + ONU como perigosos. O preenchimento desses campos não desobriga a empresa aérea de + emitir os demais documentos que constam na legislação vigente.""" + + _cte40_stacking_mixin = "cte.40.peri" + _cte40_odoo_module = ( + "odoo.addons.l10n_br_cte_spec.models.v4_0.cte_modal_aereo_v4_00" + ) + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + cte40_nONU = fields.Char(required=True) + + cte40_qTotEmb = fields.Char(required=True) + + cte40_qTotProd = fields.Float(required=True) + + cte40_uniAP = fields.Selection(required=True) diff --git a/l10n_br_cte/models/aquaviario.py b/l10n_br_cte/models/aquaviario.py new file mode 100644 index 000000000000..be9f52967d74 --- /dev/null +++ b/l10n_br_cte/models/aquaviario.py @@ -0,0 +1,51 @@ +# Copyright 2023 KMEE INFORMATICA LTDA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields + +from odoo.addons.spec_driven_model.models import spec_models + + +class Aquav(spec_models.StackedModel): + _name = "l10n_br_cte.modal.aquav" + _inherit = "cte.40.aquav" + _description = "Modal Aquaviário CTe" + + _cte40_stacking_mixin = "cte.40.aquav" + _cte40_odoo_module = ( + "odoo.addons.l10n_br_cte_spec.models.v4_0.cte_modal_aquaviario_v4_00" + ) + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + cte40_vAFRMM = fields.Monetary(related="document_id.cte40_vAFRMM") + + cte40_vPrest = fields.Monetary( + related="document_id.cte40_vTPrest" + ) # TODO: avaliar melhor + + cte40_xNavio = fields.Char(related="document_id.cte40_xNavio") + + cte40_nViag = fields.Char(related="document_id.cte40_nViag") + + cte40_direc = fields.Selection(related="document_id.cte40_direc") + + cte40_irin = fields.Char(related="document_id.cte40_irin") + + cte40_tpNav = fields.Selection(related="document_id.cte40_tpNav") + + cte40_balsa = fields.One2many(related="document_id.cte40_balsa") + + def _prepare_dacte_values(self): + if not self: + return {} + + +class Balsa(spec_models.SpecModel): + _name = "l10n_br_cte.modal.aquav.balsa" + _inherit = "cte.40.balsa" + _description = "Grupo de informações das balsas" + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + cte40_xBalsa = fields.Char(string="Identificador da Balsa") diff --git a/l10n_br_cte/models/document.py b/l10n_br_cte/models/document.py new file mode 100644 index 000000000000..f2945c66e594 --- /dev/null +++ b/l10n_br_cte/models/document.py @@ -0,0 +1,1551 @@ +# Copyright 2023 KMEE INFORMATICA LTDA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import base64 +import logging +import re +import sys +from datetime import datetime + +from erpbrasil.edoc.cte import TransmissaoCTE +from lxml import etree +from nfelib.cte.bindings.v4_0.cte_v4_00 import Cte +from nfelib.cte.bindings.v4_0.proc_cte_v4_00 import CteProc +from nfelib.nfe.ws.edoc_legacy import CTeAdapter as edoc_cte +from requests import Session +from xsdata.formats.dataclass.parsers import XmlParser + +from odoo import _, api, fields +from odoo.exceptions import UserError + +from odoo.addons.l10n_br_cte_spec.models.v4_0.cte_modal_ferroviario_v4_00 import ( + FERROV_TPTRAF, + TRAFMUT_FERREMI, + TRAFMUT_RESPFAT, +) +from odoo.addons.l10n_br_cte_spec.models.v4_0.cte_tipos_basico_v4_00 import ( + COMDATA_TPPER, + SEMHORA_TPHOR, +) +from odoo.addons.l10n_br_fiscal.constants.fiscal import ( + AUTORIZADO, + CANCELADO, + CANCELADO_DENTRO_PRAZO, + CANCELADO_FORA_PRAZO, + DENEGADO, + DOCUMENT_ISSUER_COMPANY, + EVENT_ENV_HML, + EVENT_ENV_PROD, + EVENTO_RECEBIDO, + LOTE_PROCESSADO, + PROCESSADOR_OCA, + SITUACAO_EDOC_A_ENVIAR, + SITUACAO_EDOC_AUTORIZADA, + SITUACAO_EDOC_CANCELADA, + SITUACAO_EDOC_DENEGADA, + SITUACAO_EDOC_EM_DIGITACAO, + SITUACAO_EDOC_REJEITADA, + SITUACAO_FISCAL_CANCELADO, + SITUACAO_FISCAL_CANCELADO_EXTEMPORANEO, +) +from odoo.addons.l10n_br_fiscal.constants.icms import ICMS_CST, ICMS_SN_CST +from odoo.addons.spec_driven_model.models import spec_models + +from ..constants.modal import CTE_MODAL_VERSION_DEFAULT + +CTE_XML_NAMESPACE = {"cte": "http://www.portalfiscal.inf.br/cte"} + +# TODO: https://github.com/Engenere/BrazilFiscalReport/pull/23 +# from brazilfiscalreport.dacte import Dacte + + +_logger = logging.getLogger(__name__) +try: + pass +except ImportError: + _logger.error("Biblioteca erpbrasil.base não in stalada") + + +def filter_processador_edoc_cte(record): + if record.processador_edoc == "oca" and record.document_type_id.code in [ + "57", + "67", + ]: + return True + return False + + +class CTe(spec_models.StackedModel): + _name = "l10n_br_fiscal.document" + _inherit = [ + "l10n_br_fiscal.document", + "cte.40.tcte_infcte", + "cte.40.tcte_imp", + "cte.40.tcte_fat", + ] + _cte40_odoo_module = ( + "odoo.addons.l10n_br_cte_spec.models.v4_0.cte_tipos_basico_v4_00" + ) + _cte40_stacking_mixin = "cte.40.tcte_infcte" + _cte40_stacking_skip_paths = ( + "cte40_fluxo", + "cte40_semData", + "cte40_noInter", + "cte40_comHora", + "cte40_noPeriodo", + ) + # all m2o at this level will be stacked even if not required: + _cte40_stacking_force_paths = ( + "infcte.compl", + "infcte.compl.entrega" "infcte.vprest", + "infcte.imp", + ) + _cte_search_keys = ["cte40_Id"] + + INFCTE_TREE = """ + > infCte + > + > res.partner + > + > + - + ≡ + ≡ + + > res.company + > res.company + > res.partner + > res.partner + > + ≡ + > + > + > + > + > + ≡ + > + ≡ + ≡ + ≡ + - > + > + > + > """ + + ########################## + # CT-e spec related fields + ########################## + + ########################## + # CT-e tag: infCte + ########################## + + cte40_versao = fields.Char(related="document_version") + + cte40_Id = fields.Char( + compute="_compute_cte40_Id", + inverse="_inverse_cte40_Id", + ) + + ########################## + # CT-e tag: Id + # Methods + ########################## + + @api.depends("document_type_id", "document_key") + def _compute_cte40_Id(self): + for record in self.filtered(filter_processador_edoc_cte): + if ( + record.document_type_id + and record.document_type_id.prefix + and record.document_key + ): + record.cte40_Id = "{}{}".format( + record.document_type_id.prefix, record.document_key + ) + else: + record.cte40_Id = False + + def _inverse_cte40_Id(self): + for record in self: + if record.cte40_Id: + record.document_key = re.findall(r"\d+", str(record.cte40_Id))[0] + + ########################## + # CT-e tag: ide + ########################## + + cte40_cUF = fields.Char( + related="company_id.partner_id.state_id.ibge_code", + string="cte40_cUF", + ) + + cte40_cCT = fields.Char(compute="_compute_cct") + + cte40_CFOP = fields.Char(compute="_compute_CFOP", store=True) + + cte40_natOp = fields.Char(related="operation_name") + + cte40_mod = fields.Char(related="document_type_id.code", string="cte40_mod") + + cte40_serie = fields.Char(related="document_serie") + + cte40_nCT = fields.Char(related="document_number") + + cte40_dhEmi = fields.Datetime(related="document_date") + + cte40_cDV = fields.Char(compute="_compute_cDV", store=True) + + cte40_procEmi = fields.Selection(default="0") + + cte40_verProc = fields.Char( + copy=False, + default=lambda s: s.env["ir.config_parameter"] + .sudo() + .get_param("l10n_br_cte.version.name", default="Odoo Brasil OCA v14"), + ) + + cte40_cMunEnv = fields.Char(compute="_compute_cte40_data", store=True) + + cte40_xMunEnv = fields.Char(compute="_compute_cte40_data", store=True) + + cte40_UFEnv = fields.Char(compute="_compute_cte40_data", store=True) + + cte40_indIEToma = fields.Selection( + selection=[ + ("1", "Contribuinte ICMS"), + ("2", "Contribuinte isento de inscrição"), + ("9", "Não Contribuinte"), + ], + default="1", + ) + + cte40_cMunIni = fields.Char(compute="_compute_cte40_data") + + cte40_xMunIni = fields.Char(compute="_compute_cte40_data") + + cte40_UFIni = fields.Char(compute="_compute_cte40_data") + + cte40_cMunFim = fields.Char(compute="_compute_cte40_data") + + cte40_xMunFim = fields.Char(compute="_compute_cte40_data") + + cte40_UFFim = fields.Char(compute="_compute_cte40_data") + + cte40_retira = fields.Selection(selection=[("0", "Sim"), ("1", "Não")], default="1") + + cte40_tpServ = fields.Selection( + selection=[ + ("0", "Normal"), + ("1", "Subcontratação"), + ("2", "Redespacho"), + ("3", "Redespacho Intermediário"), + ], + default="0", + ) + + cte40_tpCTe = fields.Selection( + selection=[ + ("0", "CTe Normal"), + ("1", "CTe Complementar"), + ("3", "CTe Substituição"), + ], + default="0", + ) + + cte40_tpAmb = fields.Selection( + selection=[("1", "Produção"), ("2", "Homologação")], + string="CTe Environment", + copy=False, + default="2", + ) + + cte40_tpEmis = fields.Selection( + selection=[ + ("1", "Normal"), + ("3", "Regime Especial NFF"), + ("4", "EPEC pela SVC"), + ], + default="1", + ) + + cte40_tpImp = fields.Selection( + selection=[("1", "Retrato"), ("2", "Paisagem")], default="1" + ) + + def _export_fields_cte_40_toma3(self, xsd_fields, class_obj, export_dict): + if self.cte40_choice_toma == "cte40_toma4": + xsd_fields.remove("cte40_toma") + + def _export_fields_cte_40_tcte_toma4(self, xsd_fields, class_obj, export_dict): + if self.cte40_choice_toma == "cte40_toma3": + xsd_fields.remove("cte40_toma") + xsd_fields.remove("cte40_CNPJ") + xsd_fields.remove("cte40_CPF") + xsd_fields.remove("cte40_IE") + xsd_fields.remove("cte40_xNome") + xsd_fields.remove("cte40_xFant") + xsd_fields.remove("cte40_enderToma") + + # toma + cte40_choice_toma = fields.Selection( + selection=[ + ("cte40_toma3", "toma3"), + ("cte40_toma4", "toma4"), + ], + compute="_compute_toma", + store=True, + ) + + cte40_toma = fields.Selection(related="service_provider") + + cte40_enderToma = fields.Many2one(comodel_name="res.partner", related="partner_id") + + ########################## + # CT-e tag: ide + # Compute Methods + ########################## + + @api.depends("service_provider") + def _compute_toma(self): + for doc in self: + if doc.service_provider in ["0", "1", "2", "3"]: + doc.cte40_choice_toma = "cte40_toma3" + else: + doc.cte40_choice_toma = "cte40_toma4" + + @api.depends("fiscal_line_ids") + def _compute_CFOP(self): + for rec in self: + if rec.fiscal_line_ids: + rec.cte40_CFOP = rec.fiscal_line_ids[0].cfop_id.code + + @api.depends("document_key") + def _compute_cDV(self): + for rec in self: + if rec.document_key: + rec.cte40_cDV = rec.document_key[-1] + + def _compute_cct(self): + for rec in self: + if rec.document_key: + rec.cte40_cCT = rec.document_key[35:43] + + @api.depends( + "partner_id", + "company_id", + "cte40_rem", + "cte40_dest", + "cte40_exped", + "cte40_receb", + ) + def _compute_cte40_data(self): + for doc in self: + if doc.company_id.partner_id.country_id == doc.partner_id.country_id: + doc.cte40_xMunEnv = ( + doc.company_id.partner_id.city_id.name + ) # TODO: provavelmente vai depender de quem é o emissor + doc.cte40_cMunEnv = doc.company_id.partner_id.city_id.ibge_code + doc.cte40_UFEnv = doc.company_id.partner_id.state_id.code + doc.cte40_xMunIni = ( + doc.cte40_exped.city_id.name or doc.cte40_rem.city_id.name + ) + doc.cte40_cMunIni = ( + doc.cte40_exped.city_id.ibge_code or doc.cte40_rem.city_id.ibge_code + ) + doc.cte40_UFIni = ( + doc.cte40_exped.state_id.code or doc.cte40_rem.state_id.code + ) + doc.cte40_xMunFim = ( + doc.cte40_receb.city_id.name or doc.cte40_dest.city_id.name + ) + doc.cte40_cMunFim = ( + doc.cte40_receb.city_id.ibge_code + or doc.cte40_dest.city_id.ibge_code + ) + doc.cte40_UFFim = ( + doc.cte40_receb.state_id.code or doc.cte40_dest.state_id.code + ) + else: + doc.cte40_UFIni = "EX" + doc.cte40_UFEnv = "EX" + doc.cte40_xMunIni = "EXTERIOR" + doc.cte40_cMunIni = "9999999" + doc.cte40_xMunEnv = ( + doc.company_id.partner_id.country_id.name + + "/" + + doc.company_id.partner_id.city_id.name + ) + doc.cte40_cMunEnv = "9999999" + doc.cte40_cMunFim = "9999999" + doc.cte40_xMunFim = "EXTERIOR" + doc.cte40_UFFim = "EX" + + ########################## + # CT-e tag: compl + ########################## + + cte40_xObs = fields.Text(compute="_compute_cte40_compl") + cte40_obsCont = fields.One2many( + "l10n_br_fiscal.comment", compute="_compute_cte40_obsCont" + ) + + cte40_obsFisco = fields.One2many( + "l10n_br_fiscal.comment", compute="_compute_cte40_obsCont" + ) + + ########################## + # CT-e tag: compl + # Methods + ########################## + + @api.depends("comment_ids") + def _compute_cte40_obsCont(self): + for doc in self: + doc.cte40_obsCont = doc.comment_ids.filtered( + lambda c: c.comment_type == "commercial" + ) + doc.cte40_obsFisco = doc.comment_ids.filtered( + lambda c: c.comment_type == "fiscal" + ) + + def _compute_cte40_compl(self): + for doc in self: + fiscal_data = ( + doc.fiscal_additional_data if doc.fiscal_additional_data else "" + ) + customer_data = ( + doc.customer_additional_data if doc.customer_additional_data else "" + ) + doc.cte40_xObs = (fiscal_data + " " + customer_data)[:256].strip() + + ########################## + # CT-e tag: entrega + ########################## + + # TODO: pensar em algo genericoom base nisso decidir quais tags + # puxar (comData,semData,noPeriodo...) + cte40_tpPer = fields.Selection( + selection=COMDATA_TPPER, string="Tipo de data/período programado", default="2" + ) + cte40_dProg = fields.Date("Data Programada", default=fields.Date.today) + + cte40_tpHor = fields.Selection(SEMHORA_TPHOR, string="Tipo de hora", default="0") + + ########################## + # CT-e tag: emit + ########################## + + cte40_CNPJ = fields.Char( + compute="_compute_emit_data", + ) + cte40_CPF = fields.Char( + compute="_compute_emit_data", + ) + cte40_IE = fields.Char( + compute="_compute_emit_data", + ) + cte40_xNome = fields.Char( + compute="_compute_emit_data", + ) + cte40_xFant = fields.Char( + compute="_compute_emit_data", + ) + cte40_enderEmit = fields.Many2one( + comodel_name="res.partner", compute="_compute_emit_data" + ) + + cte40_CRT = fields.Selection( + compute="_compute_emit_data", + ) + + ########################## + # CT-e tag: emit + # Compute Methods + ########################## + + @api.depends("company_id", "partner_id", "issuer") + def _compute_emit_data(self): + for doc in self: + if doc.issuer == DOCUMENT_ISSUER_COMPANY: + doc.cte40_CNPJ = doc.company_id.partner_id.cte40_CNPJ + doc.cte40_CPF = doc.company_id.partner_id.cte40_CPF + doc.cte40_IE = doc.company_id.partner_id.cte40_IE + doc.cte40_xNome = doc.company_id.partner_id.legal_name + doc.cte40_xFant = doc.company_id.partner_id.name + doc.cte40_enderEmit = doc.company_id.partner_id + doc.cte40_CRT = doc.company_tax_framework + else: + doc.cte40_CNPJ = doc.partner_id.cte40_CNPJ + doc.cte40_CPF = doc.partner_id.cte40_CPF + doc.cte40_IE = doc.partner_id.cte40_IE + doc.cte40_xNome = doc.partner_id.legal_name + doc.cte40_xFant = doc.partner_id.name + doc.cte40_enderEmit = doc.partner_id + doc.cte40_CRT = doc.partner_tax_framework + + ########################## + # CT-e tag: rem + ########################## + + cte40_rem = fields.Many2one( + comodel_name="res.partner", + string="Remetente", + ) + + ########################## + # CT-e tag: exped + ########################## + + cte40_exped = fields.Many2one( + comodel_name="res.partner", + string="Expedidor", + ) + + ########################## + # CT-e tag: dest + ########################## + + cte40_dest = fields.Many2one( + comodel_name="res.partner", string="Destinatário", related="partner_shipping_id" + ) + + ########################## + # CT-e tag: receb + ########################## + + cte40_receb = fields.Many2one( + comodel_name="res.partner", + string="Recebedor", + ) + + ########################## + # CT-e tag: vPrest + # Methods + ########################## + + cte40_vTPrest = fields.Monetary( + compute="_compute_cte40_vPrest", + string="Valor da Total Prestação Base de Cálculo", + ) + + cte40_vRec = fields.Monetary( + compute="_compute_cte40_vPrest", + string="Valor Recebido", + ) + + cte40_comp = fields.One2many( + comodel_name="l10n_br_fiscal.document.line", + inverse_name="document_id", + related="fiscal_line_ids", + ) + + def _compute_cte40_vPrest(self): + vTPrest = 0 + vRec = 0 + for doc in self: + for line in self.fiscal_line_ids: + vTPrest += line.amount_total + vRec += line.price_gross + doc.cte40_vTPrest = vTPrest + doc.cte40_vRec = vRec + + ################################################## + # CT-e tag: ICMS + # Grupo N01. Grupo Tributação do ICMS= 00 + # Grupo N02. Grupo Tributação do ICMS= 20 + # Grupo N03. Grupo Tributação do ICMS= 45 (40, 41 e 51) + # Grupo N04. Grupo Tributação do ICMS= 60 + # Grupo N05. Grupo Tributação do ICMS= 90 - ICMS outros + # Grupo N06. Grupo Tributação do ICMS= 90 - ICMS Outra UF + # Grupo N06. Grupo Tributação do ICMS= 01 - ISSN + ################################################# + + cte40_vTotTrib = fields.Monetary(related="amount_estimate_tax") + + # cte40_infAdFisco = fields.Text(related="additional_data") + + ################################################## + # CT-e tag: ICMS + # Methods + ################################################## + + cte40_choice_icms = fields.Selection( + selection=[ + ("cte40_ICMS00", "ICMS00"), + ("cte40_ICMS20", "ICMS20"), + ("cte40_ICMS45", "ICMS45"), + ("cte40_ICMS60", "ICMS60"), + ("cte40_ICMS90", "ICMS90"), + ("cte40_ICMSOutraUF", "ICMSOutraUF"), + ("cte40_ICMSSN", "ICMSSN"), + ], + string="Tipo de ICMS", + compute="_compute_choice_icms", + store=True, + ) + + cte40_CST = fields.Selection( + selection=[ + ("00", "00 - Tributação normal ICMS"), + ("20", "20 - Tributação com BC reduzida do ICMS"), + ("45", "45 - ICMS Isento, não Tributado ou diferido"), + ("60", "60 - ICMS cobrado por substituição tributária"), + ("90", "90 - ICMS outros"), + ("90", "90 - ICMS Outra UF"), + ("01", "01 - Simples Nacional"), + ], + string="Classificação Tributária do Serviço", + compute="_compute_choice_icms", + store=True, + ) + + # ICMSSN + cte40_indSN = fields.Float(default=1) + + # # ICMSOutraUF + # # TODO + + ########################## + # CT-e tag: ICMS + # Compute Methods + ########################## + + @api.depends("fiscal_line_ids") + def _compute_choice_icms(self): + for record in self: + record.cte40_choice_icms = None + record.cte40_CST = None + if not record.fiscal_line_ids: + continue + if record.fiscal_line_ids[0].icms_cst_id.code in ICMS_CST: + if record.fiscal_line_ids[0].icms_cst_id.code in ["40", "41", "50"]: + record.cte40_choice_icms = "cte40_ICMS45" + record.cte40_CST = "45" + elif ( + record.fiscal_line_ids[0].icms_cst_id.code == "90" + and record.partner_id.state_id != record.company_id.state_id + ): + record.cte40_choice_icms = "cte40_ICMSOutraUF" + else: + record.cte40_choice_icms = "{}{}".format( + "cte40_ICMS", record.fiscal_line_ids[0].icms_cst_id.code + ) + record.cte40_CST = record.fiscal_line_ids[0].icms_cst_id.code + elif record.fiscal_line_ids[0].icms_cst_id.code in ICMS_SN_CST: + record.cte40_choice_icms = "cte40_ICMSSN" + record.cte40_CST = "90" + + def _export_fields_icms(self): + # Verifica se fiscal_line_ids está vazio para evitar erros + if not self.fiscal_line_ids: + return {} + + # TODO:aprimorar. talvez criar os campos relacionados com os campos e totais + # do documento fiscal e buscar apenas os percentuais da primeira linha + first_line = self.fiscal_line_ids[0] + + icms = { + "CST": self.cte40_CST, + "vBC": 0.0, + "pRedBC": first_line.icms_reduction, + "pICMS": first_line.icms_percent, + "vICMS": 0.0, + "vICMSSubstituto": 0.0, + "indSN": int(self.cte40_indSN), + "vBCSTRet": 0.0, + "vICMSSTRet": 0.0, + "pICMSSTRet": first_line.icmsst_wh_percent, + } + + for line in self.fiscal_line_ids: + icms["vBC"] += line.icms_base + icms["vICMS"] += line.icms_value + icms["vICMSSubstituto"] += line.icms_substitute + icms["vBCSTRet"] += line.icmsst_wh_base + icms["vICMSSTRet"] += line.icmsst_wh_value + + # Formatar os valores acumulados + icms["vBC"] = str("%.02f" % icms["vBC"]) + icms["vICMS"] = str("%.02f" % icms["vICMS"]) + icms["vICMSSubstituto"] = str("%.02f" % icms["vICMSSubstituto"]) + icms["vBCSTRet"] = str("%.02f" % icms["vBCSTRet"]) + icms["vICMSSTRet"] = str("%.02f" % icms["vICMSSTRet"]) + icms["pRedBC"] = str("%.04f" % icms["pRedBC"]) + icms["pICMS"] = str("%.02f" % icms["pICMS"]) + icms["pICMSSTRet"] = str("%.02f" % icms["pICMSSTRet"]) + + return icms + + def _export_fields_cte_40_timp(self, xsd_fields, class_obj, export_dict): + # TODO Not Implemented + if "cte40_ICMSOutraUF" in xsd_fields: + xsd_fields.remove("cte40_ICMSOutraUF") + + xsd_fields = [self.cte40_choice_icms] + icms_tag = ( + self.cte40_choice_icms.replace("cte40_", "") + .replace("ICMSSN", "Icmssn") + .replace("ICMS", "Icms") + ) + binding_module = sys.modules[self._binding_module] + icms = binding_module.Timp + icms_binding = getattr(icms, icms_tag) + icms_dict = self._export_fields_icms() + sliced_icms_dict = { + key: icms_dict.get(key) + for key in icms_binding.__dataclass_fields__.keys() + if icms_dict.get(key) + } + export_dict[icms_tag.upper()] = icms_binding(**sliced_icms_dict) + + # ########################## + # # CT-e tag: ICMSUFFim + # ########################## + + # cte40_vBCUFFim = fields.Monetary(related="icms_destination_base") + # cte40_pFCPUFFim = fields.Monetary(compute="_compute_cte40_ICMSUFFim", store=True) + # cte40_pICMSUFFim = fields.Monetary(compute="_compute_cte40_ICMSUFFim", store=True) + # # TODO + # # cte40_pICMSInter = fields.Selection( + # # selection=[("0", "Teste")], + # # compute="_compute_cte40_ICMSUFFim") + + # def _compute_cte40_ICMSUFFim(self): + # for record in self: + # # if record.icms_origin_percent: + # # record.cte40_pICMSInter = + # str("%.02f" % record.icms_origin_percent) + # # else: + # # record.cte40_pICMSInter = False + + # record.cte40_pFCPUFFim = record.icmsfcp_percent + # record.cte40_pICMSUFFim = record.icms_destination_percent + + # cte40_vFCPUFfim = fields.Monetary(related="icmsfcp_value") + # cte40_vICMSUFFim = fields.Monetary(related="icms_destination_value") + # cte40_vICMSUFIni = fields.Monetary(related="icms_origin_value") + + ##################################### + # CT-e tag: infCTeNorm and infCteComp + ##################################### + + cte40_choice_infcteNorm_infcteComp = fields.Selection( + selection=[ + ("cte40_infCTeComp", "infCTeComp"), + ("cte40_infCTeNorm", "infCTeNorm"), + ], + default="cte40_infCTeNorm", + ) + + cte40_infCTeNorm = fields.One2many( + comodel_name="l10n_br_cte.normal.infos", + inverse_name="document_id", + ) + + # cte40_infCTeComp = fields.One2many( + # comodel_name="l10n_br_fiscal.document.related", + # inverse_name="document_id", + # ) + + ########################## + # CT-e tag: infCarga + ########################## + + cte40_vCarga = fields.Monetary( + string="Valor total da carga", + ) + + cte40_proPred = fields.Char( + string="Produto predominante", + ) + + cte40_xOutCat = fields.Char( + string="Outras características da carga", + ) + + cte40_infQ = fields.One2many( + comodel_name="l10n_br_cte.cargo.quantity.infos", + inverse_name="document_id", + compute="_compute_cte40_infQ", + readonly=False, + store=True, + ) + + cte40_vCargaAverb = fields.Monetary( + string="Valor da Carga para efeito de averbação", + ) + + ########################## + # CT-e tag: infDoc + ########################## + + cte40_infDoc = fields.Many2one( + comodel_name="l10n_br_fiscal.document", + compute="_compute_cte40_infDoc", + string="Informações dos documentos transportados", + ) + + def _compute_cte40_infDoc(self): + for doc in self: + doc.cte40_infDoc = doc + + def _compute_cte40_infNFe(self): + for record in self: + record.cte40_infNFe = record.document_related_ids.filtered( + lambda r: r.cte40_infDoc == "cte40_infNFe" + ) + + def _compute_cte40_infOutros(self): + for record in self: + record.cte40_infOutros = record.document_related_ids.filtered( + lambda r: r.cte40_infDoc == "cte40_infOutros" + ) + + cte40_infNFe = fields.One2many( + comodel_name="l10n_br_fiscal.document.related", + inverse_name="document_id", + string="Informações das NF-e DOCS (Cte)", + compute="_compute_cte40_infNFe", + ) + + cte40_infOutros = fields.One2many( + comodel_name="l10n_br_fiscal.document.related", + inverse_name="document_id", + string="Informações dos Outros DOCS (Cte)", + compute="_compute_cte40_infOutros", + ) + + ########################## + # CT-e tag: veicNovos + ########################## + + cte40_veicNovos = fields.One2many( + comodel_name="l10n_br_cte.transported.vehicles", + inverse_name="document_id", + ) + + ########################## + # CT-e tag: autXML + # Compute Methods + ########################## + + def _default_cte40_autxml(self): + company = self.env.company + authorized_partners = [] + if company.accountant_id: + authorized_partners.append(company.accountant_id.id) + if company.technical_support_id: + authorized_partners.append(company.technical_support_id.id) + return authorized_partners + + ########################## + # CT-e tag: autXML + ########################## + + cte40_autXML = fields.One2many(default=_default_cte40_autxml) + + ########################## + # CT-e tag: infCTeSupl + ########################## + + cte40_infCTeSupl = fields.Many2one( + comodel_name="l10n_br_fiscal.document.supplement", + ) + + ########################## + # CT-e tag: infRespTec + ########################## + + cte40_infRespTec = fields.Many2one( + comodel_name="res.partner", + compute="_compute_infresptec", + string="Responsável Técnico CTe", + ) + + ########################## + # MDF-e tag: infRespTec + # Methods + ########################## + + @api.depends("company_id.technical_support_id") + def _compute_infresptec(self): + for record in self.filtered(filter_processador_edoc_cte): + record.cte40_infRespTec = record.company_id.technical_support_id + + ########################## + # CT-e tag: infmodal + ########################## + + cte40_modal = fields.Selection(related="transport_modal") + + cte40_versaoModal = fields.Char(default=CTE_MODAL_VERSION_DEFAULT) + + # Campos do Modal Aereo + modal_aereo_id = fields.Many2one(comodel_name="l10n_br_cte.modal.aereo") + + cte40_nMinu = fields.Char( + string="Número da Minuta", + help=( + "Número da Minuta\nDocumento que precede o CT-e, assinado pelo " + "expedidor, espécie de pedido de serviço" + ), + ) + + cte40_nOCA = fields.Char( + string="Número Operacional do Conhecimento Aéreo", + help=( + "Número Operacional do Conhecimento Aéreo\nRepresenta o número de " + "controle comumente utilizado pelo conhecimento aéreo composto por" + " uma sequência numérica de onze dígitos. Os três primeiros " + "dígitos representam um código que os operadores de transporte " + "aéreo associados à IATA possuem. Em seguida um número de série de" + " sete dígitos determinados pelo operador de transporte aéreo. " + "Para finalizar, um dígito verificador, que é um sistema de módulo" + " sete imponderado o qual divide o número de série do conhecimento" + " aéreo por sete e usa o resto como dígito de verificação." + ), + ) + + cte40_dPrevAereo = fields.Date( + string="Data prevista da entrega", + help="Data prevista da entrega\nFormato AAAA-MM-DD", + ) + + cte40_xDime = fields.Char( + string="Dimensão", + help=( + "Dimensão\nFormato:1234X1234X1234 (cm). Esse campo deve sempre que" + " possível ser preenchido. Entretanto, quando for impossível o " + "preenchimento das dimensões, fica obrigatório o preenchimento da " + "cubagem em metro cúbico do leiaute do CT-e da estrutura genérica " + "(infQ)." + ), + ) + + # TODO: avaliar + # def _compute_dime(self): + # for record in self: + # for package in record.product_id.packaging_ids: + # record.cte40_xDime = ( + # package.width + "X" + package.packaging_length + + # "X" + package.width + # ) + + cte40_CL = fields.Char( + string="Classe", + help=( + "Classe\nPreencher com:\n\t\t\t\t\t\t\t\t\tM - Tarifa " + "Mínima;\n\t\t\t\t\t\t\t\t\tG - Tarifa Geral;\n\t\t\t\t\t\t\t\t\tE" + " - Tarifa Específica" + ), + ) + + cte40_cTar = fields.Char( + string="Código da Tarifa", + help=( + "Código da Tarifa\nDeverão ser incluídos os códigos de três " + "dígitos, correspondentes à tarifa." + ), + ) + # Existem dois vTar no spec, um float e um monetary, por isso a mudança de nome + cte40_aereo_vTar = fields.Monetary( + string="Valor da Tarifa", + currency_field="brl_currency_id", + help="Valor da Tarifa\nValor da tarifa por kg quando for o caso.", + ) + + cte40_peri = fields.One2many( + comodel_name="l10n_br_cte.modal.aereo.peri", + inverse_name="document_id", + string="Dados de carga perigosa", + ) + + # Campos do Modal Aquaviario + modal_aquaviario_id = fields.Many2one(comodel_name="l10n_br_cte.modal.aquav") + + # TODO: fix + # cte40_vPrest = fields.Monetary( + # compute="_compute_cte40_vPrest", # FIX + # store=True, + # string="Valor da Prestação Base de Cálculo", + # ) + + cte40_vAFRMM = fields.Monetary( + string="AFRMM", + currency_field="brl_currency_id", + help=("AFRMM (Adicional de Frete para Renovação da Marinha Mercante)"), + ) + + cte40_xNavio = fields.Char(string="Identificação do Navio") + + cte40_nViag = fields.Char(string="Número da Viagem") + + cte40_direc = fields.Selection( + selection=[ + ("N", "Norte, L-Leste, S-Sul, O-Oeste"), + ("S", "Sul, O-Oeste"), + ("L", "Leste, S-Sul, O-Oeste"), + ("O", "Oeste"), + ], + string="Direção", + help="Direção\nPreencher com: N-Norte, L-Leste, S-Sul, O-Oeste", + ) + + cte40_irin = fields.Char( + string="Irin do navio", + help="Irin do navio sempre deverá ser informado", + ) + + cte40_tpNav = fields.Selection( + selection=[ + ("0", "Interior"), + ("1", "Cabotagem"), + ], + string="Tipo de Navegação", + help=( + "Tipo de Navegação\nPreencher com: \n\t\t\t\t\t\t0 - " + "Interior;\n\t\t\t\t\t\t1 - Cabotagem" + ), + ) + + cte40_balsa = fields.One2many( + comodel_name="l10n_br_cte.modal.aquav.balsa", + inverse_name="document_id", + string="Grupo de informações das balsas", + ) + + # Campos do Modal Dutoviario + modal_dutoviario_id = fields.Many2one(comodel_name="l10n_br_cte.modal.duto") + + cte40_dIni = fields.Date(string="Data de Início da prestação do serviço") + + cte40_dFim = fields.Date(string="Data de Fim da prestação do serviço") + + cte40_vTar = fields.Float(string="Valor da tarifa") + + # Campos do Modal Ferroviario + modal_ferroviario_id = fields.Many2one(comodel_name="l10n_br_cte.modal.ferrov") + + cte40_tpTraf = fields.Selection( + selection=FERROV_TPTRAF, + default="0", + string="Tipo de Tráfego", + ) + + cte40_fluxo = fields.Char( + string="Fluxo Ferroviário", + help=( + "Fluxo Ferroviário\nTrata-se de um número identificador do " + "contrato firmado com o cliente" + ), + ) + + cte40_vFrete = fields.Monetary( + related="amount_freight_value", + string="Valor do Frete do Tráfego Mútuo", + currency_field="brl_currency_id", + ) + + cte40_respFat = fields.Selection( + TRAFMUT_RESPFAT, + string="Responsável pelo Faturamento", + ) + + cte40_ferrEmi = fields.Selection( + TRAFMUT_FERREMI, + string="Ferrovia Emitente do CTe", + help=( + "Ferrovia Emitente do CTe\nPreencher com: " + "\n\t\t\t\t\t\t\t\t\t1-Ferrovia de origem; " + "\n\t\t\t\t\t\t\t\t\t2-Ferrovia de destino" + ), + ) + + cte40_chCTeFerroOrigem = fields.Char( + string="Chave de acesso do CT-e emitido", + help="Chave de acesso do CT-e emitido pelo ferrovia de origem", + ) + + cte40_ferroEnv = fields.Many2many( + comodel_name="res.partner", + string="Informações das Ferrovias Envolvidas", + ) + + # Campos do Modal rodoviario + modal_rodoviario_id = fields.Many2one(comodel_name="l10n_br_cte.modal.rodo") + + cte40_RNTRC = fields.Char( + string="RNTRC", + help="Registro Nacional de Transportadores Rodoviários de Carga", + compute="_compute_cte40_RNTRC", + ) + + def _compute_cte40_RNTRC(self): + for record in self: + if record.issuer == DOCUMENT_ISSUER_COMPANY: + record.cte40_RNTRC = record.company_id.partner_id.rntrc_code + else: + record.cte40_RNTRC = record.partner_id.rntrc_code + + cte40_occ = fields.One2many( + comodel_name="l10n_br_cte.modal.rodo.occ", + inverse_name="document_id", + string="Ordens de Coleta associados", + copy=False, + ) + + ########################## + # CT-e tag: infmodal + # Compute Methods + ########################## + + def _export_fields_cte_40_tcte_infmodal(self, xsd_fields, class_obj, export_dict): + if self.cte40_modal == "01": + export_dict["any_element"] = self._export_modal_rodoviario() + elif self.cte40_modal == "02": + export_dict["any_element"] = self._export_modal_aereo() + elif self.cte40_modal == "03": + export_dict["any_element"] = self._export_modal_aquaviario() + elif self.cte40_modal == "04": + export_dict["any_element"] = self._export_modal_ferroviario() + elif self.cte40_modal == "05": + export_dict["any_element"] = self._export_modal_dutoviario() + + def _export_modal_aereo(self): + if not self.modal_aereo_id: + self.modal_aereo_id = self.modal_aereo_id.create({"document_id": self.id}) + + return self.modal_aereo_id._build_binding("cte", "40") + + def _export_modal_ferroviario(self): + if not self.modal_ferroviario_id: + self.modal_ferroviario_id = self.modal_ferroviario_id.create( + {"document_id": self.id} + ) + + return self.modal_ferroviario_id._build_binding("cte", "40") + + def _export_modal_aquaviario(self): + if not self.modal_aquaviario_id: + self.modal_aquaviario_id = self.modal_aquaviario_id.create( + {"document_id": self.id} + ) + + return self.modal_aquaviario_id._build_binding("cte", "40") + + def _export_modal_rodoviario(self): + if not self.modal_rodoviario_id: + self.modal_rodoviario_id = self.modal_rodoviario_id.create( + {"document_id": self.id} + ) + + return self.modal_rodoviario_id._build_binding("cte", "40") + + def _export_modal_dutoviario(self): + if not self.modal_dutoviario_id: + self.modal_dutoviario_id = self.modal_dutoviario_id.create( + {"document_id": self.id} + ) + + return self.modal_dutoviario_id._build_binding("cte", "40") + + ################################ + # Framework Spec model's methods + ################################ + + def _export_field(self, xsd_field, class_obj, member_spec, export_value=None): + if xsd_field == "cte40_tpAmb": + self.env.context = dict(self.env.context) + self.env.context.update({"tpAmb": self[xsd_field]}) + self.env.context.update({"doc": self.id}) + + # TODO: Força a remoção da tag infGlobalizado já que o + # campo xObs está no l10n_br_fiscal.document + if xsd_field == "cte40_infGlobalizado": + return False + return super()._export_field(xsd_field, class_obj, member_spec, export_value) + + ################################ + # Business Model Methods + ################################ + + def _serialize(self, edocs): + edocs = super()._serialize(edocs) + for record in self.with_context(lang="pt_BR").filtered( + filter_processador_edoc_cte + ): + inf_cte = record._build_binding("cte", "40") + inf_cte_supl = None + if record.cte40_infCTeSupl: + inf_cte_supl = record.cte40_infCTeSupl._build_binding("cte", "40") + cte = Cte(infCte=inf_cte, infCTeSupl=inf_cte_supl, signature=None) + edocs.append(cte) + return edocs + + def _edoc_processor(self): + if not self.company_id.certificate_nfe_id: + raise UserError(_("Certificado não encontrado")) + + certificado = self.env.company._get_br_ecertificate() + session = Session() + session.verify = False + transmissao = TransmissaoCTE(certificado, session) + return edoc_cte( + transmissao, + self.company_id.state_id.ibge_code, + self.cte40_versao, + self.cte40_tpAmb, + ) + + def _document_export(self, pretty_print=True): + result = super()._document_export() + for record in self.filtered(filter_processador_edoc_cte): + edoc = record.serialize()[0] + processador = record._edoc_processor() + xml_file = edoc.to_xml() + event_id = self.event_ids.create_event_save_xml( + company_id=self.company_id, + environment=( + EVENT_ENV_PROD if self.cte40_tpAmb == "1" else EVENT_ENV_HML + ), + event_type="0", + xml_file=xml_file, + document_id=self, + ) + record.authorization_event_id = event_id + xml_assinado = processador.assina_raiz(edoc, edoc.infCte.Id) + self._validate_xml(xml_assinado) + return result + + def _validate_xml(self, xml_file): + self.ensure_one() + erros = Cte.schema_validation(xml_file) + erros = "\n".join(erros) + self.write({"xml_error_message": erros or False}) + + def update_status_cte(self, process): + self.ensure_one() + + if hasattr(process, "protocolo"): + infProt = process.protocolo.infProt + else: + infProt = process.resposta.protCTe.infProt + + if infProt.cStat in AUTORIZADO: + state = SITUACAO_EDOC_AUTORIZADA + self._cte_response_add_proc(process) + elif infProt.cStat in DENEGADO: + state = SITUACAO_EDOC_DENEGADA + else: + state = SITUACAO_EDOC_REJEITADA + if self.authorization_event_id and infProt.nProt: + if type(infProt.dhRecbto) == datetime: + protocol_date = fields.Datetime.to_string(infProt.dhRecbto) + else: + protocol_date = fields.Datetime.to_string( + datetime.fromisoformat(infProt.dhRecbto) + ) + + self.authorization_event_id.set_done( + status_code=infProt.cStat, + response=infProt.xMotivo, + protocol_date=protocol_date, + protocol_number=infProt.nProt, + file_response_xml=process.processo_xml.decode("utf-8"), + ) + self.write( + { + "status_code": infProt.cStat, + "status_name": infProt.xMotivo, + } + ) + self._change_state(state) + + def _eletronic_document_send(self): + super(CTe, self)._eletronic_document_send() + for record in self.filtered(filter_processador_edoc_cte): + if record.xml_error_message: + return + processador = record._edoc_processor() + for edoc in record.serialize(): + process = None + for p in processador.processar_documento(edoc): + process = p + if process.webservice == "cteRecepcaoLote": + record.authorization_event_id._save_event_file( + process.envio_xml, "xml" + ) + + if process.resposta.cStat in LOTE_PROCESSADO + ["100"]: + record.update_status_cte(process) + + elif process.resposta.cStat in DENEGADO: + record._change_state(SITUACAO_EDOC_DENEGADA) + record.write( + { + "status_code": process.resposta.cStat, + "status_name": process.resposta.xMotivo, + } + ) + + else: + record._change_state(SITUACAO_EDOC_REJEITADA) + record.write( + { + "status_code": process.resposta.cStat, + "status_name": process.resposta.xMotivo, + } + ) + + def _document_cancel(self, justificative): + result = super(CTe, self)._document_cancel(justificative) + online_event = self.filtered(filter_processador_edoc_cte) + if online_event: + online_event._cte_cancel() + return result + + def _cte_cancel(self): + self.ensure_one() + processador = self._edoc_processor() + + if not self.authorization_protocol: + raise UserError(_("Authorization Protocol Not Found!")) + + evento = processador.cancela_documento( + chave=self.document_key, + protocolo_autorizacao=self.authorization_protocol, + justificativa=self.cancel_reason.replace("\n", "\\n"), + ) + process = processador.enviar_lote_evento(lista_eventos=[evento]) + + self.cancel_event_id = self.event_ids.create_event_save_xml( + company_id=self.company_id, + environment=(EVENT_ENV_PROD if self.cte40_tpAmb == "1" else EVENT_ENV_HML), + event_type="2", + xml_file=process.envio_xml, + document_id=self, + ) + + resposta = process.resposta.infEvento + + if resposta.cStat not in CANCELADO: + mensagem = "Erro no cancelamento" + mensagem += "\nCódigo: " + resposta.cStat + mensagem += "\nMotivo: " + resposta.xMotivo + raise UserError(mensagem) + + if resposta.chCTe == self.document_key: + if resposta.cStat in CANCELADO_FORA_PRAZO: + self.state_fiscal = SITUACAO_FISCAL_CANCELADO_EXTEMPORANEO + elif resposta.cStat in CANCELADO_DENTRO_PRAZO: + self.state_fiscal = SITUACAO_FISCAL_CANCELADO + + self.state_edoc = SITUACAO_EDOC_CANCELADA + self.cancel_event_id.set_done( + status_code=resposta.cStat, + response=resposta.xMotivo, + protocol_date=fields.Datetime.to_string( + datetime.fromisoformat(resposta.dhRegEvento) + ), + protocol_number=resposta.nProt, + file_response_xml=process.retorno.content.decode("utf-8"), + ) + + def _document_correction(self, justificative): + result = super(CTe, self)._document_correction(justificative) + online_event = self.filtered(filter_processador_edoc_cte) + if online_event: + online_event._cte_correction(justificative) + return result + + def _cte_correction(self, justificative): + self.ensure_one() + processador = self._edoc_processor() + + numeros = self.event_ids.filtered( + lambda e: e.type == "14" and e.state == "done" + ).mapped("sequence") + + sequence = str(int(max(numeros)) + 1) if numeros else "1" + + evento = processador.carta_correcao( + chave=self.document_key, + protocolo_autorizacao=self.authorization_protocol, + justificativa=justificative.replace("\n", "\\n"), + sequencia=sequence, + ) + process = processador.enviar_lote_evento(lista_eventos=[evento]) + # Gravamos o arquivo no disco e no filestore ASAP. + event_id = self.event_ids.create_event_save_xml( + company_id=self.company_id, + environment=(EVENT_ENV_PROD if self.cte40_tpAmb == "1" else EVENT_ENV_HML), + event_type="14", + xml_file=process.envio_xml, + document_id=self, + sequence=sequence, + justification=justificative, + ) + + resposta = process.resposta.infEvento + + if resposta.cStat not in EVENTO_RECEBIDO and not ( + resposta.chCTe == self.document_key + ): + mensagem = "Erro na carta de correção" + mensagem += "\nCódigo: " + resposta.cStat + mensagem += "\nMotivo: " + resposta.xMotivo + raise UserError(mensagem) + + event_id.set_done( + status_code=resposta.cStat, + response=resposta.xMotivo, + protocol_date=fields.Datetime.to_string( + datetime.fromisoformat(resposta.dhRegEvento) + ), + protocol_number=resposta.nProt, + file_response_xml=process.retorno.content.decode("utf-8"), + ) + + def _document_qrcode(self): + super()._document_qrcode() + + for record in self: + record.cte40_infCTeSupl = self.env[ + "l10n_br_fiscal.document.supplement" + ].create( + { + "qrcode": record.get_cte_qrcode(), + } + ) + + def get_cte_qrcode(self): + # if self.document_type != MODELO_FISCAL_CTE: + # return + + processador = self._edoc_processor() + # if self.nfe_transmission == "1": + # return processador.monta_qrcode(self.document_key) + return processador.monta_qrcode(self.document_key) + + # serialized_doc = self.serialize()[0] + # xml = processador.assina_raiz(serialized_doc, serialized_doc.infNFe.Id) + # return processador._generate_qrcode_contingency(serialized_doc, xml) + + # TODO: nao esta rodando direto.. corrigir + def _compute_cte40_infQ(self): + for record in self: + cargo_info_vals = [ + {"cte40_cUnid": "01", "cte40_tpMed": "Peso Bruto", "cte40_qCarga": 0}, + { + "cte40_cUnid": "01", + "cte40_tpMed": "Peso Base Calculado", + "cte40_qCarga": 0, + }, + {"cte40_cUnid": "01", "cte40_tpMed": "Peso Aferido", "cte40_qCarga": 0}, + {"cte40_cUnid": "00", "cte40_tpMed": "Cubagem", "cte40_qCarga": 0}, + {"cte40_cUnid": "03", "cte40_tpMed": "Unidade", "cte40_qCarga": 0}, + ] + + record.cte40_infQ = self.env["l10n_br_cte.cargo.quantity.infos"].create( + cargo_info_vals + ) + + def _need_compute_cte_tags(self): + if ( + self.state_edoc in [SITUACAO_EDOC_EM_DIGITACAO, SITUACAO_EDOC_A_ENVIAR] + and self.processador_edoc == PROCESSADOR_OCA + and self.document_type_id.code in ["57"] + and self.issuer == DOCUMENT_ISSUER_COMPANY + ): + return True + else: + return False + + # cte40_infAdFisco = fields.Text(related="additional_data") + + # def make_pdf(self): + # if not self.filtered(filter_processador_edoc_cte): + # return super().make_pdf() + + # file_pdf = self.file_report_id + # self.file_report_id = False + # file_pdf.unlink() + + # if self.authorization_file_id: + # arquivo = self.authorization_file_id + # xml_string = base64.b64decode(arquivo.datas).decode() + # else: + # arquivo = self.send_file_id + # xml_string = base64.b64decode(arquivo.datas).decode() + # # TODO: implementar temp_xml_autorizacao igual nfe ? + # # xml_string = self.temp_xml_autorizacao(xml_string) + + # pdf = Dacte(xml=xml_string).output() + + # self.file_report_id = self.env["ir.attachment"].create( + # { + # "name": self.document_key + ".pdf", + # "res_model": self._name, + # "res_id": self.id, + # "datas": base64.b64encode(pdf), + # "mimetype": "application/pdf", + # "type": "binary", + # } + # ) + + def _cte_response_add_proc(self, ws_response_process): + """ + Inject the final NF-e, tag `cteProc`, into the response. + """ + xml_soap = ws_response_process.retorno.content + tree_soap = etree.fromstring(xml_soap) + prot_element = tree_soap.xpath("//cte:protCTe", namespaces=CTE_XML_NAMESPACE)[0] + proc_xml = self._cte_create_proc(prot_element) + if proc_xml: + # it is not always possible to create cteProc. + parser = XmlParser() + proc = parser.from_string(proc_xml.decode(), CteProc) + ws_response_process.processo = proc + ws_response_process.processo_xml = proc_xml + + def _cte_create_proc(self, prot_element): + """ + Create the `cteProc` XML by combining the CT-e and the authorization protocol. + + This method decodes the saved `enviCTe` message, extracts the CTe> tag, + and combines it with the provided authorization protocol element to create + the `cteProc` XML, which represents the finalized CT-e document. + + Args: + prot_element: The XML element containing the authorization protocol. + + Returns: + The assembled `cteProc` XML, or None if the `send_file_id` data is not + found. + + Note: + Useful for recreating the final CT-e XML, as SEFAZ does not provide the + complete XML upon consultation, only the authorization protocol. + """ + self.ensure_one() + + if not self.send_file_id.datas: + _logger.info( + "CT-e data not found when trying to assemble the " + "xml with the authorization protocol (cteProc)" + ) + return None + + processor = self._edoc_processor() + + # Extract the tag from the `enviCTe` message, which represents the CT-e + xml_send = base64.b64decode(self.send_file_id.datas) + tree_send = etree.fromstring(xml_send) + doc_element = tree_send.xpath("//cte:CTe", namespaces=CTE_XML_NAMESPACE)[0] + + # Assemble the `cteProc` using the erpbrasil.edoc library. + proc_xml = processor.monta_cte_proc(doc=doc_element, prot=prot_element) + + return proc_xml diff --git a/l10n_br_cte/models/document_cargo_quantity_infos.py b/l10n_br_cte/models/document_cargo_quantity_infos.py new file mode 100644 index 000000000000..57b5e99397c2 --- /dev/null +++ b/l10n_br_cte/models/document_cargo_quantity_infos.py @@ -0,0 +1,26 @@ +# Copyright 2023 KMEE +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields + +from odoo.addons.spec_driven_model.models import spec_models + + +class CTeCargoQuantityInfos(spec_models.SpecModel): + _name = "l10n_br_cte.cargo.quantity.infos" + _inherit = "cte.40.tcte_infq" + _description = "Informações de quantidades da Carga do CT-e" + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + cte40_cUnid = fields.Selection( + required=True, + ) + + cte40_tpMed = fields.Char( + required=True, + ) + + cte40_qCarga = fields.Float( + required=True, + ) diff --git a/l10n_br_cte/models/document_comment.py b/l10n_br_cte/models/document_comment.py new file mode 100644 index 000000000000..215ed802dec5 --- /dev/null +++ b/l10n_br_cte/models/document_comment.py @@ -0,0 +1,37 @@ +# Copyright 2024 - TODAY, Marcel Savegnago +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields + +from odoo.addons.spec_driven_model.models import spec_models + + +class CTeComment(spec_models.StackedModel): + _name = "l10n_br_fiscal.comment" + _inherit = ["l10n_br_fiscal.comment", "cte.40.tcte_obscont", "cte.40.tcte_obsfisco"] + + _cte40_odoo_module = ( + "odoo.addons.l10n_br_cte_spec.models.v4_0.cte_tipos_basico_v4_00" + ) + _cte40_stacking_mixin = "cte.40.tcte_obscont" + + # _stacking_points = {} + _cte40_stacking_skip_paths = ("cte40_ObsCont_compl_id", "cte40_ObsFisco_compl_id") + + cte40_xCampo = fields.Char() + + cte40_xTexto = fields.Text() + + def _export_field(self, xsd_field, class_obj, member_spec, export_value=None): + if xsd_field == "cte40_xCampo": + return self.name[:20].strip() + if xsd_field == "cte40_xTexto": + if "doc" in self.env.context: + doc_id = self.env.context["doc"] + doc = self.env["l10n_br_fiscal.document"].browse(doc_id) + vals = {"user": self.env.user, "ctx": self._context, "doc": doc} + message = self.compute_message(vals).strip() + if self.comment_type == "fiscal": + return message[:60] + return message[:160] + return super()._export_field(xsd_field, class_obj, member_spec, export_value) diff --git a/l10n_br_cte/models/document_line.py b/l10n_br_cte/models/document_line.py new file mode 100644 index 000000000000..f1a2c2ed53ad --- /dev/null +++ b/l10n_br_cte/models/document_line.py @@ -0,0 +1,27 @@ +# Copyright 2023 KMEE +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields + +from odoo.addons.spec_driven_model.models import spec_models + + +class CTeLine(spec_models.StackedModel): + _name = "l10n_br_fiscal.document.line" + _inherit = ["l10n_br_fiscal.document.line", "cte.40.tcte_vprest_comp"] + + _cte40_odoo_module = ( + "odoo.addons.l10n_br_cte_spec.models.v4_0.cte_tipos_basico_v4_00" + ) + _cte40_stacking_mixin = "cte.40.tcte_vprest_comp" + + # _stacking_points = {} + _cte40_stacking_skip_paths = ("cte40_Comp_vPrest_id",) + + ########################## + # CT-e tag: comp + ########################## + + cte40_xNome = fields.Text(related="name") + + cte40_vComp = fields.Monetary(related="amount_total") diff --git a/l10n_br_cte/models/document_related.py b/l10n_br_cte/models/document_related.py new file mode 100644 index 000000000000..f68d6770d7d0 --- /dev/null +++ b/l10n_br_cte/models/document_related.py @@ -0,0 +1,109 @@ +# Copyright 2023 KMEE INFORMATICA LTDA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields + +from odoo.addons.spec_driven_model.models import spec_models + + +class CTeRelated(spec_models.StackedModel): + _name = "l10n_br_fiscal.document.related" + _inherit = [ + "l10n_br_fiscal.document.related", + "cte.40.tcte_infdoc", + ] + + _cte40_odoo_module = ( + "odoo.addons.l10n_br_cte_spec.models.v4_0.cte_tipos_basico_v4_00" + ) + _cte40_stacking_mixin = "cte.40.tcte_infdoc" + + # InfNFe + cte40_chave = fields.Char( + compute="_compute_cte_data", + inverse="_inverse_cte40_chave", + ) + + cte40_tpDoc = fields.Char( + compute="_compute_cte_data", + inverse="_inverse_cte40_tpDoc", + ) + + # infOutros + + cte40_descOutros = fields.Char(string="Descrição do documento") + + cte40_nDoc = fields.Char(string="Número", default="123123") + + cte40_dEmi = fields.Date( + string="Data de Emissão", + help="Data de Emissão\nFormato AAAA-MM-DD", + ) + + cte40_vDocFisc = fields.Monetary( + string="Valor do documento", + default=1000.0, + currency_field="brl_currency_id", + ) + + cte40_dPrev = fields.Date( + string="Data prevista de entrega", + help="Data prevista de entrega\nFormato AAAA-MM-DD", + ) + + cte40_infDoc = fields.Selection( + related="cte40_choice_infNF_infNFE_infOutros", string="infDoc" + ) + + # infCteNorm + cte40_chCTe = fields.Char(compute="_compute_chCte", string="chCte") + + ########################## + # CT-e tag: infCTeComp + # Compute Methods + ########################## + + def _compute_chCTe(self): + records = "" + for rec in self: + if rec.cte40_Id: + records += rec.document_key + self.cte40_chCTe = records + + cte40_choice_infNF_infNFE_infOutros = fields.Selection( + selection=[ + ("cte40_infNF", "infNF"), # TODO + ("cte40_infNFe", "infNFe"), + ("cte40_infOutros", "Outros"), + ], + compute="_compute_cte_data", + inverse="_inverse_cte40_choice_infNF_infNFE_infOutros", + string="CHOICE", + ) + + @api.depends("document_type_id") + def _compute_cte_data(self): + """Set schema data which are not just related fields""" + for rec in self: + if rec.document_type_id: + if rec.document_type_id.code in ("55",): + rec.cte40_choice_infNF_infNFE_infOutros = "cte40_infNFe" + rec.cte40_chave = rec.document_key + elif rec.document_type_id.code in ("00", "10", "59", "65", "99"): + rec.cte40_choice_infNF_infNFE_infOutros = "cte40_infOutros" + rec.cte40_tpDoc = rec.document_type_id.code + + def _inverse_cte40_chave(self): + for rec in self: + if rec.cte40_chave: + rec.document_key = rec.cte40_chave + + def _inverse_cte40_tpDoc(self): + for rec in self: + if rec.cte40_tpDoc: + rec.document_type_id = rec.cte40_tpDoc + + def _inverse_cte40_choice_infNF_infNFE_infOutros(self): + for rec in self: + if rec.cte40_choice_infNF_infNFE_infOutros == "cte40_infNFe": + rec.document_type_id = self.env.ref("l10n_br_fiscal.document_55") diff --git a/l10n_br_cte/models/document_supplement.py b/l10n_br_cte/models/document_supplement.py new file mode 100644 index 000000000000..9625c7441c19 --- /dev/null +++ b/l10n_br_cte/models/document_supplement.py @@ -0,0 +1,18 @@ +# Copyright 2023 KMEE (Luiz Felipe do Divino ) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import fields + +from odoo.addons.spec_driven_model.models import spec_models + + +class CTeSupplement(spec_models.StackedModel): + _name = "l10n_br_fiscal.document.supplement" + _inherit = ["l10n_br_fiscal.document.supplement", "cte.40.tcte_infctesupl"] + + _cte40_odoo_module = ( + "odoo.addons.l10n_br_cte_spec.models.v4_0.cte_tipos_basico_v4_00" + ) + _cte40_stacking_mixin = "cte.40.tcte_infctesupl" + + cte40_qrCodCTe = fields.Char(related="qrcode") diff --git a/l10n_br_cte/models/document_transported_vehicles.py b/l10n_br_cte/models/document_transported_vehicles.py new file mode 100644 index 000000000000..004ce2627172 --- /dev/null +++ b/l10n_br_cte/models/document_transported_vehicles.py @@ -0,0 +1,42 @@ +# Copyright 2023 KMEE +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields + +from odoo.addons.spec_driven_model.models import spec_models + + +class CTeCargoQuantityInfos(spec_models.SpecModel): + _name = "l10n_br_cte.transported.vehicles" + _inherit = "cte.40.veicnovos" + _description = "Informações dos veículos transportados" + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + currency_id = fields.Many2one( + comodel_name="res.currency", + related="document_id.company_id.currency_id", + ) + + cte40_chassi = fields.Char(string="Chassi do veículo", required=True, size=17) + + cte40_cCor = fields.Char(string="Cor do veículo", required=True, size=4) + + cte40_xCor = fields.Char(string="Descrição da cor", required=True) + + cte40_cMod = fields.Char( + string="Código Marca Modelo", + required=True, + ) + + cte40_vUnit = fields.Monetary( + string="Valor Unitário do Veículo", + required=True, + currency_field="currency_id", + ) + + cte40_vFrete = fields.Monetary( + string="Frete Unitário", + required=True, + currency_field="currency_id", + ) diff --git a/l10n_br_cte/models/dutoviario.py b/l10n_br_cte/models/dutoviario.py new file mode 100644 index 000000000000..8262a25e99a8 --- /dev/null +++ b/l10n_br_cte/models/dutoviario.py @@ -0,0 +1,29 @@ +# Copyright 2023 KMEE INFORMATICA LTDA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields + +from odoo.addons.spec_driven_model.models import spec_models + + +class Duto(spec_models.StackedModel): + _name = "l10n_br_cte.modal.duto" + _inherit = "cte.40.duto" + _description = "Modal Dutoviario CTe" + + _cte40_stacking_mixin = "cte.40.duto" + _cte40_odoo_module = ( + "odoo.addons.l10n_br_cte_spec.models.v4_0.cte_modal_dutoviario_v4_00" + ) + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + cte40_dIni = fields.Date(related="document_id.cte40_dIni") + + cte40_dFim = fields.Date(related="document_id.cte40_dFim") + + cte40_vTar = fields.Float(related="document_id.cte40_vTar") + + def _prepare_dacte_values(self): + if not self: + return {} diff --git a/l10n_br_cte/models/ferroviario.py b/l10n_br_cte/models/ferroviario.py new file mode 100644 index 000000000000..d3048e812212 --- /dev/null +++ b/l10n_br_cte/models/ferroviario.py @@ -0,0 +1,50 @@ +# Copyright 2023 KMEE INFORMATICA LTDA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + + +from odoo import api, fields + +from odoo.addons.spec_driven_model.models import spec_models + + +class Ferrov(spec_models.StackedModel): + _name = "l10n_br_cte.modal.ferrov" + _inherit = "cte.40.ferrov" + _description = "Modal Ferroviario CTe" + + _cte40_stacking_mixin = "cte.40.ferrov" + _cte40_odoo_module = ( + "odoo.addons.l10n_br_cte_spec.models.v4_0.cte_modal_ferroviario_v4_00" + ) + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + cte40_tpTraf = fields.Selection(related="document_id.cte40_tpTraf") + + cte40_fluxo = fields.Char(related="document_id.cte40_fluxo") + + cte40_vFrete = fields.Monetary( + related="document_id.cte40_vFrete", currency_field="currency_id" + ) + + currency_id = fields.Many2one( + comodel_name="res.currency", + default=lambda self: self.env.company.currency_id, + ) + + cte40_chCTeFerroOrigem = fields.Char(related="document_id.cte40_chCTeFerroOrigem") + + cte40_respFat = fields.Selection(related="document_id.cte40_respFat") + + cte40_ferrEmi = fields.Selection(related="document_id.cte40_ferrEmi") + + cte40_ferroEnv = fields.One2many(compute="_compute_railroad") + + @api.depends("document_id.cte40_ferroEnv") + def _compute_railroad(self): + for record in self: + record.cte40_ferroEnv = [(6, 0, record.document_id.cte40_ferroEnv.ids)] + + def _prepare_dacte_values(self): + if not self: + return {} diff --git a/l10n_br_cte/models/normal_cte_infos.py b/l10n_br_cte/models/normal_cte_infos.py new file mode 100644 index 000000000000..9a893311c3b4 --- /dev/null +++ b/l10n_br_cte/models/normal_cte_infos.py @@ -0,0 +1,157 @@ +# Copyright 2023 KMEE +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields + +from odoo.addons.spec_driven_model.models import spec_models + + +class CTeNormalInfos(spec_models.StackedModel): + _name = "l10n_br_cte.normal.infos" + _inherit = ["cte.40.tcte_infctenorm"] + _description = "Grupo de informações do CTe Normal e Substituto" + + _cte40_stacking_mixin = "cte.40.tcte_infctenorm" + _cte40_odoo_module = ( + "odoo.addons.l10n_br_cte_spec.models.v4_0.cte_tipos_basico_v4_00" + ) + _cte40_stacking_force_paths = "infctenorm.infdoc" + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + currency_id = fields.Many2one( + comodel_name="res.currency", + related="document_id.company_id.currency_id", + ) + + cte40_vCarga = fields.Monetary( + related="document_id.cte40_vCarga", + currency_field="currency_id", + ) + + cte40_proPred = fields.Char( + related="document_id.cte40_proPred", + ) + + cte40_xOutCat = fields.Char( + related="document_id.cte40_xOutCat", + ) + + cte40_infQ = fields.One2many( + comodel_name="l10n_br_cte.cargo.quantity.infos", + related="document_id.cte40_infQ", + ) + + cte40_vCargaAverb = fields.Monetary( + related="document_id.cte40_vCargaAverb", + ) + + cte40_veicNovos = fields.One2many( + comodel_name="l10n_br_cte.transported.vehicles", + related="document_id.cte40_veicNovos", + ) + + cte40_infNFe = fields.One2many( + comodel_name="l10n_br_fiscal.document.related", + related="document_id.cte40_infNFe", + ) + + cte40_infOutros = fields.One2many( + comodel_name="l10n_br_fiscal.document.related", + related="document_id.cte40_infOutros", + ) + + cte40_versaoModal = fields.Char(related="document_id.cte40_versaoModal") + + # Campos do Modal Aereo + modal_aereo_id = fields.Many2one( + comodel_name="l10n_br_cte.modal.aereo", related="document_id.modal_aereo_id" + ) + + cte40_nMinu = fields.Char(related="document_id.cte40_nMinu") + + cte40_nOCA = fields.Char(related="document_id.cte40_nOCA") + + cte40_dPrevAereo = fields.Date(related="document_id.cte40_dPrevAereo") + + cte40_xDime = fields.Char(related="document_id.cte40_xDime") + + cte40_CL = fields.Char(related="document_id.cte40_CL") + + cte40_cTar = fields.Char(related="document_id.cte40_cTar") + + # Existem dois vTar no spec, um float e um monetary, por isso a mudança de nome + cte40_aereo_vTar = fields.Monetary(related="document_id.cte40_aereo_vTar") + + cte40_peri = fields.One2many( + comodel_name="l10n_br_cte.modal.aereo.peri", related="document_id.cte40_peri" + ) + + # Campos do Modal Aquaviario + modal_aquaviario_id = fields.Many2one( + comodel_name="l10n_br_cte.modal.aquav", + related="document_id.modal_aquaviario_id", + ) + + cte40_vPrest = fields.Monetary( + related="document_id.cte40_vTPrest" + ) # TODO: avaliar melhor + + cte40_vAFRMM = fields.Monetary(related="document_id.cte40_vAFRMM") + + cte40_xNavio = fields.Char(related="document_id.cte40_xNavio") + + cte40_nViag = fields.Char(related="document_id.cte40_nViag") + + cte40_direc = fields.Selection(related="document_id.cte40_direc") + + cte40_irin = fields.Char(related="document_id.cte40_irin") + + cte40_tpNav = fields.Selection(related="document_id.cte40_tpNav") + + cte40_balsa = fields.One2many( + comodel_name="l10n_br_cte.modal.aquav.balsa", + related="document_id.cte40_balsa", + ) + + # Campos do Modal Dutoviario + modal_dutoviario_id = fields.Many2one( + comodel_name="l10n_br_cte.modal.duto", + related="document_id.modal_dutoviario_id", + ) + + cte40_dIni = fields.Date(related="document_id.cte40_dIni") + + cte40_dFim = fields.Date(related="document_id.cte40_dFim") + + cte40_vTar = fields.Float(related="document_id.cte40_vTar") + + # Campos do Modal Ferroviario + modal_ferroviario_id = fields.Many2one( + comodel_name="l10n_br_cte.modal.ferrov", + related="document_id.modal_ferroviario_id", + ) + + cte40_tpTraf = fields.Selection(related="document_id.cte40_tpTraf") + + cte40_fluxo = fields.Char(related="document_id.cte40_fluxo") + + cte40_vFrete = fields.Monetary(related="document_id.cte40_vFrete") + + cte40_respFat = fields.Selection(related="document_id.cte40_respFat") + + cte40_ferrEmi = fields.Selection(related="document_id.cte40_ferrEmi") + + cte40_ferroEnv = fields.Many2many(related="document_id.cte40_ferroEnv") + + # Campos do Modal rodoviario + modal_rodoviario_id = fields.Many2one( + comodel_name="l10n_br_cte.modal.rodo", + related="document_id.modal_rodoviario_id", + ) + + cte40_RNTRC = fields.Char(related="document_id.cte40_RNTRC") + + cte40_occ = fields.One2many( + comodel_name="l10n_br_cte.modal.rodo.occ", related="document_id.cte40_occ" + ) diff --git a/l10n_br_cte/models/res_company.py b/l10n_br_cte/models/res_company.py new file mode 100644 index 000000000000..c07ec7c855f8 --- /dev/null +++ b/l10n_br_cte/models/res_company.py @@ -0,0 +1,69 @@ +# Copyright 2023 KMEE INFORMATICA LTDA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResCompany(models.Model): + _name = "res.company" + _inherit = ["res.company"] + + ########################## + # CT-e models fields + ########################## + + cte_default_serie_id = fields.Many2one( + comodel_name="l10n_br_fiscal.document.serie", + string="CT-e Default Serie", + ) + + cte_dacte_layout = fields.Selection( + selection=[("1", "Paisagem"), ("2", "Retrato")], + string="CT-e DACTE Layout", + default="1", + ) + + cte_transmission = fields.Selection( + selection=[ + ("1", "Normal"), + ("2", "Regime Especial NFF"), + ("4", "EPEC pela SVC"), + ("5", "Contingência FSDA"), + ("7", "Contingência SVC-RS"), + ("8", "Contingência SVC-SP"), + ], + string="CT-e Transmission Type", + default="1", + ) + + cte_type = fields.Selection( + selection=[ + ("0", "CT-e Normal"), + ("1", "CT-e de Complemento de Valores"), + ("3", "CT-e de Substituição"), + ], + string="CT-e Type", + default="0", + ) + + cte_environment = fields.Selection( + selection=[("1", "Produção"), ("2", "Homologação")], + string="CT-e Environment", + default="2", + ) + + cte_version = fields.Selection( + selection=[("3.00", "3.00"), ("4.00", "4.00")], + string="CT-e Version", + default="4.00", + ) + + processador_edoc = fields.Selection( + selection_add=[("erpbrasil.edoc", "erpbrasil.edoc")], + ) + + cte_authorize_accountant_download_xml = fields.Boolean( + string="Include Accountant Partner data in persons authorized to " + "download CTe XML", + default=False, + ) diff --git a/l10n_br_cte/models/res_config_settings.py b/l10n_br_cte/models/res_config_settings.py new file mode 100644 index 000000000000..278b43cb80ac --- /dev/null +++ b/l10n_br_cte/models/res_config_settings.py @@ -0,0 +1,12 @@ +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + cte_authorize_accountant_download_xml = fields.Boolean( + string="Include Accountant Partner data in persons authorized to " + "download CTe XML", + related="company_id.cte_authorize_accountant_download_xml", + readonly=False, + ) diff --git a/l10n_br_cte/models/res_partner.py b/l10n_br_cte/models/res_partner.py new file mode 100644 index 000000000000..aab363047245 --- /dev/null +++ b/l10n_br_cte/models/res_partner.py @@ -0,0 +1,179 @@ +# Copyright 2023 KMEE INFORMATICA LTDA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import logging + +from odoo import api, fields + +from odoo.addons.spec_driven_model.models import spec_models + +_logger = logging.getLogger(__name__) + +try: + from erpbrasil.base.misc import format_zipcode, punctuation_rm +except ImportError: + _logger.error("Biblioteca erpbrasil.base não instalada") + + +class ResPartner(spec_models.SpecModel): + _name = "res.partner" + _inherit = [ + "res.partner", + "cte.40.tendereco", + "cte.40.tlocal", + "cte.40.tendeemi", + "cte.40.tcte_dest", + "cte.40.tresptec", + "cte.40.tcte_autxml", + "cte.40.tenderfer", + ] + _cte_search_keys = ["cte40_CNPJ", "cte40_CPF", "cte40_xNome"] + + cte40_choice_cnpj_cpf = fields.Selection( + selection=[("cte40_CNPJ", "CNPJ"), ("cte40_CPF", "CPF")], + string="CNPJ/CPF do Parceiro", + ) + + cte40_CNPJ = fields.Char( + compute="_compute_cte_data", + store=True, + ) + + cte40_cInt = fields.Char( + string="Código interno da Ferrovia envolvida", + help="Código interno da Ferrovia envolvida\nUso da transportadora", + ) + + cte40_CPF = fields.Char( + compute="_compute_cte_data", + store=True, + ) + + # Same problem with Tendereco that NFE has, it has to use m2o fields + cte40_enderToma = fields.Many2one( + comodel_name="res.partner", compute="_compute_cte40_ender" + ) + + cte40_enderReme = fields.Many2one( + comodel_name="res.partner", compute="_compute_cte40_ender" + ) + + cte40_enderDest = fields.Many2one( + comodel_name="res.partner", compute="_compute_cte40_ender" + ) + + cte40_enderExped = fields.Many2one( + comodel_name="res.partner", compute="_compute_cte40_ender" + ) + + cte40_enderReceb = fields.Many2one( + comodel_name="res.partner", compute="_compute_cte40_ender" + ) + + cte40_enderFerro = fields.Many2one( + comodel_name="res.partner", compute="_compute_cte40_ender" + ) + + # enderToma/enderEmit/enderReme + cte40_xLgr = fields.Char(related="street_name", readonly=True) + cte40_nro = fields.Char(related="street_number", readonly=True) + cte40_xCpl = fields.Char(related="street2", readonly=True) + cte40_xBairro = fields.Char(related="district", readonly=True) + cte40_cMun = fields.Char(related="city_id.ibge_code", readonly=True) + cte40_xMun = fields.Char(related="city_id.name", readonly=True) + cte40_UF = fields.Char(related="state_id.code") + cte40_CEP = fields.Char( + compute="_compute_cep", + inverse="_inverse_cte40_CEP", + compute_sudo=True, + ) + cte40_cPais = fields.Char( + related="country_id.bc_code", + ) + cte40_xPais = fields.Char( + related="country_id.name", + ) + + cte40_IE = fields.Char(compute="_compute_cte40_IE") + + cte40_xNome = fields.Char(related="legal_name") + + cte40_xContato = fields.Char(related="legal_name") + + cte40_email = fields.Char(related="email") + + cte40_fone = fields.Char( + compute="_compute_cte_data", inverse="_inverse_cte40_fone", compute_sudo=True + ) + + def _compute_cte40_IE(self): + for rec in self: + rec.cte40_IE = str(rec.inscr_est).replace(".", "") + + def _compute_cte40_ender(self): + for rec in self: + rec.cte40_enderToma = rec.id + rec.cte40_enderReme = rec.id + rec.cte40_enderDest = rec.id + rec.cte40_enderExped = rec.id + rec.cte40_enderReceb = rec.id + rec.cte40_enderFerro = rec.id + + @api.depends("company_type", "inscr_est", "cnpj_cpf", "country_id") + def _compute_cte_data(self): + for rec in self: + cnpj_cpf = punctuation_rm(rec.cnpj_cpf) + if cnpj_cpf: + if rec.is_company: + rec.cte40_CNPJ = cnpj_cpf + rec.cte40_CPF = None + else: + rec.cte40_CNPJ = None + rec.cte40_CPF = cnpj_cpf + rec.cte40_fone = punctuation_rm(rec.phone or "").replace(" ", "") + + def _inverse_cte40_CEP(self): + for rec in self: + if rec.cte40_CEP: + country_code = rec.country_id.code if rec.country_id else "BR" + rec.zip = format_zipcode(rec.cte40_CEP, country_code) + + def _inverse_cte40_fone(self): + for rec in self: + if rec.cte40_fone: + rec.phone = rec.nfe40_fone + + def _compute_cep(self): + for rec in self: + rec.cte40_CEP = punctuation_rm(rec.zip) + + def _export_field(self, xsd_field, class_obj, member_spec, export_value=None): + if ( + xsd_field == "cte40_xNome" + and class_obj._name + in ["cte.40.tcte_rem", "cte.40.tcte_dest", "cte.40.exped", "cte.40.receb"] + and self.env.context.get("tpAmb") == "2" + ): + return "CTE EMITIDO EM AMBIENTE DE HOMOLOGACAO - SEM VALOR FISCAL" + + if not self.cnpj_cpf and self.parent_id: + cnpj_cpf = punctuation_rm(self.parent_id.cnpj_cpf) + else: + cnpj_cpf = punctuation_rm(self.cnpj_cpf) + + if xsd_field == self.cte40_choice_cnpj_cpf: + return cnpj_cpf + + if self.country_id.code != "BR": + if xsd_field == "cte40_xBairro": + return "EX" + + if xsd_field == "cte40_xMun": + return "EXTERIOR" + + if xsd_field == "cte40_cMun": + return "9999999" + + if xsd_field == "cte40_UF": + return "EX" + return super()._export_field(xsd_field, class_obj, member_spec, export_value) diff --git a/l10n_br_cte/models/rodoviario.py b/l10n_br_cte/models/rodoviario.py new file mode 100644 index 000000000000..11d63261a597 --- /dev/null +++ b/l10n_br_cte/models/rodoviario.py @@ -0,0 +1,70 @@ +# Copyright 2023 KMEE INFORMATICA LTDA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields + +from odoo.addons.spec_driven_model.models import spec_models + +from ..constants.modal import TUF + + +class Rodo(spec_models.StackedModel): + _name = "l10n_br_cte.modal.rodo" + _inherit = "cte.40.rodo" + _description = "Modal Rodoviario CTe" + + _cte40_stacking_mixin = "cte.40.rodo" + _cte40_odoo_module = ( + "odoo.addons.l10n_br_cte_spec.models.v4_0.cte_modal_rodoviario_v4_00" + ) + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + cte40_RNTRC = fields.Char(related="document_id.cte40_RNTRC") + + cte40_occ = fields.One2many(related="document_id.cte40_occ") + + +class Occ(spec_models.StackedModel): + _name = "l10n_br_cte.modal.rodo.occ" + _inherit = "cte.40.occ" + _description = "Ordens de Coleta associados" + + _cte40_stacking_mixin = "cte.40.occ" + _cte40_odoo_module = ( + "odoo.addons.l10n_br_cte_spec.models.v4_0.cte_modal_rodoviario_v4_00" + ) + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + cte40_serie = fields.Char(string="Série da OCC") + + cte40_nOcc = fields.Char(string="Número da Ordem de coleta") + + cte40_dEmi = fields.Date( + string="Data de emissão da ordem de coleta", + help="Data de emissão da ordem de coleta\nFormato AAAA-MM-DD", + ) + + cte40_CNPJ = fields.Char( + string="Número do CNPJ", + help="Número do CNPJ\nInformar os zeros não significativos.", + ) + + cte40_cInt = fields.Char( + string="Código interno de uso da transportadora", + help=( + "Código interno de uso da transportadora\nUso intermo das " + "transportadoras." + ), + ) + + cte40_IE = fields.Char(string="Inscrição Estadual") + + cte40_UF = fields.Selection( + TUF, + string="Sigla da UF", + help="Sigla da UF\nInformar EX para operações com o exterior.", + ) + + cte40_fone = fields.Char(string="Telefone") diff --git a/l10n_br_cte/readme/CONFIGURE.rst b/l10n_br_cte/readme/CONFIGURE.rst new file mode 100644 index 000000000000..754e51aeff53 --- /dev/null +++ b/l10n_br_cte/readme/CONFIGURE.rst @@ -0,0 +1,10 @@ +[ This file is optional, it should explain how to configure + the module before using it; it is aimed at advanced users. ] + +To configure this module, you need to: + +#. Go to ... + +.. figure:: ../static/description/image.png + :alt: alternative description + :width: 600 px diff --git a/l10n_br_cte/readme/CONTRIBUTORS.rst b/l10n_br_cte/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000000..957041454b69 --- /dev/null +++ b/l10n_br_cte/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Ygor Carvalho diff --git a/l10n_br_cte/readme/DESCRIPTION.rst b/l10n_br_cte/readme/DESCRIPTION.rst new file mode 100644 index 000000000000..43cf54b776e0 --- /dev/null +++ b/l10n_br_cte/readme/DESCRIPTION.rst @@ -0,0 +1,4 @@ +[ This file must be max 2-3 paragraphs, and is required. ] + +This module extends the functionality of ... to support ... +and to allow you to ... diff --git a/l10n_br_cte/readme/USAGE.rst b/l10n_br_cte/readme/USAGE.rst new file mode 100644 index 000000000000..f4629c3d548a --- /dev/null +++ b/l10n_br_cte/readme/USAGE.rst @@ -0,0 +1,11 @@ +[ This file must be present and contains the usage instructions + for end-users. As all other rst files included in the README, + it MUST NOT contain reStructuredText sections + only body text (paragraphs, lists, tables, etc). Should you need + a more elaborate structure to explain the addon, please create a + Sphinx documentation (which may include this file as a "quick start" + section). ] + +To use this module, you need to: + +#. Go to ... diff --git a/l10n_br_cte/security/ir.model.access.csv b/l10n_br_cte/security/ir.model.access.csv new file mode 100644 index 000000000000..a7ed316bbe7a --- /dev/null +++ b/l10n_br_cte/security/ir.model.access.csv @@ -0,0 +1,17 @@ +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" +l10n_br_cte_modal_aereo_user,l10n_br_cte_modal_aereo_user,model_l10n_br_cte_modal_aereo,base.group_user,1,1,1,1 +l10n_br_cte_modal_aereo_peri_user,l10n_br_cte_modal_aereo_peri_user,model_l10n_br_cte_modal_aereo_peri,base.group_user,1,1,1,1 + +l10n_br_cte_modal_rodoviario_user,l10n_br_cte_modal_rodoviario_user,model_l10n_br_cte_modal_rodo,base.group_user,1,1,1,1 +l10n_br_cte_modal_rodoviario_occ_user,l10n_br_cte_modal_rodoviario_occ_user,model_l10n_br_cte_modal_rodo_occ,base.group_user,1,1,1,1 + +l10n_br_cte_modal_ferrov_user,l10n_br_cte_modal_ferrov_user,model_l10n_br_cte_modal_ferrov,base.group_user,1,1,1,1 + +l10n_br_cte_modal_aquaviario_user,l10n_br_cte_modal_aquav_user,model_l10n_br_cte_modal_aquav,base.group_user,1,1,1,1 +l10n_br_cte_modal_aquaviario_balsa_user,l10n_br_cte_modal_aquav_balsa_user,model_l10n_br_cte_modal_aquav_balsa,base.group_user,1,1,1,1 + +l10n_br_cte_modal_duto_user,l10n_br_cte_modal_duto_user,model_l10n_br_cte_modal_duto,base.group_user,1,1,1,1 + +l10n_br_cte_normal_infos_user,l10n_br_cte_normal_infos_user,model_l10n_br_cte_normal_infos,base.group_user,1,1,1,1 +l10n_br_cte_cargo_quantity_infos_user,l10n_br_cte_cargo_quantity_infos_user,model_l10n_br_cte_cargo_quantity_infos,base.group_user,1,1,1,1 +l10n_br_cte_transported_vehicles_user,l10n_br_cte_transported_vehicles_user,model_l10n_br_cte_transported_vehicles,base.group_user,1,1,1,1 diff --git a/l10n_br_cte/static/description/icon.png b/l10n_br_cte/static/description/icon.png new file mode 100644 index 000000000000..4515bffd318d Binary files /dev/null and b/l10n_br_cte/static/description/icon.png differ diff --git a/l10n_br_cte/static/description/index.html b/l10n_br_cte/static/description/index.html new file mode 100644 index 000000000000..e1d718ef2fd5 --- /dev/null +++ b/l10n_br_cte/static/description/index.html @@ -0,0 +1,463 @@ + + + + + +CT-e + + + +
+

CT-e

+ + +

Alpha License: AGPL-3 OCA/l10n-brazil Translate me on Weblate Try me on Runboat

+

[ This file must be max 2-3 paragraphs, and is required. ]

+

This module extends the functionality of … to support … +and to allow you to …

+
+

Important

+

This is an alpha version, the data model and design can change at any time without warning. +Only for development or testing purpose, do not use in production. +More details on development status

+
+

Table of contents

+ +
+

Configuration

+
+
[ This file is optional, it should explain how to configure
+
the module before using it; it is aimed at advanced users. ]
+
+

To configure this module, you need to:

+
    +
  1. Go to …
  2. +
+
+alternative description +
+
+
+

Usage

+
+
[ This file must be present and contains the usage instructions
+
for end-users. As all other rst files included in the README, +it MUST NOT contain reStructuredText sections +only body text (paragraphs, lists, tables, etc). Should you need +a more elaborate structure to explain the addon, please create a +Sphinx documentation (which may include this file as a “quick start” +section). ]
+
+

To use this module, you need to:

+
    +
  1. Go to …
  2. +
+
+
+

Bug Tracker

+

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 +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • KMEE
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

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.

+

This module is part of the OCA/l10n-brazil project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/l10n_br_cte/tests/__init__.py b/l10n_br_cte/tests/__init__.py new file mode 100644 index 000000000000..8c797134a89c --- /dev/null +++ b/l10n_br_cte/tests/__init__.py @@ -0,0 +1,6 @@ +# from . import test_cte_serialize +# from . import test_cte_serialize_lc +# from . import test_cte_serialize_sn +from . import test_cte_import +# from . import test_cte_structure +# from . import test_cte_res_partner diff --git a/l10n_br_cte/tests/cte/v4_00/leiauteCTe/43120178408960000182570010000000041000000047-cte.xml b/l10n_br_cte/tests/cte/v4_00/leiauteCTe/43120178408960000182570010000000041000000047-cte.xml new file mode 100644 index 000000000000..b42362d85ea6 --- /dev/null +++ b/l10n_br_cte/tests/cte/v4_00/leiauteCTe/43120178408960000182570010000000041000000047-cte.xml @@ -0,0 +1,162 @@ + + + + + 43 + 00000004 + 6353 + SERV. TRANSPORTE + 57 + 1 + 4 + 2012-01-06T17:25:56-02:00 + 1 + 1 + 7 + 2 + 0 + 0 + 104 + 4213609 + PORTO UNIAO + SC + 01 + + 4213609 + PORTO UNIAO + SC + 4128203 + UNIAO DA VITORIA + PR + 1 + 9 + + 0 + + + + MASTER + NOTA FISCAL DE PRODUTOR RURAL N. 253-254-255 + + + 78408960000182 + 251079554 + KERBER E CIA. LTDA. + PEDREIRA + + ESTRADA VELHA DE PALMAS + S/N + CAIXA POSTAL 268 + RIO DA AREIA + 4213609 + PORTO UNIAO + 89400000 + SC + 4235224933 + + + + 78408960000182 + 251079554 + KERBER E CIA. LTDA. + PEDREIRA + 4235224933 + + ESTRADA VELHA DE PALMAS + S/N + CAIXA POSTAL 268 + RIO DA AREIA + 4213609 + PORTO UNIAO + 89400000 + SC + 1058 + Brasil + + pedreira@kerberecia.com.br + + + 78408960000182 + 251079554 + KERBER E CIA. LTDA. + 4235224933 + + ESTRADA VELHA DE PALMAS + S/N + CAIXA POSTAL 268 + RIO DA AREIA + 4213609 + PORTO UNIAO + 89400000 + SC + 1058 + Brasil + + + + 81639791000104 + 3010264714 + HOBI E CIA LTDA. - MATRIZ + 4235211922 + + AUTO VIA JOAO REOLON + 02105 + CENTRO + 4128203 + UNIAO DA VITORIA + 84600000 + PR + 1058 + Brasil + + + + 81639791000104 + 3010264714 + HOBI E CIA LTDA. - MATRIZ + 4235211922 + + AUTO VIA JOAO REOLON + 02105 + CENTRO + 4128203 + UNIAO DA VITORIA + 84600000 + PR + 1058 + Brasil + + + + 2300.00 + 2300.00 + + + + + 00 + 2300.00 + 12.00 + 276.00 + + + + + + 174.38 + Pedra Brita + Pedra Brita a Granel + + 00 + TON + 9.3000 + + + + + 04150238 + + + + + diff --git a/l10n_br_cte/tests/cte/v4_00/leiauteCTe/51160624686092000173570010000000031000000020-cte.XML b/l10n_br_cte/tests/cte/v4_00/leiauteCTe/51160624686092000173570010000000031000000020-cte.XML new file mode 100644 index 000000000000..24a88299e1a8 --- /dev/null +++ b/l10n_br_cte/tests/cte/v4_00/leiauteCTe/51160624686092000173570010000000031000000020-cte.XML @@ -0,0 +1,130 @@ + + + + + 51 + 00000002 + 5353 + PREST. DE SERV. TRANSPORTE A ESTAB. COMERCIAL.. + 57 + 1 + 3 + 2016-06-07T17:58:40-02:00 + 1 + 1 + 0 + 2 + 0 + 0 + 2.0.1 + 5108402 + VARZEA GRANDE + MT + 01 + 9 + 5101407 + ARIPUANA + MT + 5108402 + VARZEA GRANDE + MT + 1 + 9 + + 0 + + + + MASTER + + NOTA FISCAL DE PRODUTOR RURAL N. 253-254-255 + + + 24686092000173 + 136268870 + P J TOSTA TRANSPORTES ME + + RUA RENATO JOSE DOS SANTOS + 10 + COHAB PRIMAVERA + 5108402 + VARZEA GRANDE + 78132712 + MT + 6599893021 + + + + 72304553915 + ISENTO + ROGERIO MARCIO TOLARDO + 6635651335 + + GLEBA GUARIBA VI LOTE RURAL 38 + SN + ZONA RURAL + 5101407 + ARIPUANA + 78325000 + MT + 1058 + BRASIL + + + + 03851469000122 + 131952927 + FRICAL FRIGORIFICO LTDA EPP + 6536342236 + + ESTRADA SOUZA LIMA + SN + SOUZA LIMA + 5108402 + VARZEA GRANDE + 78110000 + MT + 1058 + BRASIL + + + + 4000.00 + 4000.00 + + + + + 90 + 1 + + + + + + 79400.00 + GADO + GADO + + 01 + KILO + 50.0000 + + + + + 99 + NOTA FISCAL MANUAL + 123456 + 2016-06-05 + 79400.00 + + + + + 05277204 + + + + + diff --git a/l10n_br_cte/tests/test_cte_document.py b/l10n_br_cte/tests/test_cte_document.py new file mode 100644 index 000000000000..7d23614fb464 --- /dev/null +++ b/l10n_br_cte/tests/test_cte_document.py @@ -0,0 +1,86 @@ +# @ 2020 KMEE INFORMATICA LTDA - www.kmee.com.br - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from datetime import datetime + +from nfelib.nfe.ws.edoc_legacy import CTeAdapter + +from odoo.exceptions import UserError +from odoo.tests import SavepointCase + + +class CTeDocumentTest(SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + FiscalDocument = cls.env["l10n_br_fiscal.document"] + + cls.acre_state = cls.env.ref("base.state_br_ac") + cls.cte_document_type_id = cls.env.ref("l10n_br_fiscal.document_57") + cls.sn_company_id = cls.env.ref("l10n_br_base.empresa_simples_nacional") + cls.sn_company_id.processador_edoc = "erpbrasil.edoc" + cls.cte_id = FiscalDocument.create( + { + "document_type_id": cls.cte_document_type_id.id, + "company_id": cls.sn_company_id.id, + "document_number": "70000", + "document_serie": "30", + "document_data": datetime.now(), + } + ) + + def test_cte_compute_fields(self): + self.cte_id.fiscal_additional_data = "TEST FISCAL ADDITIONAL DATA" + self.cte_id.customer_additional_data = "TEST CUSTOMER ADDITIONAL DATA" + + self.assertTrue(self.cte_id.cte40_infAdFisco) + self.assertTrue(self.cte_id.cte40_infCpl) + + def test_cte_inverse_fields(self): + self.cte_id.cte40_UFIni = self.acre_state.code + self.cte_id.cte40_UFFim = self.acre_state.code + self.assertEqual(self.cte_id.cte_initial_state_id, self.acre_state) + self.assertEqual(self.cte_id.cte_final_state_id, self.acre_state) + + self.cte_id.cte40_UF = self.acre_state.ibge_code + self.assertEqual(self.cte_id.company_id.partner_id.state_id, self.acre_state) + + self.cte_id.cte40_infMunCarrega = [ + ( + 0, + 0, + { + "cte40_cMunCarrega": "1200013", + "cte40_xMunCarrega": "Acrelândia", + }, + ) + ] + self.assertIn( + self.env.ref("l10n_br_base.city_1200013"), + self.cte_id.cte_loading_city_ids, + ) + + def test_cte_processor(self): + processor = self.cte_id._edoc_processor() + self.assertTrue(isinstance(processor, CTeAdapter)) + + self.cte_id.document_type_id = False + processor = self.cte_id._edoc_processor() + self.assertFalse(isinstance(processor, CTeAdapter)) + + self.cte_id.document_type_id = self.cte_document_type_id + + self.cte_id.company_id.certificate_nfe_id = False + processor = self.cte_id._edoc_processor() + self.assertTrue(isinstance(processor, CTeAdapter)) + + self.cte_id.company_id.certificate_ecnpj_id = False + with self.assertRaises(UserError): + processor = self.cte_id._edoc_processor() + + def test_generate_key(self): + self.cte_id._generate_key() + self.assertTrue(self.cte_id.document_key) + self.assertTrue(self.cte_id.key_random_code) + self.assertTrue(self.cte_id.key_check_digit) diff --git a/l10n_br_cte/tests/test_cte_import.py b/l10n_br_cte/tests/test_cte_import.py new file mode 100644 index 000000000000..92c3d53d7969 --- /dev/null +++ b/l10n_br_cte/tests/test_cte_import.py @@ -0,0 +1,73 @@ +# @ 2020 KMEE INFORMATICA LTDA - www.kmee.com.br - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +import logging + +import nfelib +import pkg_resources +from nfelib.cte.bindings.v4_0.cte_v4_00 import Tcte + +from odoo.models import NewId +from odoo.tests import SavepointCase + +_logger = logging.getLogger(__name__) + + +class CTeImportTest(SavepointCase): + def test_import_in_cte_dry_run(self): + res_items = ( + "cte", + "samples", + "v4_0", + "51160624686092000173570010000000031000000020-cte.XML", + ) + + resource_path = "/".join(res_items) + cte_stream = pkg_resources.resource_stream(nfelib.__name__, resource_path) + binding = Tcte.from_xml(cte_stream.read().decode()) + + cte = ( + self.env["cte.40.tcte_infcte"] + .with_context(tracking_disable=True, edoc_type="in") + .build_from_binding("cte", "40", binding.infCte, dry_run=True) + ) + assert isinstance(cte.id, NewId) + self._check_cte(cte) + + def test_import_in_cte(self): + res_items = ( + "cte", + "samples", + "v4_0", + "51160624686092000173570010000000031000000020-cte.XML", + ) + resource_path = "/".join(res_items) + cte_stream = pkg_resources.resource_stream(nfelib.__name__, resource_path) + binding = Tcte.from_xml(cte_stream.read().decode()) + cte = ( + self.env["cte.40.tcte_infcte"] + .with_context(tracking_disable=True, edoc_type="in") + .build_from_binding("cte", "40", binding.infCte, dry_run=False) + ) + + assert isinstance(cte.id, int) + self._check_cte(cte) + + def _check_cte(self, cte): + self.assertEqual(type(cte)._name, "l10n_br_fiscal.document") + + # ide + self.assertEqual(cte.cte40_nCT, "3") + # self.assertEqual(cte.cte40_infMunCarrega[0].cte40_xMunCarrega, "IVINHEMA") + self.assertEqual(cte.cte40_UFIni, "MT") + self.assertEqual(cte.cte40_UFFim, "MT") + + # # modal + # self.assertEqual(cte.cte40_placa, "XXX1228") + # self.assertEqual(cte.cte40_tara, "0") + # self.assertEqual(cte.cte40_condutor[0].cte40_xNome, "TESTE") + # self.assertEqual(len(cte.cte40_veicReboque), 0) + + self.assertEqual(cte.cte40_verProc, "104") + + def test_import_out_cte(self): + "(can be useful after an ERP migration)" diff --git a/l10n_br_cte/tests/test_cte_res_partner.py b/l10n_br_cte/tests/test_cte_res_partner.py new file mode 100644 index 000000000000..204e1c04775f --- /dev/null +++ b/l10n_br_cte/tests/test_cte_res_partner.py @@ -0,0 +1,66 @@ +# Copyright 2023 KMEE +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from erpbrasil.base.fiscal import cnpj_cpf +from erpbrasil.base.misc import format_zipcode + +from odoo.tests import TransactionCase + + +class TestCTeResPartner(TransactionCase): + def setUp(self): + super().setUp() + + self.partner_id = self.env.ref("l10n_br_base.res_partner_kmee") + + def test_compute_fields(self): + self.partner_id.country_id = self.env.ref("base.us") + + self.assertEqual( + self.partner_id.cte40_choice_tcontractor, "cte40_idEstrangeiro" + ) + self.assertEqual(self.partner_id.cte40_idEstrangeiro, self.partner_id.cnpj_cpf) + + def test_inverse_fields(self): + self.partner_id.cte40_idEstrangeiro = "999999999999" + self.assertEqual(self.partner_id.vat, self.partner_id.cte40_idEstrangeiro) + + self.partner_id.cte40_CNPJ = "97414612000162" + self.assertEqual( + self.partner_id.cnpj_cpf, cnpj_cpf.formata(self.partner_id.cte40_CNPJ) + ) + + self.partner_id.cte40_CPF = "48737433032" + self.assertEqual( + self.partner_id.cnpj_cpf, cnpj_cpf.formata(self.partner_id.cte40_CPF) + ) + + self.partner_id.cte40_IE = "630514648079" + self.assertEqual(self.partner_id.inscr_est, self.partner_id.cte40_IE) + + self.partner_id.cte40_CEP = "04324240" + self.assertEqual(self.partner_id.zip, format_zipcode(self.partner_id.cte40_CEP)) + + self.partner_id.cte40_fone = "(99) 9999-9999" + self.assertEqual(self.partner_id.phone, self.partner_id.cte40_fone) + + self.partner_id.cte40_cPais = "1058" + self.partner_id.cte40_UF = "SP" + self.partner_id.cte40_cMun = "3550308" + self.assertEqual( + self.partner_id.country_id.bc_code, self.partner_id.cte40_cPais + ) + self.assertEqual(self.partner_id.state_id.code, self.partner_id.cte40_UF) + self.assertEqual(self.partner_id.city_id.ibge_code, self.partner_id.cte40_cMun) + + self.partner_id.cte40_xLgr = "TESTE" + self.assertEqual(self.partner_id.street_name, self.partner_id.cte40_xLgr) + + self.partner_id.cte40_nro = "999" + self.assertEqual(self.partner_id.street_number, self.partner_id.cte40_nro) + + self.partner_id.cte40_xCpl = "TESTE" + self.assertEqual(self.partner_id.street2, self.partner_id.cte40_xCpl) + + self.partner_id.cte40_xBairro = "TESTE" + self.assertEqual(self.partner_id.district, self.partner_id.cte40_xBairro) diff --git a/l10n_br_cte/tests/test_cte_serialize.py b/l10n_br_cte/tests/test_cte_serialize.py new file mode 100644 index 000000000000..76ba19856958 --- /dev/null +++ b/l10n_br_cte/tests/test_cte_serialize.py @@ -0,0 +1,234 @@ +# @ 2020 KMEE INFORMATICA LTDA - www.kmee.com.br - +# Gabriel Cardoso de Faria +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import logging +import os +from datetime import datetime + +from xmldiff import main + +from odoo.tests.common import TransactionCase +from odoo.tools import config + +from odoo.addons import l10n_br_cte + +_logger = logging.getLogger(__name__) + + +class TestCTeSerialize(TransactionCase): + def setUp(self, cte_list): + super().setUp() + self.cte_list = cte_list + for cte_data in self.cte_list: + cte = self.env.ref(cte_data["record_ref"]) + cte_data["cte"] = cte + self.prepare_test_cte(cte) + + def prepare_test_cte(self, cte): + """ + Performs actions necessary to prepare an CTe of the demo data to + perform the tests + """ + if cte.state != "em_digitacao": # 2nd test run + cte.action_document_back2draft() + + cte._compute_amount() + cte.action_document_confirm() + cte.document_date = datetime.strptime( + "2020-01-01T11:00:00", "%Y-%m-%dT%H:%M:%S" + ) + cte.cte40_cMDF = "20801844" + + if cte.cte_modal == "1": + self.prepare_modal_rodoviario_data(cte) + elif cte.cte_modal == "2": + self.prepare_modal_aereo_data(cte) + elif cte.cte_modal == "3": + self.prepare_modal_aquaviario_data(cte) + elif cte.cte_modal == "4": + self.prepare_modal_ferroviario_data(cte) + + cte._document_export() + + def prepare_modal_rodoviario_data(self, cte): + cte.cte40_codAgPorto = "12345678" + + # infANTT + cte.cte40_RNTRC = "12345678" + cte.cte40_categCombVeic = "02" + cte.cte40_infCIOT = [ + ( + 0, + 0, + { + "is_company": False, + "cte40_CIOT": "123456789101", + "cte40_CPF": "99999999999", + }, + ), + ] + cte.cte40_disp = [ + ( + 0, + 0, + { + "cte40_CNPJForn": "99999999999999", + "cte40_CNPJPg": "99999999999999", + "cte40_nCompra": "1234", + "cte40_vValePed": 5, + "cte40_tpValePed": "01", + }, + ), + ] + cte.cte40_infPag = [ + ( + 0, + 0, + { + "partner_id": self.env.ref("l10n_br_base.res_partner_intel").id, + "cte40_vContrato": 5, + "cte40_indPag": "0", + "payment_type": "pix", + "cte40_PIX": "99999999999999", + "cte40_comp": [ + ( + 0, + 0, + { + "cte40_tpComp": "01", + "cte40_vComp": 5, + }, + ) + ], + }, + ), + ] + + # veicTracao + cte.cte40_cInt = "1" + cte.cte40_RENAVAM = "42423325472" + cte.cte40_placa = "AAA1233" + cte.cte40_tpTransp = False + cte.cte40_tara = 7500 + cte.cte40_capKG = 42500 + cte.cte40_capM3 = 300 + cte.cte40_tpRod = "03" + cte.cte40_tpCar = "00" + cte.rodo_vehicle_state_id = self.env.ref("base.state_br_ac").id + cte.cte40_condutor = [ + ( + 0, + 0, + { + "cte40_xNome": "Teste", + "cte40_CPF": "99999999999", + }, + ), + ( + 0, + 0, + { + "cte40_xNome": "Teste2", + "cte40_CPF": "99999999999", + }, + ), + ] + + # veicReboque + cte.cte40_veicReboque = [ + ( + 0, + 0, + { + "cte40_cInt": "2", + "cte40_placa": "AAA4321", + "cte40_RENAVAM": "11557770179", + "cte40_tara": 7200, + "cte40_capKG": 42500, + "cte40_capM3": 300, + "cte40_tpCar": "00", + "cte40_UF": "AC", + }, + ) + ] + + def prepare_modal_aereo_data(self, cte): + cte.cte40_nac = "TEST" + cte.cte40_matr = "TEST" + cte.cte40_nVoo = "123456789" + cte.cte40_cAerEmb = "OACI" + cte.cte40_cAerDes = "OACI" + cte.cte40_dVoo = datetime.strptime("2020-01-01", "%Y-%m-%d") + + def prepare_modal_aquaviario_data(self, cte): + cte.cte40_irin = "1234567899" + cte.cte40_tpEmb = "01" + cte.cte40_cEmbar = "123456" + cte.cte40_xEmbar = "teste" + cte.cte40_nViag = "123456" + cte.cte40_cPrtEmb = "BRADR" + cte.cte40_cPrtDest = "BRAFU" + cte.cte40_infTermCarreg = [ + (0, 0, {"loading_harbor": "BRADR"}), + (0, 0, {"loading_harbor": "BRANT"}), + ] + cte.cte40_infTermDescarreg = [ + (0, 0, {"unloading_harbor": "BRAFU"}), + (0, 0, {"unloading_harbor": "BRBZC"}), + ] + + def prepare_modal_ferroviario_data(self, cte): + cte.cte40_dhTrem = datetime.strptime("2020-01-01T11:00:00", "%Y-%m-%dT%H:%M:%S") + cte.cte40_xPref = "TES" + cte.cte40_xOri = "TES" + cte.cte40_xDest = "TES" + cte.cte40_qVag = 2 + cte.cte40_vag = [ + ( + 0, + 0, + { + "cte40_pesoBC": 500, + "cte40_pesoR": 1, + "cte40_tpVag": 123, + "cte40_serie": 123, + "cte40_nVag": 123, + "cte40_nSeq": 123, + "cte40_TU": 1, + }, + ), + ( + 0, + 0, + { + "cte40_pesoBC": 500, + "cte40_pesoR": 1, + "cte40_tpVag": 321, + "cte40_serie": 321, + "cte40_nVag": 321, + "cte40_nSeq": 321, + "cte40_TU": 1, + }, + ), + ] + + def serialize_xml(self, cte_data): + cte = cte_data["cte"] + xml_path = os.path.join( + l10n_br_cte.__path__[0], + "tests", + "cte", + "V4_00", + "leiauteCTe", + cte_data["xml_file"], + ) + output = os.path.join( + config["data_dir"], + "filestore", + self.cr.dbname, + cte.send_file_id.store_fname, + ) + _logger.info(f"XML file saved at {output}") + diff = main.diff_files(output, xml_path) + return diff diff --git a/l10n_br_cte/tests/test_cte_serialize_lc.py b/l10n_br_cte/tests/test_cte_serialize_lc.py new file mode 100644 index 000000000000..cead0bf51335 --- /dev/null +++ b/l10n_br_cte/tests/test_cte_serialize_lc.py @@ -0,0 +1,28 @@ +# @ 2020 KMEE INFORMATICA LTDA - www.kmee.com.br - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +import logging + +from .test_cte_serialize import TestCTeSerialize + +_logger = logging.getLogger(__name__) + + +class TestCTeExportLC(TestCTeSerialize): + def setUp(self): + cte_list = [ + { + "record_ref": "l10n_br_cte.demo_cte_lc_modal_ferroviario", + "xml_file": "CTe35230905472475000102580200000602011208018449.xml", + }, + { + "record_ref": "l10n_br_cte.demo_cte_lc_modal_rodoviario", + "xml_file": "CTe35230905472475000102580200000602071611554500.xml", + }, + ] + super().setUp(cte_list) + + def test_serialize_xml(self): + for cte_data in self.cte_list: + diff = self.serialize_xml(cte_data) + _logger.info("Diff with expected XML (if any): %s" % (diff,)) + assert len(diff) == 0 diff --git a/l10n_br_cte/tests/test_cte_serialize_sn.py b/l10n_br_cte/tests/test_cte_serialize_sn.py new file mode 100644 index 000000000000..5ed1aae0da1b --- /dev/null +++ b/l10n_br_cte/tests/test_cte_serialize_sn.py @@ -0,0 +1,29 @@ +# @ 2020 KMEE INFORMATICA LTDA - www.kmee.com.br - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +import logging + +from .test_cte_serialize import TestCTeSerialize + +_logger = logging.getLogger(__name__) + + +class TestCTeExportSN(TestCTeSerialize): + def setUp(self): + cte_list = [ + { + "record_ref": "l10n_br_cte.demo_cte_sn_modal_aereo", + "xml_file": "CTe35230905472475000102580200000602081550195716.xml", + }, + { + "record_ref": "l10n_br_cte.demo_cte_sn_modal_aquaviario", + "xml_file": "CTe35231005472475000102580200000602161434590525.xml", + }, + ] + + super().setUp(cte_list) + + def test_serialize_xml(self): + for cte_data in self.cte_list: + diff = self.serialize_xml(cte_data) + _logger.info("Diff with expected XML (if any): %s" % (diff,)) + assert len(diff) == 0 diff --git a/l10n_br_cte/tests/test_cte_structure.py b/l10n_br_cte/tests/test_cte_structure.py new file mode 100644 index 000000000000..213702f076f3 --- /dev/null +++ b/l10n_br_cte/tests/test_cte_structure.py @@ -0,0 +1,160 @@ +# @ 2020 KMEE INFORMATICA LTDA - www.kmee.com.br - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from io import StringIO + +from odoo.tests import SavepointCase + +from odoo.addons.spec_driven_model.models.spec_models import SpecModel + +from ..models.document import CTe + + +class CTeStructure(SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + @classmethod + def get_stacked_tree(cls, klass): + """ + # > means the content of the m2o is stacked in the parent + # - means standard m2o. Eventually followd by the mapped Odoo model + # ≡ means o2m. Eventually followd by the mapped Odoo model + """ + spec_module = "odoo.addons.l10n_br_cte_spec.models.v4_0.cte_tipos_basico_v4_00" + spec_prefix = "cte40" + stacking_settings = { + "odoo_module": getattr(klass, f"_{spec_prefix}_odoo_module"), + "stacking_mixin": getattr(klass, f"_{spec_prefix}_stacking_mixin"), + "stacking_points": getattr(klass, f"_{spec_prefix}_stacking_points"), + "stacking_skip_paths": getattr( + klass, f"_{spec_prefix}_stacking_skip_paths", [] + ), + "stacking_force_paths": getattr( + klass, f"_{spec_prefix}_stacking_force_paths", [] + ), + } + node = SpecModel._odoo_name_to_class( + stacking_settings["stacking_mixin"], spec_module + ) + tree = StringIO() + visited = set() + for kind, n, path, field_path, child_concrete in klass._visit_stack( + cls.env, node, stacking_settings + ): + visited.add(n) + path_items = path.split(".") + indent = " ".join(["" for i in range(0, len(path_items))]) + if kind == "stacked": + line = "\n%s> <%s>" % (indent, path.split(".")[-1]) + elif kind == "one2many": + line = "\n%s \u2261 <%s> %s" % ( + indent, + field_path, + child_concrete or "", + ) + elif kind == "many2one": + line = "\n%s - <%s> %s" % (indent, field_path, child_concrete or "") + tree.write(line.rstrip()) + tree_txt = tree.getvalue() + return tree_txt, visited + + def test_inherited_fields(self): + assert "cte40_CNPJ" in self.env["res.company"]._fields.keys() + + def test_concrete_spec(self): + # this ensure basic SQL is set up + self.assertEqual( + len( + self.env["cte.40.infmuncarrega"].search( + [("cte40_cMunCarrega", "=", "NO_RECORD")] + ) + ), + 0, + ) + + def test_m2o_concrete_to_concrete_spec(self): + self.assertEqual( + self.env["cte.40.infcte"] + ._fields["cte40_infCTe_infMunDescarga_id"] + .comodel_name, + "cte.40.infmundescarga", + ) + + def test_o2m_concrete_to_concrete_spec(self): + self.assertEqual( + self.env["cte.40.ide"]._fields["cte40_infMunCarrega"].comodel_name, + "cte.40.infmuncarrega", + ) + + def test_m2o_stacked_to_odoo(self): + self.assertEqual( + self.env["l10n_br_fiscal.document"]._fields["cte40_prodPred"].comodel_name, + "product.product", + ) + + def test_o2m_to_odoo(self): + self.assertEqual( + self.env["l10n_br_fiscal.document"] + ._fields["cte40_infEmbComb"] + .comodel_name, + "l10n_br_cte.modal.aquaviario.comboio", + ) + self.assertEqual( + len( + self.env["l10n_br_cte.modal.aquaviario.comboio"].search( + [("cte40_cEmbComb", "=", "NO_RECORD")] + ) + ), + 0, + ) + + def test_m2o_stacked_to_concrete(self): + # not stacked because optional + model = ( + self.env["l10n_br_fiscal.document"] + ._fields["cte40_infSolicNFF"] + .comodel_name + ) + self.assertEqual(model, "cte.40.infsolicnff") + + # def test_m2o_stacked(self): + # # not stacked because optional + # cte_model = self.env["l10n_br_fiscal.document"] + # # cte40_cana is optional so its fields shoudn't be stacked + # assert "cte40_XXX" not in cte_model._fields.keys() + + def test_doc_stacking_points(self): + doc_keys = [ + "cte40_ide", + "cte40_infModal", + "cte40_infDoc", + "cte40_tot", + "cte40_infAdic", + # "cte40_trem", + # "cte40_infANTT", + # "cte40_valePed", + # "cte40_veicTracao", + # "cte40_infBanc", + ] + keys = [ + k + for k in self.env["l10n_br_fiscal.document"] + .with_context(spec_schema="cte", spec_version="30") + ._get_stacking_points() + .keys() + ] + self.assertEqual(sorted(keys), sorted(doc_keys)) + + def test_doc_tree(self): + base_class = self.env["l10n_br_fiscal.document"] + tree, visited = self.get_stacked_tree(base_class) + self.assertEqual(tree, CTe.INFCTE_TREE) + self.assertEqual(len(visited), 6) # all stacked classes + + def test_m2o_force_stack(self): + pass + + def test_doc_visit_stack(self): + pass diff --git a/l10n_br_cte/views/cte_document.xml b/l10n_br_cte/views/cte_document.xml new file mode 100644 index 000000000000..1f092ef14c5e --- /dev/null +++ b/l10n_br_cte/views/cte_document.xml @@ -0,0 +1,149 @@ + + + + + + l10n_br_cte.document.form.inherit + l10n_br_fiscal.document + 10 + + + + [('document_type_id.code', 'in', ['57', '08', '09', '10', '11', '26', '67', '8B'])] + + + [('document_type_id.code', 'in', ['57', '08', '09', '10', '11', '26', '67', '8B'])] + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + + + + +
+ + + + + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
diff --git a/l10n_br_cte/views/document_line.xml b/l10n_br_cte/views/document_line.xml new file mode 100644 index 000000000000..e2da706e8b22 --- /dev/null +++ b/l10n_br_cte/views/document_line.xml @@ -0,0 +1,13 @@ + + + + + + document.line + + + + + + diff --git a/l10n_br_cte/views/document_related.xml b/l10n_br_cte/views/document_related.xml new file mode 100644 index 000000000000..1a66fbe13e15 --- /dev/null +++ b/l10n_br_cte/views/document_related.xml @@ -0,0 +1,17 @@ + + + + + + document.related.form (in l10n_br_cte) + document.related + + + + + + + + + diff --git a/l10n_br_cte/views/res_company.xml b/l10n_br_cte/views/res_company.xml new file mode 100644 index 000000000000..b4faac397deb --- /dev/null +++ b/l10n_br_cte/views/res_company.xml @@ -0,0 +1,31 @@ + + + + + + cte.res.company.form + res.company + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n_br_cte/views/res_partner.xml b/l10n_br_cte/views/res_partner.xml new file mode 100644 index 000000000000..9d738eaa174c --- /dev/null +++ b/l10n_br_cte/views/res_partner.xml @@ -0,0 +1,17 @@ + + + + + + res.partner.form (in l10n_br_cte) + res.partner + + + + + + + + + diff --git a/l10n_br_cte/wizards/document_correction_wizard.xml b/l10n_br_cte/wizards/document_correction_wizard.xml new file mode 100644 index 000000000000..7b93ccffab13 --- /dev/null +++ b/l10n_br_cte/wizards/document_correction_wizard.xml @@ -0,0 +1,30 @@ + + + + + + l10n_br_fiscal.document.correction.wizard + + + + + + + +
+ Por favor, para carta de correção de CT-e, no campo Justificativa informe por linha (Grupo Alterado;Campo Alterado;Valor Alterado).
+ Exemplo:
+ compl;xObs;Nova Observação
+ ide;cfop;6353 +
+
+
+
+
+ +
diff --git a/l10n_br_cte_spec/__manifest__.py b/l10n_br_cte_spec/__manifest__.py index 63fe69ab1ce8..e7701f6a4616 100644 --- a/l10n_br_cte_spec/__manifest__.py +++ b/l10n_br_cte_spec/__manifest__.py @@ -11,4 +11,7 @@ "development_status": "Alpha", "maintainers": ["rvalyi"], "website": "https://github.com/OCA/l10n-brazil", + "data": [ + "security/ir.model.access.csv", + ], } diff --git a/l10n_br_cte_spec/models/__init__.py b/l10n_br_cte_spec/models/__init__.py index 1d382931ae2d..3140ceedcffa 100644 --- a/l10n_br_cte_spec/models/__init__.py +++ b/l10n_br_cte_spec/models/__init__.py @@ -1,2 +1,2 @@ -from . import spec_models +from . import spec_mixin from . import v4_0 diff --git a/l10n_br_cte_spec/models/spec_models.py b/l10n_br_cte_spec/models/spec_mixin.py similarity index 74% rename from l10n_br_cte_spec/models/spec_models.py rename to l10n_br_cte_spec/models/spec_mixin.py index 52d5afaccfb3..e3b5d6fa694d 100644 --- a/l10n_br_cte_spec/models/spec_models.py +++ b/l10n_br_cte_spec/models/spec_mixin.py @@ -7,13 +7,10 @@ class CteSpecMixin(models.AbstractModel): _description = "Abstract Model" _name = "spec.mixin.cte" - _field_prefix = "cte40_" - _schema_name = "cte" - _schema_version = "4.0.0" - _odoo_module = "l10n_br_cte" - _spec_module = "odoo.addons.l10n_br_cte_spec.models.v4_0.cte_tipos_basico_v4_00" - _binding_module = "nfelib.cte.bindings.v4_0.cte_v4_00" - _spec_tab_name = "cte" + _cte40_odoo_module = ( + "odoo.addons.l10n_br_cte_spec.models.v4_0.cte_tipos_basico_v4_00" + ) + _cte40_binding_module = "nfelib.cte.bindings.v4_0.cte_tipos_basico_v4_00" brl_currency_id = fields.Many2one( comodel_name="res.currency", diff --git a/l10n_br_cte_spec/models/v4_0/cte_modal_aereo_v4_00.py b/l10n_br_cte_spec/models/v4_0/cte_modal_aereo_v4_00.py index ca0ea2c6db56..4ded85769bc5 100644 --- a/l10n_br_cte_spec/models/v4_0/cte_modal_aereo_v4_00.py +++ b/l10n_br_cte_spec/models/v4_0/cte_modal_aereo_v4_00.py @@ -126,7 +126,6 @@ class NatCarga(models.AbstractModel): ) - class Tarifa(models.AbstractModel): "Informações de tarifa" _description = textwrap.dedent(" %s" % (__doc__,)) diff --git a/l10n_br_cte_spec/models/v4_0/cte_tipos_basico_v4_00.py b/l10n_br_cte_spec/models/v4_0/cte_tipos_basico_v4_00.py index 235fa73e02f7..0cfffed535bf 100644 --- a/l10n_br_cte_spec/models/v4_0/cte_tipos_basico_v4_00.py +++ b/l10n_br_cte_spec/models/v4_0/cte_tipos_basico_v4_00.py @@ -442,6 +442,7 @@ class TendeEmi(models.AbstractModel): _name = "cte.40.tendeemi" _inherit = "spec.mixin.cte" _binding_type = "TendeEmi" + _generateds_type = "TEndeEmi" cte40_xLgr = fields.Char(string="Logradouro", xsd_required=True) @@ -453,8 +454,8 @@ class TendeEmi(models.AbstractModel): cte40_cMun = fields.Char( string="Código do município", - xsd_required=True, xsd_type="TCodMunIBGE", + xsd_required=True, help="Código do município (utilizar a tabela do IBGE)", ) @@ -469,12 +470,12 @@ class TendeEmi(models.AbstractModel): cte40_fone = fields.Char(string="Telefone", xsd_type="TFone") -class Tendereco(models.AbstractModel): +class Tendernac(models.AbstractModel): "Tipo Dados do Endereço" _description = textwrap.dedent(" %s" % (__doc__,)) - _name = "cte.40.tendereco" + _name = "cte.40.tendernac" _inherit = "spec.mixin.cte" - _binding_type = "Tendereco" + _binding_type = "Tendernac" cte40_xLgr = fields.Char(string="Logradouro", xsd_required=True) @@ -489,20 +490,20 @@ class Tendereco(models.AbstractModel): xsd_required=True, xsd_type="TCodMunIBGE", help=( - "Código do município (utilizar a tabela do IBGE)\nInformar 9999999" + "Código do município (utilizar a tabela do IBGE), informar 9999999" " para operações com o exterior." ), ) cte40_xMun = fields.Char( - string="Nome do município", + string="Nome do município, ", xsd_required=True, - help=("Nome do município\nInformar EXTERIOR para operações com o " "exterior."), + help=( + "Nome do município, , informar EXTERIOR para operações com o " "exterior." + ), ) - cte40_CEP = fields.Char( - string="CEP", help="CEP\nInformar os zeros não significativos" - ) + cte40_CEP = fields.Char(string="CEP") cte40_UF = fields.Selection( TUF, @@ -512,71 +513,158 @@ class Tendereco(models.AbstractModel): help="Sigla da UF\nInformar EX para operações com o exterior.", ) - cte40_cPais = fields.Char( - string="Código do país", help="Código do país\nUtilizar a tabela do BACEN" + +class Timp(models.AbstractModel): + "Tipo Dados do Imposto para CT-e OS" + _description = textwrap.dedent(" %s" % (__doc__,)) + _name = "cte.40.timp" + _inherit = "spec.mixin.cte" + _binding_type = "Timp" + + cte40_ICMSOutraUF = fields.Many2one( + comodel_name="cte.40.icmsoutrauf", + string="ICMS de empresa com UF diferente da emissora", + choice="icms", + xsd_choice_required=True, ) - cte40_xPais = fields.Char(string="Nome do país") +class IcmsOutraUF(models.AbstractModel): + """Partilha do ICMS entre a UF de origem e UF de destino ou a UF definida + na legislação + Operação interestadual para consumidor final com partilha do ICMS devido na + operação entre a UF de origem e a UF do destinatário ou ou a UF definida na + legislação. (Ex. UF da concessionária de entrega do veículos)""" -class Tendernac(models.AbstractModel): - "Tipo Dados do Endereço" _description = textwrap.dedent(" %s" % (__doc__,)) - _name = "cte.40.tendernac" + _name = "cte.40.icmspart" _inherit = "spec.mixin.cte" - _binding_type = "Tendernac" + _binding_type = "Timp.IcmsoutraUf" + _generateds_type = "ICMSOutraUFType" - cte40_xLgr = fields.Char(string="Logradouro", xsd_required=True) + cte40_CST = fields.Selection( + ICMSOUTRAUF_CST, + string="Tributação pelo ICMS", + xsd_required=True, + help=( + "Tributação pelo ICMS \n10 - Tributada e com cobrança do ICMS por " + "substituição tributária;\n90 – Outros." + ), + ) - cte40_nro = fields.Char(string="Número", xsd_required=True) + cte40_vBC = fields.Monetary( + string="Valor da BC do ICMS", + xsd_required=True, + xsd_type="TDec_1302", + currency_field="brl_currency_id", + ) - cte40_xCpl = fields.Char(string="Complemento") + cte40_pRedBC = fields.Float( + string="Percentual de redução da BC", + xsd_type="TDec_0302a04Opc", + digits=( + 3, + 2, + ), + ) - cte40_xBairro = fields.Char(string="Bairro", xsd_required=True) + cte40_pICMS = fields.Float( + string="Alíquota do ICMS", + xsd_required=True, + xsd_type="TDec_0302a04", + digits=( + 3, + 2, + ), + ) - cte40_cMun = fields.Char( - string="Código do município", + cte40_vICMS = fields.Monetary( + string="Valor do ICMS", xsd_required=True, - xsd_type="TCodMunIBGE", - help=( - "Código do município (utilizar a tabela do IBGE), informar 9999999" - " para operações com o exterior." + xsd_type="TDec_1302", + currency_field="brl_currency_id", + ) + + + cte40_pMVAST = fields.Float( + string="Percentual da Margem", + xsd_type="TDec_0302a04Opc", + digits=( + 3, + 2, ), + help="Percentual da Margem de Valor Adicionado ICMS ST", ) - cte40_xMun = fields.Char( - string="Nome do município, ", + cte40_pRedBCST = fields.Float( + string="Percentual de redução da BC ICMS ST", + xsd_type="TDec_0302a04Opc", + digits=( + 3, + 2, + ), + ) + + cte40_vBCST = fields.Monetary( + string="Valor da BC do ICMS ST", xsd_required=True, - help=( - "Nome do município, , informar EXTERIOR para operações com o " "exterior." + xsd_type="TDec_1302", + currency_field="brl_currency_id", + ) + + cte40_pICMSST = fields.Float( + string="Alíquota do ICMS ST", + xsd_required=True, + xsd_type="TDec_0302a04", + digits=( + 3, + 2, ), ) - cte40_CEP = fields.Char(string="CEP") + cte40_vICMSST = fields.Monetary( + string="Valor do ICMS ST", + xsd_required=True, + xsd_type="TDec_1302", + currency_field="brl_currency_id", + ) - cte40_UF = fields.Selection( + cte40_pBCOp = fields.Float( + string="Percentual para determinação do valor", + xsd_required=True, + xsd_type="TDec_0302a04Opc", + digits=( + 3, + 2, + ), + help=( + "Percentual para determinação do valor da Base de Cálculo da " + "operação própria." + ), + ) + + cte40_UFST = fields.Selection( TUF, - string="Sigla da UF", + string="Sigla da UF para qual é devido o ICMS ST", xsd_required=True, xsd_type="TUf", - help="Sigla da UF\nInformar EX para operações com o exterior.", + help="Sigla da UF para qual é devido o ICMS ST da operação.", ) -class Timp(models.AbstractModel): - "Tipo Dados do Imposto CT-e" - _description = textwrap.dedent(" %s" % (__doc__,)) - _name = "cte.40.timp" - _inherit = "spec.mixin.cte" - _binding_type = "Timp" - - class TimpOs(models.AbstractModel): "Tipo Dados do Imposto para CT-e OS" _description = textwrap.dedent(" %s" % (__doc__,)) - _name = "cte.40.timpos" + _name = "cte.40.icmsos" _inherit = "spec.mixin.cte" - _binding_type = "TimpOs" + _binding_type = "IcmsOs" + + cte40_vBC = fields.Monetary( + string="Valor da Base de Cálculo do ICMS", + xsd_required=True, + xsd_type="TDec_1302", + currency_field="brl_currency_id", + ) class Tlocal(models.AbstractModel): @@ -1353,7 +1441,7 @@ class InfPercurso(models.AbstractModel): xsd_required=True, xsd_type="TUf", help=( - "Sigla das Unidades da Federação do percurso do veículo.\nNão é " + "Sigla das Unidades da Federação do percurso do veículo.\nNo é " "necessário repetir as UF de Início e Fim" ), ) @@ -1471,7 +1559,6 @@ class TcteOsEmit(models.AbstractModel): cte40_enderEmit = fields.Many2one( comodel_name="cte.40.tendeemi", string="Endereço do emitente", - xsd_required=True, xsd_type="TEndeEmi", ) @@ -1613,10 +1700,9 @@ class TcteOsImp(models.AbstractModel): _binding_type = "TcteOs.InfCte.Imp" cte40_ICMS = fields.Many2one( - comodel_name="cte.40.timpos", + comodel_name="cte.40.icmsos", string="Informações relativas ao ICMS", - xsd_required=True, - xsd_type="TImp", + xsd_type="Icms", ) cte40_vTotTrib = fields.Monetary( @@ -2472,7 +2558,6 @@ class TomaTerceiro(models.AbstractModel): cte40_enderToma = fields.Many2one( comodel_name="cte.40.tendereco", string="Dados do endereço", - xsd_required=True, xsd_type="TEndereco", ) @@ -2591,7 +2676,6 @@ class TgtveEmit(models.AbstractModel): cte40_enderEmit = fields.Many2one( comodel_name="cte.40.tendeemi", string="Endereço do emitente", - xsd_required=True, xsd_type="TEndeEmi", ) @@ -2648,7 +2732,6 @@ class TgtveRem(models.AbstractModel): cte40_enderReme = fields.Many2one( comodel_name="cte.40.tendereco", string="Dados do endereço", - xsd_required=True, xsd_type="TEndereco", ) @@ -2713,7 +2796,6 @@ class TgtveDest(models.AbstractModel): cte40_enderDest = fields.Many2one( comodel_name="cte.40.tendereco", string="Dados do endereço", - xsd_required=True, xsd_type="TEndereco", ) @@ -4009,6 +4091,7 @@ class TcteEmit(models.AbstractModel): _name = "cte.40.tcte_emit" _inherit = "spec.mixin.cte" _binding_type = "Tcte.InfCte.Emit" + _generateds_type = "emitType" cte40_CNPJ = fields.Char( string="CNPJ do emitente", @@ -4049,7 +4132,6 @@ class TcteEmit(models.AbstractModel): cte40_enderEmit = fields.Many2one( comodel_name="cte.40.tendeemi", string="Endereço do emitente", - xsd_required=True, xsd_type="TEndeEmi", ) @@ -4119,7 +4201,6 @@ class TcteRem(models.AbstractModel): cte40_enderReme = fields.Many2one( comodel_name="cte.40.tendereco", string="Dados do endereço", - xsd_required=True, xsd_type="TEndereco", ) @@ -4171,7 +4252,6 @@ class Exped(models.AbstractModel): cte40_enderExped = fields.Many2one( comodel_name="cte.40.tendereco", string="Dados do endereço", - xsd_required=True, xsd_type="TEndereco", ) @@ -4223,7 +4303,6 @@ class Receb(models.AbstractModel): cte40_enderReceb = fields.Many2one( comodel_name="cte.40.tendereco", string="Dados do endereço", - xsd_required=True, xsd_type="TEndereco", ) @@ -4288,7 +4367,6 @@ class TcteDest(models.AbstractModel): cte40_enderDest = fields.Many2one( comodel_name="cte.40.tendereco", string="Dados do endereço", - xsd_required=True, xsd_type="TEndereco", ) @@ -4364,8 +4442,6 @@ class TcteImp(models.AbstractModel): cte40_ICMS = fields.Many2one( comodel_name="cte.40.timp", string="Informações relativas ao ICMS", - xsd_required=True, - xsd_type="TImp", ) cte40_vTotTrib = fields.Monetary( @@ -4965,6 +5041,57 @@ class InfOutros(models.AbstractModel): ) +class TEndereco(models.AbstractModel): + "Tipo Dados do Endereço" + _description = textwrap.dedent(" %s" % (__doc__,)) + _name = "cte.40.tendereco" + _inherit = "spec.mixin.cte" + _binding_type = "Tendereco" + _generateds_type = "TEndereco" + + cte40_xLgr = fields.Char(string="Logradouro", xsd_required=True) + + cte40_nro = fields.Char(string="Número", xsd_required=True) + + cte40_xCpl = fields.Char(string="Complemento") + + cte40_xBairro = fields.Char(string="Bairro", xsd_required=True) + + cte40_cMun = fields.Char( + string="Código do município", + xsd_required=True, + xsd_type="TCodMunIBGE", + help=( + "Código do município (utilizar a tabela do IBGE)\nInformar 9999999" + " para operações com o exterior." + ), + ) + + cte40_xMun = fields.Char( + string="Nome do município", + xsd_required=True, + help=("Nome do município\nInformar EXTERIOR para operações com o " "exterior."), + ) + + cte40_CEP = fields.Char( + string="CEP", help="CEP\nInformar os zeros não significativos" + ) + + cte40_UF = fields.Selection( + TUF, + string="Sigla da UF", + xsd_required=True, + xsd_type="TUf", + help="Sigla da UF\nInformar EX para operações com o exterior.", + ) + + cte40_cPais = fields.Char( + string="Código do país", help="Código do país\nUtilizar a tabela do BACEN" + ) + + cte40_xPais = fields.Char(string="Nome do país") + + class DocAnt(models.AbstractModel): "Documentos de Transporte Anterior" _description = textwrap.dedent(" %s" % (__doc__,)) diff --git a/l10n_br_cte_spec/security/ir.model.access.csv b/l10n_br_cte_spec/security/ir.model.access.csv new file mode 100644 index 000000000000..1821f5fa2720 --- /dev/null +++ b/l10n_br_cte_spec/security/ir.model.access.csv @@ -0,0 +1,4 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_tcte_vprest_comp_user,access_tcte_vprest_comp,model_cte_40_tcte_vprest_comp,base.group_user,1,1,1,1 +access_veicnovos,access_veicnovos,model_cte_40_veicnovos,base.group_user,1,1,1,1 +access_tcte_infctecomp,access_tcte_infctecomp,model_cte_40_tcte_infctecomp,base.group_user,1,1,1,1 diff --git a/l10n_br_cte_spec/static/description/index.html b/l10n_br_cte_spec/static/description/index.html index b3c0f5544677..7120355cce7a 100644 --- a/l10n_br_cte_spec/static/description/index.html +++ b/l10n_br_cte_spec/static/description/index.html @@ -1,4 +1,3 @@ - @@ -9,10 +8,11 @@ /* :Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $ +:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. +Despite the name, some widely supported CSS2 features are used. See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to customize this style sheet. @@ -275,7 +275,7 @@ margin-left: 2em ; margin-right: 2em } -pre.code .ln { color: grey; } /* line numbers */ +pre.code .ln { color: gray; } /* line numbers */ pre.code, code { background-color: #eeeeee } pre.code .comment, code .comment { color: #5C6576 } pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } @@ -301,7 +301,7 @@ span.pre { white-space: pre } -span.problematic { +span.problematic, pre.problematic { color: red } span.section-subtitle { @@ -430,7 +430,9 @@

Contributors

Maintainers

This module is maintained by the OCA.

-Odoo Community Association + +Odoo Community Association +

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_cte_spec/tests/test_cte_import.py b/l10n_br_cte_spec/tests/test_cte_import.py index f6ec5c7acdf3..d70ad68e725d 100644 --- a/l10n_br_cte_spec/tests/test_cte_import.py +++ b/l10n_br_cte_spec/tests/test_cte_import.py @@ -6,13 +6,12 @@ import nfelib import pkg_resources -from nfelib.cte.bindings.v4_0.cte_v4_00 import Cte -from xsdata.formats.dataclass.parsers import XmlParser +from nfelib.cte.bindings.v4_0.cte_v4_00 import Tcte 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$") @@ -38,11 +37,7 @@ def build_attrs_fake(self, node, create_m2o=False): value = getattr(node, fname) if value is None: continue - key = "%s%s" % ( - self._field_prefix, - fname, - ) - + key = f"cte40_{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,15 +61,10 @@ def build_attrs_fake(self, node, create_m2o=False): if fields.get(key) and fields[key].get("related"): key = fields[key]["related"][0] comodel_name = fields[key]["relation"] - comodel = self.env.get(comodel_name) else: - comodel = None - for name in self.env.keys(): - if ( - hasattr(self.env[name], "_binding_type") - and self.env[name]._binding_type == binding_type - ): - comodel = self.env[name] + clean_type = binding_type.lower() + comodel_name = f"cte.40.{clean_type.split('.')[-1]}" + comodel = self.env.get(comodel_name) if comodel is None: # example skip ICMS100 class continue @@ -116,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.CteSpecMixin.build_fake = build_fake -spec_models.CteSpecMixin.build_attrs_fake = build_attrs_fake -spec_models.CteSpecMixin.match_or_create_m2o_fake = match_or_create_m2o_fake +spec_mixin.CteSpecMixin.build_fake = build_fake +spec_mixin.CteSpecMixin.build_attrs_fake = build_attrs_fake +spec_mixin.CteSpecMixin.match_or_create_m2o_fake = match_or_create_m2o_fake -class CTeImportTest(TransactionCase): +class CTeImportTest(SavepointCase): def test_import_cte(self): res_items = ( "cte", @@ -131,8 +120,12 @@ def test_import_cte(self): "43120178408960000182570010000000041000000047-cte.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(), Cte) - cte = self.env["cte.40.tcte"].build_fake(binding, create=False) - self.assertEqual(cte.cte40_infCte.cte40_emit.cte40_CNPJ, "78408960000182") + cte_stream = pkg_resources.resource_stream(nfelib.__name__, resource_path) + binding = Tcte.from_xml(cte_stream.read().decode()) + cte = ( + self.env["cte.40.tcte_infcte"] + .with_context(tracking_disable=True, edoc_type="in", lang="pt_BR") + .build_fake(binding.infCte, create=False) + ) + + self.assertEqual(cte.cte40_exped.cte40_CNPJ, "78408960000182") diff --git a/l10n_br_fiscal/models/document.py b/l10n_br_fiscal/models/document.py index dfe625f30ca3..dfe3af9c1755 100644 --- a/l10n_br_fiscal/models/document.py +++ b/l10n_br_fiscal/models/document.py @@ -198,6 +198,29 @@ class Document(models.Model): default=False, ) + transport_modal = fields.Selection( + selection=[ + ("01", "Rodoviário"), + ("02", "Aéreo"), + ("03", "Aquaviário"), + ("04", "Ferroviário"), + ("05", "Dutoviário"), + ("06", "Multimodal"), + ], + string="Modal de Transporte", + ) + + service_provider = fields.Selection( + selection=[ + ("0", "Remetente"), + ("1", "Expedidor"), + ("2", "Recebedor"), + ("3", "Destinatário"), + ("4", "Outros"), + ], + string="Tomador do Serviço", + ) + @api.constrains("document_key") def _check_key(self): for record in self: diff --git a/l10n_br_fiscal/views/document_view.xml b/l10n_br_fiscal/views/document_view.xml index 48a91d4597eb..00b7a16dd2f6 100644 --- a/l10n_br_fiscal/views/document_view.xml +++ b/l10n_br_fiscal/views/document_view.xml @@ -285,6 +285,10 @@ + + + + diff --git a/setup/l10n_br_cte/odoo/addons/l10n_br_cte b/setup/l10n_br_cte/odoo/addons/l10n_br_cte new file mode 120000 index 000000000000..a76696a2ca0b --- /dev/null +++ b/setup/l10n_br_cte/odoo/addons/l10n_br_cte @@ -0,0 +1 @@ +../../../../l10n_br_cte \ No newline at end of file diff --git a/setup/l10n_br_cte/setup.py b/setup/l10n_br_cte/setup.py new file mode 100644 index 000000000000..28c57bb64031 --- /dev/null +++ b/setup/l10n_br_cte/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)