diff --git a/l10n_br_fiscal/__manifest__.py b/l10n_br_fiscal/__manifest__.py index da5414a4708a..88bf5bf67709 100644 --- a/l10n_br_fiscal/__manifest__.py +++ b/l10n_br_fiscal/__manifest__.py @@ -10,7 +10,7 @@ "maintainers": ["renatonlima"], "website": "https://github.com/OCA/l10n-brazil", "development_status": "Production/Stable", - "version": "14.0.23.6.0", + "version": "14.0.23.7.0", "depends": [ "product", "l10n_br_base", diff --git a/l10n_br_fiscal/constants/fiscal.py b/l10n_br_fiscal/constants/fiscal.py index fcd0cd468d59..18ebbd788c73 100644 --- a/l10n_br_fiscal/constants/fiscal.py +++ b/l10n_br_fiscal/constants/fiscal.py @@ -333,10 +333,12 @@ MODELO_FISCAL_CFE = "59" MODELO_FISCAL_CUPOM_FISCAL_ECF = "2D" MODELO_FISCAL_CTE = "57" +MODELO_FISCAL_MDFE = "58" MODELO_FISCAL_RL = "04" # Produto Rural MODELO_FISCAL_01 = "01" MODELO_FISCAL_04 = "04" + MODELO_FISCAL_EMISSAO_PRODUTO = [ MODELO_FISCAL_NFE, MODELO_FISCAL_NFCE, diff --git a/l10n_br_fiscal/data/l10n_br_fiscal.document.type.csv b/l10n_br_fiscal/data/l10n_br_fiscal.document.type.csv index 97630d38ce40..5f4971714d12 100644 --- a/l10n_br_fiscal/data/l10n_br_fiscal.document.type.csv +++ b/l10n_br_fiscal/data/l10n_br_fiscal.document.type.csv @@ -31,6 +31,7 @@ "document_30","30","Bilhete/Recibo do Passageiro","False",,,"icms" "document_55","55","Nota Fiscal Eletrônica","True","NFe","nf-e","icms" "document_57","57","Conhecimento de Transporte Eletrônico – CT-e","True","CTe","ct-e","icms" +"document_58","58","Manifesto Eletrônico de Documentos Fiscais","True","MDFe","mdf-e","icms" "document_59","59","Cupom Fiscal Eletrônico - CF-e","True","CFe","cf-e","icms" "document_60","60","Cupom Fiscal Eletrônico CF-e-ECF","True",,,"icms" "document_65","65","Nota Fiscal Eletrônica ao Consumidor Final – NFC-e","True","NFe","nfc-e","icms" diff --git a/l10n_br_fiscal/data/operation_data.xml b/l10n_br_fiscal/data/operation_data.xml index a9927efa46bb..9834da0c743c 100644 --- a/l10n_br_fiscal/data/operation_data.xml +++ b/l10n_br_fiscal/data/operation_data.xml @@ -460,6 +460,24 @@ approved + + Manifesto + Manifesto + out + other + sale_price + approved + + + + + Manifesto + 1 + False + + approved + + diff --git a/l10n_br_fiscal/demo/company_demo.xml b/l10n_br_fiscal/demo/company_demo.xml index 407e1cdc7053..224d742422c1 100644 --- a/l10n_br_fiscal/demo/company_demo.xml +++ b/l10n_br_fiscal/demo/company_demo.xml @@ -162,6 +162,14 @@ True + + 1 + Série 1 + + + True + + 1 Série 1 @@ -194,6 +202,14 @@ True + + 1 + Série 1 + + + True + + + + + + diff --git a/l10n_br_fiscal/views/document_view.xml b/l10n_br_fiscal/views/document_view.xml index 84ed8378d793..48a91d4597eb 100644 --- a/l10n_br_fiscal/views/document_view.xml +++ b/l10n_br_fiscal/views/document_view.xml @@ -401,6 +401,7 @@ + diff --git a/l10n_br_fiscal_edi/__manifest__.py b/l10n_br_fiscal_edi/__manifest__.py index b5253ebfc727..69b9959d5eb2 100644 --- a/l10n_br_fiscal_edi/__manifest__.py +++ b/l10n_br_fiscal_edi/__manifest__.py @@ -9,7 +9,7 @@ "maintainers": ["renatonlima", "rvalyi", "mileo"], "website": "https://github.com/OCA/l10n-brazil", "development_status": "Beta", - "version": "14.0.1.1.1", + "version": "14.0.1.2.0", "depends": [ "l10n_br_fiscal", ], diff --git a/l10n_br_fiscal_edi/models/document_workflow.py b/l10n_br_fiscal_edi/models/document_workflow.py index b703e227c1d1..e40d26dd50d6 100644 --- a/l10n_br_fiscal_edi/models/document_workflow.py +++ b/l10n_br_fiscal_edi/models/document_workflow.py @@ -9,6 +9,7 @@ from odoo.addons.l10n_br_fiscal.constants.fiscal import ( DOCUMENT_ISSUER_COMPANY, MODELO_FISCAL_CTE, + MODELO_FISCAL_MDFE, MODELO_FISCAL_NFCE, MODELO_FISCAL_NFE, MODELO_FISCAL_NFSE, @@ -236,6 +237,7 @@ def _generate_key(self): MODELO_FISCAL_NFE, MODELO_FISCAL_NFCE, MODELO_FISCAL_CTE, + MODELO_FISCAL_MDFE, ): date = fields.Datetime.context_timestamp(record, record.document_date) chave_edoc = ChaveEdoc( @@ -252,6 +254,9 @@ def _generate_key(self): numero_serie=record.document_serie or "", validar=False, ) + record.key_random_code = chave_edoc.codigo_aleatorio + record.key_check_digit = chave_edoc.digito_verificador + # TODO: Implementar campos no Odoo # record.key_number = chave_edoc.campos # record.key_formated = ' '.joint(chave_edoc.partes()) @@ -385,3 +390,12 @@ def _action_document_correction(self): "this fical document you are not the document issuer" ) ) + + def _document_qrcode(self): + pass + + def _edoc_processor(self): + pass + + def _validate_xml(self, xml_file): + pass diff --git a/l10n_br_mdfe/README.rst b/l10n_br_mdfe/README.rst new file mode 100644 index 000000000000..fa01e89f417b --- /dev/null +++ b/l10n_br_mdfe/README.rst @@ -0,0 +1,144 @@ +==== +MDFe +==== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:c7c356eef2c69c2d36d3dfea44bca958c3353c323f95319f8ec7f3e3022b7098 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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_mdfe + :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_mdfe + :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| + +Este módulo permite a emissão de MDF-e. + +Mais especificamente ele: + * mapea os campos de MDF-e do módulo ``l10n_br_mdfe_spec`` com os campos Odoo. + * usa a logica do módulo ``spec_driven_model`` para realizar esse mapeamento de forma dinâmica, em especial ele usa o sistema de modelos com várias camadas, ou ``StackedModel``, com os modelos ``l10n_br_fiscal.document`` e ``l10n_br_fiscal.document.related`` que tem varios niveis hierarquicos de elementos XML que estão sendo denormalizados dentro desses modelos Odoo  + * tem wizards para implementar a comunicação SOAP de MDF-e com a SEFAZ (Autorização, Cancelamento, Encerramento...) + +.. 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 +============= + +To configure this module you need to set a digital certificate on the company, and also set the company edoc processor. + +Usage +===== + +Para utilizar o módulo `l10n_br_mdfe` em conjunto com o módulo `l10n_br_account`, é necessário configurar uma linha de operação fiscal que não adicione valor ao montante do documento, uma vez que o MDF-e (Manifesto Eletrônico de Documentos Fiscais) não possui valor financeiro. + +**Passo a Passo:** + +1. **Criar uma Fatura:** + - Defina o tipo de documento como **58 (MDFe)**. + +2. **Configurar o Parceiro da Fatura:** + - Configure o parceiro para ser o mesmo da empresa emissora do MDF-e. + +3. **Adicionar uma Linha na Aba Produtos:** + - Adicione uma linha de fatura com a operação fiscal previamente configurada. + - **Não recomedamos que informe um produto** ou utilize um produto que **não possua CFOP** (Código Fiscal de Operações e Prestações), ou que o CFOP esteja configurado para **não gerar valor financeiro** e esteja atento a dados como impostos e afins. + +4. **Acesse os detalhes fiscais da fatura e informe os demais dados necessário para emissão do MDF-e:** + - Preencha os campos obrigatórios para emissão do MDF-e, como UF de descarregamento, município de descarregamento, etc. + +5. **Valide o MDF-e, verifique os dados do XML e envie para a SEFAZ:** + - Após preencher todos os dados necessários, valide o MDF-e e envie para a SEFAZ. + +**Considerações Adicionais** + +- **Operação Fiscal:** Certifique-se de que a operação fiscal esteja parametrizada corretamente para evitar a adição de valores financeiros ao documento. +- **CFOP:** No caso de utilização de um produto cadastrado e que carregue o CFOP para a linha da fatura, verifique a configuração do CFOP para garantir que ele não gere impacto financeiro no montante da fatura. + +Seguindo esses passos, o módulo `l10n_br_mdfe` funcionará corretamente em conjunto com o `l10n_br_account`, permitindo a emissão de MDF-e sem valores financeiros associados. + +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 +* Escodoo + +Contributors +~~~~~~~~~~~~ + +* `KMEE `_: + + * Felipe Zago Rodrigues + * Ygor Carvalho + +* `ESCODOO `_: + + * Marcel Savegnago + +* `AKRETION `_: + + * Raphaël Valyi + +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. + +.. |maintainer-mileo| image:: https://github.com/mileo.png?size=40px + :target: https://github.com/mileo + :alt: mileo +.. |maintainer-marcelsavegnago| image:: https://github.com/marcelsavegnago.png?size=40px + :target: https://github.com/marcelsavegnago + :alt: marcelsavegnago + +Current `maintainers `__: + +|maintainer-mileo| |maintainer-marcelsavegnago| + +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_mdfe/__init__.py b/l10n_br_mdfe/__init__.py new file mode 100644 index 000000000000..cc6b6354ad8f --- /dev/null +++ b/l10n_br_mdfe/__init__.py @@ -0,0 +1,2 @@ +from . import models +from .hooks import post_init_hook diff --git a/l10n_br_mdfe/__manifest__.py b/l10n_br_mdfe/__manifest__.py new file mode 100644 index 000000000000..c0d759ae1d6b --- /dev/null +++ b/l10n_br_mdfe/__manifest__.py @@ -0,0 +1,48 @@ +# Copyright 2023 KMEE +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "MDFe", + "summary": """Brazilian Eletronic Invoice MDF-e""", + "version": "14.0.1.0.0", + "category": "Localisation", + "license": "AGPL-3", + "author": "KMEE,Escodoo,Odoo Community Association (OCA)", + "maintainers": ["mileo", "marcelsavegnago"], + "website": "https://github.com/OCA/l10n-brazil", + "development_status": "Alpha", + "depends": [ + "l10n_br_fiscal_edi", + "l10n_br_fiscal_certificate", + "l10n_br_mdfe_spec", + "spec_driven_model", + ], + "data": [ + "security/ir.model.access.csv", + "data/ir_config_parameter.xml", + "views/document.xml", + "views/mdfe_action.xml", + "views/mdfe_menu.xml", + "views/res_company.xml", + "views/transporte.xml", + "views/res_partner.xml", + "views/product_product.xml", + "views/modal/modal_aquaviario.xml", + "views/modal/modal_rodoviario.xml", + "views/modal/modal_ferroviario.xml", + ], + "demo": [ + "demo/fiscal_document_demo.xml", + "demo/company_demo.xml", + ], + "post_init_hook": "post_init_hook", + "installable": True, + "auto_install": False, + "external_dependencies": { + "python": [ + "nfelib<=2.0.7", + "erpbrasil.transmissao>=1.1.0", + "erpbrasil.edoc>=2.5.2", + ] + }, +} diff --git a/l10n_br_mdfe/constants/mdfe.py b/l10n_br_mdfe/constants/mdfe.py new file mode 100644 index 000000000000..8fff1df401bf --- /dev/null +++ b/l10n_br_mdfe/constants/mdfe.py @@ -0,0 +1,38 @@ +# Copyright (C) 2023 Felipe Zago - KMEE +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +MDFE_VERSIONS = [("3.00", "3.00")] + +MDFE_VERSION_DEFAULT = "3.00" + +MDFE_ENVIRONMENTS = [("1", "Produção"), ("2", "Homologação")] + +MDFE_ENVIRONMENT_DEFAULT = "2" + +MDFE_EMIT_TYPES = [ + ("1", "1 - Prestador de serviço de transporte"), + ("2", "2 - Transportador de Carga Própria"), + ("3", "3 - Prestador de serviço de transporte que emitirá CT-e Globalizado"), +] + +MDFE_EMIT_TYPE_DEFAULT = "2" + +MDFE_TRANSP_TYPE = [ + ("1", "Empresa de Transporte de Cargas – ETC"), + ("2", "Transportador Autônomo de Cargas – TAC"), + ("3", "Cooperativa de Transporte de Cargas – CTC"), +] + +MDFE_TRANSP_TYPE_DEFAULT = "1" + +MDFE_TRANSMISSIONS = [ + ("1", "Emissão Normal"), + ("2", "Contingência Off-Line"), + ("3", "Regime Especial NFF"), +] + +MDFE_TRANSMISSION_DEFAULT = "1" + +MDFE_EMISSION_PROCESSES = [("0", "Emissão de MDFe com aplicativo do contribuinte")] + +MDFE_EMISSION_PROCESS_DEFAULT = "0" diff --git a/l10n_br_mdfe/constants/modal.py b/l10n_br_mdfe/constants/modal.py new file mode 100644 index 000000000000..28d3baa4ded7 --- /dev/null +++ b/l10n_br_mdfe/constants/modal.py @@ -0,0 +1,353 @@ +MDFE_MODALS = [ + ("1", "Rodoviário"), + ("2", "Aéreo"), + ("3", "Aquaviário"), + ("4", "Ferroviário"), +] + +MDFE_MODAL_DEFAULT = "1" + +MDFE_MODAL_VERSION_DEFAULT = "3.00" + +MDFE_MODAL_DEFAULT_AIRCRAFT = "OACI" + +MDFE_MODAL_SHIP_TYPES = [ + ("01", "ALVARENGA"), + ("02", "BARCAÇA"), + ("03", "BARCAÇA PROPULSADA"), + ("04", "CARGUEIRO"), + ("05", "CATAMARÃ MISTO"), + ("06", "CATAMARÃ PASSAGEIROS"), + ("07", "CHATA CARGUEIRA"), + ("08", "CHATA GRANELEIRA"), + ("09", "CHATA MISTA"), + ("10", "CHATA TANQUE"), + ("11", "EMPURRADOR"), + ("12", "ESTIMULAÇÃO"), + ("13", "FERRY BOAT"), + ("14", "FLOTEL"), + ("15", "FLUTUANTE"), + ("16", "CABREA/GUINDASTE"), + ("17", "LANCHA MISTA"), + ("18", "LANCHA PASSAGEIROS"), + ("19", "LANCHA PRÁTICO"), + ("20", "MANUSEIO DE ESPIAS"), + ("21", "PESQUISA"), + ("22", "REBOCADOR"), + ("23", "SUPRIMENTO"), + ("24", "TRANSBORDADOR"), + ("25", "CARGA PESADA"), + ("26", "FRIGORÍFICO"), + ("27", "GLP"), + ("28", "GRANELEIRO"), + ("29", "MINERO-PETROLEIRO"), + ("30", "MULTI-PROPOSITO"), + ("31", "NAVIO CISTERNA"), + ("32", "PASSAGEIROS"), + ("33", "PETROLEIRO"), + ("34", "PORTA CONTEINER"), + ("35", "TANQUE QUIMICO"), + ("36", "RO-RO"), + ("37", "DRAGA"), + ("38", "OUTROS"), + ("39", "BALSA"), + ("40", "DSV(APOIO E MERGULHO)"), + ("41", "SWATH"), + ("42", "ROV"), + ("43", "AHTS(REBOQUE E MANUSEIO DE ÂNCORAS)"), + ("44", "UT 4.000"), + ("45", "UT 750"), + ("46", "RSV"), + ("47", "TANQUE GLP"), + ("48", "PATROL VESSEL"), + ("50", "TANQUE GNL"), + ("51", "LANÇAMENTO DE LINHAS"), + ("52", "BARCO A MOTOR"), + ("53", "BALSA MOTORIZADA"), + ("54", "MPSV"), + ("55", "PESQUEIRO"), + ("56", "PSV"), +] + +MDFE_MODAL_HARBORS = [ + ("BRADR", "ANGRA DOS REIS"), + ("BRAFU", "AFUÁ"), + ("BRAJU", "PORTO BARRA DOS COQUEIROS - ARACAJU - SE"), + ("BRALT", "ALENQUER"), + ("BRAMM", "ALMEIRIM"), + ("BRAMW", "ALUMAR"), + ("BRANT", "ANTONINA"), + ("BRARB", "ARATU"), + ("BRARE", "AREIA BRANCA (TERMISA)"), + ("BRATB", "ABAETETUBA"), + ("BRATM", "ALTAMIRA"), + ("BRBAR", "BARRA DOS COQUEIROS"), + ("BRBEL", "BELEM"), + ("BRBPS", "PORTO SEGURO"), + ("BRBVB", "BOA VISTA"), + ("BRBVE", "BREVES"), + ("BRBVM", "BELO MONTE"), + ("BRBZC", "BUZIOS"), + ("BRCAF", "CARAUARI"), + ("BRCAM", "BACIA PETROLÍFERA DE CAMPOS"), + ("BRCAW", "CAMPOS"), + ("BRCCX", "CÁCERES"), + ("BRCDA", "CODAJÁS"), + ("BRCDA", "CODAJÁS"), + ("BRCDO", "CABEDELO"), + ("BRCIZ", "COARI"), + ("BRCMG", "CORUMBÁ/LADÁRIO"), + ("BRCNV", "CANAVIEIRA"), + ("BRCQD", "CHARQUEADAS"), + ("BRCRQ", "CARAVELAS"), + ("BRCZS", "CRUZEIRO DO SUL - AC"), + ("BRERN", "EIRUNEPÉ"), + ("BRESP", "BACIA PETROLÍFERO DO ESPIRITO SANTO"), + ("BRETA", "ESTRELA"), + ("BRFEJ", "FEIJÓ"), + ("BRFEN", "SANTO ANTONIO - FERNANDO DE NORONHA"), + ("BRFLN", "FLORIANOPOLIS"), + ("BRFNO", "FORNO (ARRAIAL DO CABO)"), + ("BRFOA", "FONTE BOA"), + ("BRFOR", "FORTALEZA (MUCURIPE)"), + ("BRFOT", "BACIA PETROLÍFERA DE FORTALEZA"), + ("BRGIB", "GUAÍBA -RS"), + ("BRGJM", "GUAJARÁ-MIRIM (RO)"), + ("BRHMA", "HUMAITÁ"), + ("BRIBB", "IMBITUBA"), + ("PORTO", "DE EMBARQUE / DESTINO"), + ("BRIBE", "ILHABELA"), + ("BRIGI", "ITAGUAI ( EX SEPETIBA)"), + ("BRIGU", "FOZ DO IGUAÇU"), + ("BRIMM", "ITAMARATI"), + ("BRIOA", "ITAPOA"), + ("BRIOS", "ILHEUS"), + ("BRIPG", "IPIRANGA"), + ("BRIQI", "ITAQUI"), + ("BRITA", "ITACOATIARA"), + ("BRITB", "ITAITUBA"), + ("BRITJ", "ITAJAI"), + ("BRLBR", "LÁBREA"), + ("BRLDR", "LADÁRIO - MS"), + ("BRLIN", "LINDÓIA"), + ("BRLJI", "LARANJAL DO JARI"), + ("BRMAO", "MANAUS"), + ("BRMBZ", "MAUÉS"), + ("BRMCP", "SANTANA/MACAPÁ"), + ("BRMCU", "MACAU"), + ("BRMCZ", "MACEIÓ"), + ("BRMEA", "MACAÉ"), + ("BRMEA", "MACAÉ"), + ("BRMGU", "MUNGUBA"), + ("BRMHO", "SÃO LUÍS (MARANHÃO)"), + ("BRMNX", "MANICORÉ"), + ("BRMPR", "MANACAPURU"), + ("BRMRS", "MORRETES"), + ("BRMTE", "MONTE ALEGRE"), + ("BRNAT", "NATAL"), + ("BRNTR", "NITERÓI"), + ("BRNVP", "NOVO ARIPUANA"), + ("BRNVT", "NAVEGANTES"), + ("BROBI", "ÓBIDOS"), + ("BRORX", "ORIXIMINÁ"), + ("BROUT", "OUTEIRO"), + ("BRPAT", "PARATY"), + ("BRPBO", "PORTO BELO"), + ("BRPBX", "PORTO ALEGRE - PA"), + ("BRPEC", "PECEM"), + ("BRPEO", "PRESIDENTE EPITÁCIO-SP"), + ("BRPET", "PELOTAS"), + ("BRPIN", "PARINTINS"), + ("BRPIN", "PARINTINS"), + ("BRPJZ", "SANTANA"), + ("BRPKC", "PORTOCEL"), + ("BRPMA", "PONTA DA MADEIRA"), + ("BRPMH", "PORTO MURTINHO"), + ("BRPMR", "PALMEIRAS"), + ("BRPNA", "PANORAMA"), + ("BRPNG", "PARANAGUA"), + ("BRPOA", "PORTO ALEGRE - RS"), + ("BRPOU", "PONTA DO UBU"), + ("BRPPB", "PRESIDENTE PRUDENTE"), + ("BRPRM", "PRAIA MOLE"), + ("BRPTQ", "PORTO DE MOZ"), + ("BRPVH", "PORTO VELHO - RO"), + ("BRQAV", "BENJAMIN CONSTANT"), + ("BRQCK", "CABO FRIO"), + ("BRQNS", "CANOAS"), + ("BRRBB", "BORBA"), + ("BRRBR", "RIO BRANCO (RO)"), + ("BRRCH", "BARRA DO RIACHO - PORTOCEL"), + ("BRREC", "RECIFE"), + ("BRREL", "TERMINAL DE REGÊNCIA"), + ("BRRIA", "SANTA MARIA"), + ("BRRIG", "RIO GRANDE"), + ("BRRIO", "RIO DE JANEIRO"), + ("BRSAS", "BACIA PETROLIFERA DE SANTOS"), + ("BRSBE", "SAO BENEDITO"), + ("BRSFK", "SOURE"), + ("BRSFK", "SOURE"), + ("BRSFS", "SAO FRANCISCO DO SUL"), + ("BRSIV", "SILVES"), + ("BRSJL", "SAO GABRIEL DA CACHOEIRA"), + ("BRSLV", "SALVA TERRA"), + ("BRSNH", "SANTA HELENA"), + ("BRSOS", "SÃO SIMÃO-GO"), + ("BRSSA", "SALVADOR"), + ("BRSSO", "SÃO SEBASTIAO"), + ("BRSSZ", "SANTOS"), + ("BRSTM", "SANTAREM"), + ("BRSUA", "SUAPE"), + ("BRSWK", "SÃO PAULO DO OLIVENC"), + ("BRSYC", "SANTA CLARA"), + ("BRTAP", "TAPES"), + ("BRTBE", "SANTO ANTONIO DO ICA"), + ("BRTBT", "TABATINGA"), + ("BRTBT", "TABATINGA"), + ("BRTFF", "TEFÉ"), + ("BRTFO", "TRIUNFO"), + ("BRTHE", "TERESINA"), + ("BRTLG", "TRÊS LAGOAS"), + ("BRTMT", "TROMBETAS"), + ("BRTRM", "TRAMANDAÍ"), + ("BRTRQ", "TARAUACA"), + ("BRTUB", "PONTA DO TUBARÃO"), + ("BRTUR", "TUCURUÍ"), + ("BRUBT", "UBATUBA"), + ("BRURT", "URUCURITUBA"), + ("BRVDC", "VILA DO CONDE"), + ("BRVIX", "VITÓRIA"), + ("BRVVE", "CAPUABA"), + ("BRXX1", "PONTO DE ABASTECIMENTO - BANKER NAVIOS"), + ("BRZMD", "SENA MADUREIRA AC"), + ("BRZZZ", "MADRE DE DEUS"), + ("BR006", "MANOEL URBANO"), + ("BR007", "ALVARÃES"), + ("BR008", "AMATURA"), + ("BR009", "ANAJÁS"), + ("BR010", "ANAMÃ"), + ("BR011", "ANORÍ"), + ("BR012", "ATALAIA DO NORTE"), + ("BR013", "AUTAZES"), + ("BR014", "BAGRE"), + ("BR015", "BARCARENA"), + ("BR016", "BARCELOS"), + ("BR017", "BARREIRINHA"), + ("BR021", "BOCA DO ACRE"), + ("BR025", "BREVES"), + ("BR026", "CAAPIRANGA"), + ("BR027", "CACHOEIRA DO SUL"), + ("BR028", "CANUTAMA"), + ("BR030", "CAREIRO"), + ("BR031", "CHAVES"), + ("BR032", "GUAÍRA"), + ("BR035", "CUCUÍ"), + ("BR036", "CURRALINHO"), + ("BR038", "ENVIRA"), + ("BR040", "BOCA DO PURUS"), + ("BR042", "GURUPÁ"), + ("BR050", "JAPURÁ"), + ("BR051", "JARI"), + ("BR052", "JURUTI"), + ("BR053", "JURUÁ"), + ("BR054", "JUTAI"), + ("BR057", "MANACAPURU"), + ("BR058", "MANAQUIRI"), + ("BR060", "MARAÃ"), + ("BR064", "FONTE BOA"), + ("BR065", "MUANÁ"), + ("BR067", "NHAMUNDA"), + ("BR068", "NOVA OLINDA DO NORTE"), + ("BR069", "NOVO AIRÃO"), + ("BR071", "OEIRAS DO PARA"), + ("BR074", "PAUINÍ"), + ("BR075", "PORTEL"), + ("BR077", "PRAINHA"), + ("BR079", "SANTA CRUZ DO ARARI"), + ("BR081", "SANTA ISABEL DO RIO NEGRO"), + ("BR083", "SANTO ANTONIO DO IÇÁ"), + ("BR088", "SAO SEBASTIA0 DA BOA VISTA"), + ("BR095", "TAPAUÁ"), + ("BR097", "BAIÃO"), + ("BR098", "TERRA SANTA"), + ("BR099", "TONANTINS"), + ("BR101", "URUCARÁ"), + ("BR103", "VITORIA DO PARÁ"), + ("BR108", "CARNAMBEIRA"), + ("BR131", "SEPETIBA (ILHA DE JAGUANUM)"), + ("BR134", "SÃO FRANCISCO"), + ("BR147", "SANTA CLARA (TERMINAL)"), + ("BR149", "TAQUARI"), + ("BR151", "CÁCERES - MT"), + ("BR170", "CACHOEIRA DO ARARI"), + ("BR174", "CORUMBÁ"), + ("BR184", "PORTO MURTINHO - MS"), + ("BR185", "PORTO SAO FRANCISCO"), + ("BR192", "BERURI"), + ("BR193", "BOA VISTA DO RAMOS"), + ("BR195", "MARECHAL TAUMATURGO"), + ("BR196", "IAURETÊ"), + ("BR197", "IPIXUNA"), + ("BR202", "PONTA DE PEDRA"), + ("BR205", "PORTO MIGUEL DE OLIVEIRA"), + ("BR221", "CAREIRO DA VÁRZEA"), + ("BR224", "SANTA ROSA DO PURUS"), + ("BR225", "BELÉM DO SOLIMÕES"), + ("BR227", "GUAJARÁ"), + ("BR229", "SAO SEBATIAO UATUMA"), + ("BR235", "TERMINAL NORTE CAPIXABA"), + ("BR236", "APUI"), + ("BR238", "SANTA CRUZ DO SUL"), + ("BR239", "FPSO PIRANEMA"), + ("BR240", "SANTA RITA DO PURUS - AC"), + ("BR243", "JAGUARIAIVA"), + ("BR245", "URUCURITUBA"), + ("BR246", "FPSO CIDADE DE MACAÉ"), + ("BR252", "TERGUÁ (TERMINAL DE GUAMARÉ)"), + ("BR254", "LAGOA PARDA"), + ("BR260", "MATARIPE"), + ("BR267", "POÇO DE CARAVELA NORTE"), + ("BR280", "TURIAÇU"), + ("BR298", "CARACARAÍ (RR)"), + ("BR300", "TERMINAL AQUAVIARIO BARRA DO RIACHO-TRANSPETRO"), + ("BR301", "TERMINAL STA. MARIA DA SERRA"), + ("BR302", "NOVA SANTA RITA"), + ("BR306", "AMAPÁ"), + ("BR321", "CARAPEBA"), + ("BR339", "ALIANÇA"), + ("BR342", "SENADOR JOSE PORFÍRIO"), + ("BR371", "BRASILÉIA - AC"), + ("BR376", "PORTO ACRE - AC"), + ("BR386", "PORTO IRANDUBA"), + ("BR400", "PORTO DO AÇU"), + ("BR409", "MOURA"), + ("BR410", "UARINI"), + ("BR414", "FARO"), + ("BR444", "MELGAÇO"), + ("BR454", "ALTER DO CHÃO"), + ("BR456", "SAO SEBASTIÃO"), + ("BR462", "CAVIANA"), + ("BR500", "ESTIRAO DO EQUADOR"), + ("BR551", "SBM 04"), + ("BR553", "JUMA"), + ("BR602", "NITERÓI"), + ("BR696", "GREGÓRIO CURVO - MS"), + ("BR719", "MIRAMAR"), + ("BR723", "NATAL"), + ("BR728", "NOVO REMANSO"), + ("BR756", "FPSO P-54"), + ("BR768", "RIO LAMBARI - MG"), + ("BR778", "SÃO PAULO DE OLIVENÇA"), + ("BR793", "SANTANA"), + ("BR801", "CURUNA UNA"), + ("BR802", "SAO SEBASTIAO"), + ("BR821", "VILA DO CARMO"), + ("BR855", "JACARÉ GRANDE"), + ("BR900", "BOCA DA VALERIA"), + ("BR907", "URUCU"), + ("BR930", "TERMINAL MANAUS DA ENERGIA"), + ("BR938", "PORTO WALTER"), + ("BR940", "VITORIA DO XINGU"), + ("BR985", "PORTO DO RIO IGUACU TERMINAL E COMERCIO LTDA"), + ("BR997", "TERMINAL BELM"), +] diff --git a/l10n_br_mdfe/data/ir_config_parameter.xml b/l10n_br_mdfe/data/ir_config_parameter.xml new file mode 100644 index 000000000000..28a75474e0da --- /dev/null +++ b/l10n_br_mdfe/data/ir_config_parameter.xml @@ -0,0 +1,9 @@ + + + + + l10n_br_mdfe.version.name + Odoo Brasil OCA v14 + + + diff --git a/l10n_br_mdfe/demo/company_demo.xml b/l10n_br_mdfe/demo/company_demo.xml new file mode 100644 index 000000000000..e6600faab41a --- /dev/null +++ b/l10n_br_mdfe/demo/company_demo.xml @@ -0,0 +1,14 @@ + + + + + + oca + + + + + oca + + + diff --git a/l10n_br_mdfe/demo/fiscal_document_demo.xml b/l10n_br_mdfe/demo/fiscal_document_demo.xml new file mode 100644 index 000000000000..cc5328f4f269 --- /dev/null +++ b/l10n_br_mdfe/demo/fiscal_document_demo.xml @@ -0,0 +1,168 @@ + + + + + + 41190806117473000150550010000586251016759484 + 10 + 33.19 + + + + + + + + + 2 + 1 + 35230905472475000102580200000602011208018449 + 2 + oca + + out + + + + + 4 + + + + + + + nfe + + + + + + + + + + 3 + 1 + 35230905472475000102580200000602071611554500 + 2 + oca + + out + + + + + 1 + + + + + + + nfe + + + + + + + + + + 4 + 1 + 35230905472475000102580200000602081550195716 + 2 + oca + + out + + + + + 2 + + + + + + + nfe + + + + + + + + + + 5 + 1 + 35231005472475000102580200000602161434590525 + 2 + oca + + out + + + + + 3 + + + + + + + nfe + + + + diff --git a/l10n_br_mdfe/hooks.py b/l10n_br_mdfe/hooks.py new file mode 100644 index 000000000000..94b946545f2e --- /dev/null +++ b/l10n_br_mdfe/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.mdfe.bindings.v3_0.mdfe_tipos_basico_v3_00 import Tmdfe + +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_mdfe';") + is_demo = cr.fetchone()[0] + if is_demo: + res_items = ( + "mdfe", + "samples", + "v3_0", + "41190876676436000167580010000500001000437558-mdfe.xml", + ) + resource_path = "/".join(res_items) + doc_stream = pkg_resources.resource_stream(nfelib.__name__, resource_path) + binding = Tmdfe.from_xml(doc_stream.read().decode()) + document_number = binding.infMDFe.ide.nMDF + existing_docs = env["l10n_br_fiscal.document"].search( + [("document_number", "=", document_number)] + ) + try: + existing_docs.unlink() + doc = ( + env["mdfe.30.tmdfe_infmdfe"] + .with_context(tracking_disable=True, edoc_type="in") + .build_from_binding("mdfe", "30", binding.infMDFe) + ) + _logger.info(doc.mdfe30_emit.mdfe30_CNPJ) + except ValidationError: + _logger.info(f"MDF-e already {document_number} imported by hooks") diff --git a/l10n_br_mdfe/models/__init__.py b/l10n_br_mdfe/models/__init__.py new file mode 100644 index 000000000000..99fa527c58ad --- /dev/null +++ b/l10n_br_mdfe/models/__init__.py @@ -0,0 +1,18 @@ +from . import transporte +from . import res_company +from . import document +from . import document_related +from . import res_partner +from . import res_config_settings +from . import modal_aereo +from . import modal_aquaviario +from . import modal_ferroviario +from . import modal_rodoviario +from . import document_info +from . import seguro_carga +from . import product_product +from . import document_supplement +from . import document_type + +spec_schema = "mdfe" +spec_version = "30" diff --git a/l10n_br_mdfe/models/document.py b/l10n_br_mdfe/models/document.py new file mode 100644 index 000000000000..ac50dbc12516 --- /dev/null +++ b/l10n_br_mdfe/models/document.py @@ -0,0 +1,914 @@ +# Copyright 2023 KMEE +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import re +import string +from enum import Enum +from unicodedata import normalize + +from erpbrasil.base.fiscal.edoc import ChaveEdoc +from erpbrasil.transmissao import TransmissaoSOAP +from nfelib.mdfe.bindings.v3_0.mdfe_v3_00 import Mdfe +from nfelib.nfe.ws.edoc_legacy import MDFeAdapter as edoc_mdfe +from requests import Session + +from odoo import api, fields + +from odoo.addons.l10n_br_fiscal.constants.fiscal import ( + EVENT_ENV_HML, + EVENT_ENV_PROD, + MODELO_FISCAL_MDFE, + PROCESSADOR_OCA, +) +from odoo.addons.l10n_br_mdfe_spec.models.v3_0.mdfe_modal_aquaviario_v3_00 import ( + AQUAV_TPNAV, +) +from odoo.addons.l10n_br_mdfe_spec.models.v3_0.mdfe_modal_rodoviario_v3_00 import ( + TUF, + VALEPED_CATEGCOMBVEIC, + VEICTRACAO_TPCAR, + VEICTRACAO_TPROD, +) +from odoo.addons.spec_driven_model.models import spec_models + +from ..constants.mdfe import ( + MDFE_EMISSION_PROCESS_DEFAULT, + MDFE_EMISSION_PROCESSES, + MDFE_EMIT_TYPES, + MDFE_ENVIRONMENTS, + MDFE_TRANSMISSIONS, + MDFE_TRANSP_TYPE, +) +from ..constants.modal import ( + MDFE_MODAL_DEFAULT, + MDFE_MODAL_DEFAULT_AIRCRAFT, + MDFE_MODAL_HARBORS, + MDFE_MODAL_SHIP_TYPES, + MDFE_MODAL_VERSION_DEFAULT, + MDFE_MODALS, +) + + +def filtered_processador_edoc_mdfe(record): + return ( + record.processador_edoc == PROCESSADOR_OCA + and record.document_type_id.code == MODELO_FISCAL_MDFE + ) + + +class MDFe(spec_models.StackedModel): + _name = "l10n_br_fiscal.document" + _inherit = ["l10n_br_fiscal.document", "mdfe.30.tmdfe_infmdfe"] + _mdfe30_odoo_module = ( + "odoo.addons.l10n_br_mdfe_spec.models.v3_0.mdfe_tipos_basico_v3_00" + ) + _mdfe30_stacking_mixin = "mdfe.30.tmdfe_infmdfe" + # all m2o at this level will be stacked even if not required: + _mdfe30_stacking_force_paths = [ + "infmdfe.infAdic", + "infmdfe.tot", + "infmdfe.infsolicnff", + "infmdfe.InfDoc", + ] + _mdfe_search_keys = ["mdfe30_Id"] + + # When dynamic stacking is applied the MDFe structure is: + INFMDFE_TREE = """ +> + > + ≡ + ≡ + - res.company + > + > + ≡ l10n_br_mdfe.municipio.descarga + ≡ l10n_br_mdfe.seguro.carga + - product.product + > + ≡ + ≡ res.partner + > + - res.partner + - """ + + mdfe_version = fields.Selection( + string="MDF-e Version", + related="company_id.mdfe_version", + readonly=False, + ) + + mdfe_environment = fields.Selection( + string="MDF-e Environment", + related="company_id.mdfe_environment", + readonly=False, + ) + + ########################## + # MDF-e spec related fields + ########################## + + ########################## + # MDF-e tag: infMDFe + ########################## + + mdfe30_versao = fields.Char(compute="_compute_mdfe_version") + + mdfe30_Id = fields.Char( + compute="_compute_mdfe30_id_tag", + inverse="_inverse_mdfe30_id_tag", + ) + + ########################## + # MDF-e tag: infMDFe + # Methods + ########################## + + @api.depends("mdfe_version") + def _compute_mdfe_version(self): + for record in self.filtered(filtered_processador_edoc_mdfe): + record.mdfe30_versao = record.mdfe_version + + @api.depends("document_type_id", "document_key") + def _compute_mdfe30_id_tag(self): + """Set schema data which are not just related fields""" + + for record in self.filtered(filtered_processador_edoc_mdfe): + record.mdfe30_Id = False + + if ( + record.document_type_id + and record.document_type_id.prefix + and record.document_key + ): + record.mdfe30_Id = "{}{}".format( + record.document_type_id.prefix, record.document_key + ) + + def _inverse_mdfe30_id_tag(self): + for record in self: + if record.mdfe30_Id: + record.document_key = re.findall(r"\d+", str(record.mdfe30_Id))[0] + + ########################## + # MDF-e tag: ide + ########################## + + mdfe30_cUF = fields.Selection( + compute="_compute_mdfe30_uf", inverse="_inverse_mdfe30_uf" + ) + + mdfe30_tpAmb = fields.Selection(related="mdfe_environment") + + mdfe_environment = fields.Selection( + selection=MDFE_ENVIRONMENTS, + string="Environment", + copy=False, + default=lambda self: self.env.company.mdfe_environment, + ) + + mdfe30_tpEmit = fields.Selection(related="mdfe_emit_type") + + mdfe_emit_type = fields.Selection( + selection=MDFE_EMIT_TYPES, + string="Emit Type", + copy=False, + default=lambda self: self.env.company.mdfe_emit_type, + ) + + mdfe30_tpTransp = fields.Selection(related="mdfe_transp_type") + + mdfe_transp_type = fields.Selection( + selection=MDFE_TRANSP_TYPE, + string="Transp Type", + copy=False, + default=lambda self: self.env.company.mdfe_transp_type, + ) + + mdfe30_mod = fields.Char(related="document_type_id.code") + + mdfe30_serie = fields.Char(related="document_serie") + + mdfe30_nMDF = fields.Char(related="document_number") + + mdfe30_dhEmi = fields.Datetime(related="document_date") + + mdfe30_modal = fields.Selection(related="mdfe_modal") + + mdfe_modal = fields.Selection( + selection=MDFE_MODALS, string="Transport Modal", default=MDFE_MODAL_DEFAULT + ) + + mdfe30_tpEmis = fields.Selection(related="mdfe_transmission") + + mdfe_transmission = fields.Selection( + selection=MDFE_TRANSMISSIONS, + string="Transmission", + copy=False, + default=lambda self: self.env.company.mdfe_transmission, + ) + + mdfe30_procEmi = fields.Selection( + selection=MDFE_EMISSION_PROCESSES, + string="Emission Process", + default=MDFE_EMISSION_PROCESS_DEFAULT, + ) + + mdfe30_verProc = fields.Char( + copy=False, + default=lambda s: s.env["ir.config_parameter"] + .sudo() + .get_param("l10n_br_mdfe.version.name", default="Odoo Brasil OCA v14"), + ) + + mdfe30_UFIni = fields.Selection( + compute="_compute_mdfe30_initial_final_state", + inverse="_inverse_mdfe30_initial_final_state", + ) + + mdfe30_UFFim = fields.Selection( + compute="_compute_mdfe30_initial_final_state", + inverse="_inverse_mdfe30_initial_final_state", + ) + + mdfe_initial_state_id = fields.Many2one( + comodel_name="res.country.state", + string="Initial State", + domain=[("country_id.code", "=", "BR")], + ) + + mdfe_final_state_id = fields.Many2one( + comodel_name="res.country.state", + string="Final State", + domain=[("country_id.code", "=", "BR")], + ) + + mdfe30_cMDF = fields.Char(related="key_random_code", string="Código Numérico MDFe") + + mdfe30_cDV = fields.Char(related="key_check_digit") + + mdfe30_infMunCarrega = fields.One2many( + compute="_compute_mdfe30_inf_carrega", + inverse="_inverse_mdfe30_inf_carrega", + string="Informações dos Municipios de Carregamento", + ) + + mdfe_loading_city_ids = fields.Many2many( + comodel_name="res.city", string="Loading Cities" + ) + + mdfe30_infPercurso = fields.One2many(compute="_compute_mdfe30_inf_percurso") + + mdfe_route_state_ids = fields.Many2many( + comodel_name="res.country.state", + string="Route States", + domain=[("country_id.code", "=", "BR")], + ) + + ########################## + # MDF-e tag: ide + # Methods + ########################## + + @api.depends("company_id") + def _compute_mdfe30_uf(self): + for record in self.filtered(filtered_processador_edoc_mdfe): + record.mdfe30_cUF = record.company_id.partner_id.state_id.ibge_code + + @api.depends("mdfe_initial_state_id", "mdfe_final_state_id") + def _compute_mdfe30_initial_final_state(self): + for record in self.filtered(filtered_processador_edoc_mdfe): + record.mdfe30_UFIni = record.mdfe_initial_state_id.code + record.mdfe30_UFFim = record.mdfe_final_state_id.code + + @api.depends("mdfe_loading_city_ids") + def _compute_mdfe30_inf_carrega(self): + for record in self.filtered(filtered_processador_edoc_mdfe): + record.mdfe30_infMunCarrega = [(5, 0, 0)] + record.mdfe30_infMunCarrega = [ + ( + 0, + 0, + { + "mdfe30_cMunCarrega": city.ibge_code, + "mdfe30_xMunCarrega": city.name, + }, + ) + for city in record.mdfe_loading_city_ids + ] + + def _inverse_mdfe30_inf_carrega(self): + for record in self: + city_ids = self.env["res.city"].search( + [("ibge_code", "=", record.mdfe30_infMunCarrega.mdfe30_cMunCarrega)] + ) + if city_ids: + record.mdfe_loading_city_ids = [(6, 0, city_ids.ids)] + + def _inverse_mdfe30_initial_final_state(self): + for record in self: + initial_state_id = self.env["res.country.state"].search( + [("code", "=", record.mdfe30_UFIni)], limit=1 + ) + final_state_id = self.env["res.country.state"].search( + [("code", "=", record.mdfe30_UFFim)], limit=1 + ) + + if initial_state_id: + record.mdfe_initial_state_id = initial_state_id + + if final_state_id: + record.mdfe_final_state_id = final_state_id + + def _inverse_mdfe30_uf(self): + for record in self: + state_id = self.env["res.country.state"].search( + [("code", "=", record.mdfe30_cUF)], limit=1 + ) + if state_id: + record.company_id.partner_id.state_id = state_id + + @api.depends("mdfe_route_state_ids") + def _compute_mdfe30_inf_percurso(self): + for record in self: + record.mdfe30_infPercurso = [(5, 0, 0)] + record.mdfe30_infPercurso = [ + ( + 0, + 0, + { + "mdfe30_UFPer": state.code, + }, + ) + for state in record.mdfe_route_state_ids + ] + + ########################## + # MDF-e tag: emit + ########################## + + mdfe30_emit = fields.Many2one(comodel_name="res.company", related="company_id") + + ########################## + # MDF-e tag: infModal + ########################## + + mdfe30_versaoModal = fields.Char(default=MDFE_MODAL_VERSION_DEFAULT) + + # Campos do Modal Aéreo + modal_aereo_id = fields.Many2one( + comodel_name="l10n_br_mdfe.modal.aereo", copy=False + ) + + mdfe30_nac = fields.Char(size=4, string="Nacionalidade da Aeronave") + + mdfe30_matr = fields.Char(size=6, string="Matrícula da Aeronave") + + mdfe30_nVoo = fields.Char(size=9, string="Número do Voo") + + mdfe30_dVoo = fields.Date(string="Data do Voo") + + mdfe30_cAerEmb = fields.Char( + default=MDFE_MODAL_DEFAULT_AIRCRAFT, size=4, string="Aeródromo de Embarque" + ) + + mdfe30_cAerDes = fields.Char( + default=MDFE_MODAL_DEFAULT_AIRCRAFT, size=4, string="Aeródromo de Destino" + ) + + # Campos do Modal Aquaviário + modal_aquaviario_id = fields.Many2one( + comodel_name="l10n_br_mdfe.modal.aquaviario", copy=False + ) + + mdfe30_irin = fields.Char(size=10, string="IRIN da Embarcação") + + mdfe30_tpEmb = fields.Selection( + selection=MDFE_MODAL_SHIP_TYPES, string="Tipo da Embarcação" + ) + + mdfe30_cEmbar = fields.Char(size=10, string="Código da Embarcação") + + mdfe30_xEmbar = fields.Char(size=60, string="Nome da Embarcação") + + mdfe30_nViag = fields.Char(string="Número da Viagem") + + mdfe30_cPrtEmb = fields.Selection( + selection=MDFE_MODAL_HARBORS, string="Porto de Embarque" + ) + + mdfe30_cPrtDest = fields.Selection( + selection=MDFE_MODAL_HARBORS, string="Porto de Destino" + ) + + mdfe30_prtTrans = fields.Char(size=60, string="Porto de Transbordo") + + mdfe30_tpNav = fields.Selection(selection=AQUAV_TPNAV, string="Tipo de Navegação") + + mdfe30_infTermCarreg = fields.One2many( + comodel_name="l10n_br_mdfe.modal.aquaviario.carregamento", + inverse_name="document_id", + size=5, + ) + + mdfe30_infTermDescarreg = fields.One2many( + comodel_name="l10n_br_mdfe.modal.aquaviario.descarregamento", + inverse_name="document_id", + size=5, + ) + + mdfe30_infEmbComb = fields.One2many( + comodel_name="l10n_br_mdfe.modal.aquaviario.comboio", + inverse_name="document_id", + size=30, + ) + + mdfe30_infUnidCargaVazia = fields.One2many( + comodel_name="l10n_br_mdfe.modal.aquaviario.carga.vazia", + inverse_name="document_id", + ) + + mdfe30_infUnidTranspVazia = fields.One2many( + comodel_name="l10n_br_mdfe.modal.aquaviario.transporte.vazio", + inverse_name="document_id", + ) + + # Campos do Modal Ferroviário + modal_ferroviario_id = fields.Many2one( + comodel_name="l10n_br_mdfe.modal.ferroviario", copy=False + ) + + mdfe30_xPref = fields.Char(string="Prefixo do Trem", size=10) + + mdfe30_dhTrem = fields.Datetime(string="Data/hora de Liberação do Trem") + + mdfe30_xOri = fields.Char(string="Origem do Trem", size=3) + + mdfe30_xDest = fields.Char(string="Destino do Trem", size=3) + + mdfe30_qVag = fields.Char(string="Quantidade de Vagões") + + mdfe30_vag = fields.One2many( + comodel_name="l10n_br_mdfe.modal.ferroviario.vagao", inverse_name="document_id" + ) + + # Campos do Modal Rodoviário + modal_rodoviario_id = fields.Many2one( + comodel_name="l10n_br_mdfe.modal.rodoviario", copy=False + ) + + mdfe30_codAgPorto = fields.Char(string="Código de Agendamento", size=16) + + mdfe30_infCIOT = fields.One2many( + comodel_name="l10n_br_mdfe.modal.rodoviario.ciot", inverse_name="document_id" + ) + + mdfe30_disp = fields.One2many( + comodel_name="l10n_br_mdfe.modal.rodoviario.vale_pedagio.dispositivo", + inverse_name="document_id", + ) + + mdfe30_categCombVeic = fields.Selection( + selection=VALEPED_CATEGCOMBVEIC, string="Categoria de Combinação Veicular" + ) + + mdfe30_infContratante = fields.One2many( + comodel_name="l10n_br_mdfe.modal.rodoviario.contratante", + inverse_name="document_id", + ) + + mdfe30_RNTRC = fields.Char(size=8, string="RNTRC") + + mdfe30_infPag = fields.One2many( + comodel_name="l10n_br_mdfe.modal.rodoviario.pagamento", + inverse_name="document_id", + ) + + mdfe30_prop = fields.Many2one( + comodel_name="res.partner", string="Proprietário do Veículo" + ) + + mdfe30_condutor = fields.One2many( + comodel_name="l10n_br_mdfe.modal.rodoviario.veiculo.condutor", + inverse_name="document_id", + size=10, + ) + + mdfe30_cInt = fields.Char(size=10, string="Código do Veículo") + + mdfe30_RENAVAM = fields.Char(size=11, string="RENAVAM") + + mdfe30_placa = fields.Char(string="Placa do Veículo") + + mdfe30_tara = fields.Char(string="Tara em KG") + + mdfe30_capKG = fields.Char(string="Capacidade em KG") + + mdfe30_capM3 = fields.Char(string="Capacidade em M3") + + mdfe30_tpRod = fields.Selection(selection=VEICTRACAO_TPROD, string="Tipo do Rodado") + + mdfe30_tpCar = fields.Selection( + selection=VEICTRACAO_TPCAR, string="Tipo de Carroceria" + ) + + mdfe30_veicReboque = fields.One2many( + comodel_name="l10n_br_mdfe.modal.rodoviario.reboque", + inverse_name="document_id", + size=3, + ) + + mdfe30_lacRodo = fields.One2many( + comodel_name="l10n_br_mdfe.modal.rodoviario.lacre", + inverse_name="document_id", + size=3, + ) + + mdfe30_UF = fields.Selection(selection=TUF, compute="_compute_mdfe30_rodo_uf") + + rodo_vehicle_state_id = fields.Many2one( + comodel_name="res.country.state", + string="UF do Veículo", + domain=[("country_id.code", "=", "BR")], + ) + + ########################## + # MDF-e tag: infModal + # Methods + ########################## + + @api.depends("rodo_vehicle_state_id") + def _compute_mdfe30_rodo_uf(self): + for record in self.filtered(filtered_processador_edoc_mdfe): + record.mdfe30_UF = record.rodo_vehicle_state_id.code + + def _export_fields_mdfe_30_infmodal(self, xsd_fields, class_obj, export_dict): + if self.mdfe_modal == "1": + export_dict["any_element"] = self._export_modal_rodoviario() + elif self.mdfe_modal == "2": + export_dict["any_element"] = self._export_modal_aereo() + elif self.mdfe_modal == "3": + export_dict["any_element"] = self._export_modal_aquaviario() + elif self.mdfe_modal == "4": + export_dict["any_element"] = self._export_modal_ferroviario() + + 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("mdfe", "30") + + 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("mdfe", "30") + + 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("mdfe", "30") + + 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("mdfe", "30") + + ########################## + # MDF-e tag: seg + ########################## + + mdfe30_seg = fields.One2many( + comodel_name="l10n_br_mdfe.seguro.carga", + inverse_name="document_id", + string="Seguros da Carga", + ) + + ########################## + # MDF-e tag: prodPred + ########################## + + mdfe30_prodPred = fields.Many2one(comodel_name="product.product") + + ########################## + # MDF-e tag: lacres + ########################## + + mdfe30_lacres = fields.One2many( + comodel_name="l10n_br_mdfe.transporte.lacre", + inverse_name="document_id", + ) + + ########################## + # MDF-e tag: infDoc + ########################## + + mdfe30_infMunDescarga = fields.One2many( + comodel_name="l10n_br_mdfe.municipio.descarga", inverse_name="document_id" + ) + + ########################## + # MDF-e tag: infRespTec + ########################## + + mdfe30_infRespTec = fields.Many2one( + comodel_name="res.partner", + related="company_id.technical_support_id", + string="Responsável Técnico MDFe", + ) + + ########################## + # NF-e tag: infAdic + ########################## + + mdfe30_infAdFisco = fields.Char( + compute="_compute_mdfe30_additional_data", + string="Informações Adicionais Fiscais MDFe", + ) + + mdfe30_infCpl = fields.Char( + compute="_compute_mdfe30_additional_data", + string="Informações Complementares MDFE", + ) + + ########################## + # MDF-e tag: infAdic + # Methods + ########################## + + @api.depends("fiscal_additional_data") + def _compute_mdfe30_additional_data(self): + for record in self.filtered(filtered_processador_edoc_mdfe): + record.mdfe30_infCpl = False + record.mdfe30_infAdFisco = False + + if record.fiscal_additional_data: + record.mdfe30_infAdFisco = ( + normalize("NFKD", record.fiscal_additional_data) + .encode("ASCII", "ignore") + .decode("ASCII") + .replace("\n", "") + .replace("\r", "") + ) + if record.customer_additional_data: + record.mdfe30_infCpl = ( + normalize("NFKD", record.customer_additional_data) + .encode("ASCII", "ignore") + .decode("ASCII") + .replace("\n", "") + .replace("\r", "") + ) + + ########################## + # MDF-e tag: autXML + ########################## + + def _default_mdfe30_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 + + mdfe30_autXML = fields.One2many(default=_default_mdfe30_autxml) + + ########################## + # NF-e tag: tot + ########################## + + mdfe30_qCTe = fields.Char(compute="_compute_mdfe30_tot") + + mdfe30_qNFe = fields.Char(compute="_compute_mdfe30_tot") + + mdfe30_qMDFe = fields.Char(compute="_compute_mdfe30_tot") + + mdfe30_qCarga = fields.Float(compute="_compute_mdfe30_tot") + + mdfe30_vCarga = fields.Float(compute="_compute_mdfe30_tot") + + mdfe30_cUnid = fields.Selection(default="01") + + ########################## + # MDF-e tag: tot + # Methods + ########################## + + @api.depends( + "mdfe30_infMunDescarga.cte_ids", + "mdfe30_infMunDescarga.nfe_ids", + "mdfe30_infMunDescarga.mdfe_ids", + ) + def _compute_mdfe30_tot(self): + for record in self.filtered(filtered_processador_edoc_mdfe): + record.mdfe30_qCarga = 0 + record.mdfe30_vCarga = 0 + + cte_ids = record.mdfe30_infMunDescarga.mapped("cte_ids") + nfe_ids = record.mdfe30_infMunDescarga.mapped("nfe_ids") + mdfe_ids = record.mdfe30_infMunDescarga.mapped("mdfe_ids") + + record.mdfe30_qCTe = cte_ids and len(cte_ids) or False + record.mdfe30_qNFe = nfe_ids and len(nfe_ids) or False + record.mdfe30_qMDFe = mdfe_ids and len(mdfe_ids) or False + + all_documents = cte_ids + nfe_ids + mdfe_ids + record.mdfe30_qCarga = sum(all_documents.mapped("document_total_weight")) + record.mdfe30_vCarga = sum(all_documents.mapped("document_total_amount")) + + ########################## + # NF-e tag: infMDFeSupl + ########################## + + mdfe30_infMDFeSupl = fields.Many2one( + comodel_name="l10n_br_fiscal.document.supplement", + ) + + ################################ + # Framework Spec model's methods + ################################ + + def _export_many2one(self, field_name, xsd_required, class_obj=None): + if field_name == "mdfe30_infModal": + return self._build_binding( + class_name=class_obj._fields[field_name].comodel_name + ) + + return super()._export_many2one(field_name, xsd_required, class_obj) + + def _build_attr(self, node, fields, vals, path, attr): + key = "mdfe30_%s" % (attr[0],) # TODO schema wise + value = getattr(node, attr[0]) + + # if attr[0] == "any_element": # build modal + # modal_id = self._get_modal_to_build(node.any_element.__module__) + # if modal_id is False: + # return + + # modal_attrs = modal_id.build_attrs(value, path=path) + # for chave, valor in modal_attrs.items(): + # vals[chave] = valor + # return + + if key == "mdfe30_mod": + if isinstance(value, Enum): + value = value.value + + vals["document_type_id"] = ( + self.env["l10n_br_fiscal.document.type"] + .search([("code", "=", value)], limit=1) + .id + ) + + return super()._build_attr(node, fields, vals, path, attr) + + def _get_modal_to_build(self, module): + modal_by_binding_module = { + self.modal_rodoviario_id._binding_module: self.modal_rodoviario_id, + self.modal_aereo_id._binding_module: self.modal_aereo_id, + self.modal_aquaviario_id._binding_module: self.modal_aquaviario_id, + self.modal_ferroviario_id._binding_module: self.modal_ferroviario_id, + } + if module not in modal_by_binding_module: + return False + + return modal_by_binding_module[module] + + def _build_many2one(self, comodel, vals, new_value, key, value, path): + if key == "mdfe30_emit" and self.env.context.get("edoc_type") == "in": + enderEmit_value = self.env["res.partner"].build_attrs( + value.enderEmit, path=path + ) + new_value.update(enderEmit_value) + company_cnpj = self.env.user.company_id.cnpj_cpf.translate( + str.maketrans("", "", string.punctuation) + ) + emit_cnpj = new_value.get("mdfe30_CNPJ", False) + if emit_cnpj: + emit_cnpj = new_value.get("mdfe30_CNPJ").translate( + str.maketrans("", "", string.punctuation) + ) + if company_cnpj != emit_cnpj: + vals["issuer"] = "partner" + new_value["is_company"] = True + new_value["cnpj_cpf"] = emit_cnpj + super()._build_many2one( + self.env["res.partner"], vals, new_value, "partner_id", value, path + ) + + else: + super()._build_many2one(comodel, vals, new_value, key, value, path) + + @api.model + def match_or_create_m2o(self, rec_dict, parent_dict, model=None): + if rec_dict.get("mdfe30_Id"): + domain = [("mdfe30_Id", "=", rec_dict.get("mdfe30_Id"))] + match = self.search(domain, limit=1) + if match: + return match.id + return False + + ################################ + # Business Model Methods + ################################ + + def _serialize(self, edocs): + edocs = super()._serialize(edocs) + for record in self.with_context(lang="pt_BR").filtered( + filtered_processador_edoc_mdfe + ): + inf_mdfe = record._build_binding("mdfe", "30") + + inf_mdfe_supl = None + if record.mdfe30_infMDFeSupl: + inf_mdfe_supl = record.mdfe30_infMDFeSupl._build_binding("mdfe", "30") + + mdfe = Mdfe(infMDFe=inf_mdfe, infMDFeSupl=inf_mdfe_supl, signature=None) + edocs.append(mdfe) + return edocs + + def _edoc_processor(self): + if self.document_type != MODELO_FISCAL_MDFE: + return super()._edoc_processor() + + certificado = self.company_id._get_br_ecertificate() + + session = Session() + session.verify = False + + params = { + "transmissao": TransmissaoSOAP(certificado, session), + "uf": self.company_id.state_id.ibge_code, + "versao": self.mdfe_version, + "ambiente": self.mdfe_environment, + } + return edoc_mdfe(**params) + + def _generate_key(self): + if self.document_type_id.code not in [MODELO_FISCAL_MDFE]: + return super()._generate_key() + + for record in self.filtered(filtered_processador_edoc_mdfe): + date = fields.Datetime.context_timestamp(record, record.document_date) + chave_edoc = ChaveEdoc( + ano_mes=date.strftime("%y%m").zfill(4), + cnpj_cpf_emitente=record.company_cnpj_cpf, + codigo_uf=( + record.company_state_id and record.company_state_id.ibge_code or "" + ), + forma_emissao=int(self.mdfe_transmission), + modelo_documento=record.document_type_id.code or "", + numero_documento=record.document_number or "", + numero_serie=record.document_serie or "", + validar=False, + ) + record.key_random_code = chave_edoc.codigo_aleatorio + record.key_check_digit = chave_edoc.digito_verificador + record.document_key = chave_edoc.chave + + def _document_export(self, pretty_print=True): + result = super()._document_export() + for record in self.filtered(filtered_processador_edoc_mdfe): + edoc = record.serialize()[0] + processador = record._edoc_processor() + xml_file = processador.render_edoc_xsdata(edoc, pretty_print=pretty_print)[ + 0 + ] + # Delete previous authorization events in draft + if ( + record.authorization_event_id + and record.authorization_event_id.state == "draft" + ): + record.sudo().authorization_event_id.unlink() + + event_id = self.event_ids.create_event_save_xml( + company_id=self.company_id, + environment=( + EVENT_ENV_PROD if self.mdfe_environment == "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.infMDFe.Id) + self._validate_xml(xml_assinado) + return result + + def _validate_xml(self, xml_file): + self.ensure_one() + + if self.document_type != MODELO_FISCAL_MDFE: + return super()._validate_xml(xml_file) + + erros = Mdfe.schema_validation(xml_file) + erros = "\n".join(erros) + self.write({"xml_error_message": erros or False}) diff --git a/l10n_br_mdfe/models/document_info.py b/l10n_br_mdfe/models/document_info.py new file mode 100644 index 000000000000..bd3e8238e5d3 --- /dev/null +++ b/l10n_br_mdfe/models/document_info.py @@ -0,0 +1,96 @@ +# Copyright 2023 KMEE +# 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 MDFeMunicipioDescarga(spec_models.SpecModel): + _name = "l10n_br_mdfe.municipio.descarga" + _inherit = "mdfe.30.infmundescarga" + _description = "Informações de Descarga do Documento MDFe" + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + mdfe30_cMunDescarga = fields.Char(compute="_compute_city_data") + + mdfe30_xMunDescarga = fields.Char(compute="_compute_city_data") + + mdfe30_infCTe = fields.One2many(compute="_compute_document_data") + + mdfe30_infNFe = fields.One2many(compute="_compute_document_data") + + mdfe30_infMDFeTransp = fields.One2many(compute="_compute_document_data") + + country_id = fields.Many2one( + comodel_name="res.country.state", + default=lambda self: self.env.ref("base.br"), + ) + + state_id = fields.Many2one( + comodel_name="res.country.state", + string="State", + domain="[('country_id', '=', country_id)]", + required=True, + ) + + city_id = fields.Many2one( + string="City", + comodel_name="res.city", + domain="[('state_id', '=', state_id)]", + required=True, + ) + + document_type = fields.Selection( + selection=[ + ("nfe", "NF-e"), + ("cte", "CT-e"), + ("mdfe", "MDF-e"), + ], + string="Document Type", + default="nfe", + required=True, + ) + + nfe_ids = fields.Many2many( + comodel_name="l10n_br_fiscal.document.related", + relation="mdfe_related_nfe_carregamento_rel", + ) + + cte_ids = fields.Many2many( + comodel_name="l10n_br_fiscal.document.related", + relation="mdfe_related_cte_carregamento_rel", + ) + + mdfe_ids = fields.Many2many( + comodel_name="l10n_br_fiscal.document.related", + relation="mdfe_related_mdfe_carregamento_rel", + ) + + @api.depends("city_id") + def _compute_city_data(self): + for record in self: + record.mdfe30_cMunDescarga = record.city_id.ibge_code + record.mdfe30_xMunDescarga = record.city_id.name + + @api.depends("document_type", "nfe_ids", "cte_ids") + def _compute_document_data(self): + for record in self: + record.mdfe30_infCTe = [(5, 0, 0)] + record.mdfe30_infNFe = [(5, 0, 0)] + record.mdfe30_infMDFeTransp = [(5, 0, 0)] + + if record.document_type == "nfe": + record.mdfe30_infNFe = [ + (0, 0, {"mdfe30_chNFe": nfe.mdfe30_chNFe}) for nfe in record.nfe_ids + ] + elif record.document_type == "cte": + record.mdfe30_infCTe = [ + (0, 0, {"mdfe30_chCTe": cte.mdfe30_chCTe}) for cte in record.cte_ids + ] + else: + record.mdfe30_infMDFeTransp = [ + (0, 0, {"mdfe30_chMDFe": mdfe.mdfe30_chMDFe}) + for mdfe in record.mdfe_ids + ] diff --git a/l10n_br_mdfe/models/document_related.py b/l10n_br_mdfe/models/document_related.py new file mode 100644 index 000000000000..f2b48d7d2054 --- /dev/null +++ b/l10n_br_mdfe/models/document_related.py @@ -0,0 +1,30 @@ +# 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 MDFeRelated(spec_models.StackedModel): + _name = "l10n_br_fiscal.document.related" + _inherit = [ + "l10n_br_fiscal.document.related", + "mdfe.30.infmdfetransp", + "mdfe.30.tmdfe_infnfe", + "mdfe.30.infcte", + ] + _mdfe30_odoo_module = ( + "odoo.addons.l10n_br_mdfe_spec.models.v3_0.mdfe_tipos_basico_v3_00" + ) + _mdfe30_stacking_mixin = "mdfe.30.tmdfe_infnfe" + + mdfe30_chNFe = fields.Char(related="document_key") + + mdfe30_chCTe = fields.Char(related="document_key") + + mdfe30_chMDFe = fields.Char(related="document_key") + + mdfe30_peri = fields.One2many(comodel_name="l10n_br_mdfe.transporte.perigoso") + + mdfe30_infUnidTransp = fields.One2many(comodel_name="l10n_br_mdfe.transporte.inf") diff --git a/l10n_br_mdfe/models/document_supplement.py b/l10n_br_mdfe/models/document_supplement.py new file mode 100644 index 000000000000..3c6097652d21 --- /dev/null +++ b/l10n_br_mdfe/models/document_supplement.py @@ -0,0 +1,17 @@ +# Copyright 2023 KMEE (Felipe Zago Rodrigues ) +# 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 MDFeSupplement(spec_models.StackedModel): + _name = "l10n_br_fiscal.document.supplement" + _inherit = ["l10n_br_fiscal.document.supplement", "mdfe.30.infmdfesupl"] + _mdfe30_odoo_module = ( + "odoo.addons.l10n_br_mdfe_spec.models.v3_0.mdfe_tipos_basico_v3_00" + ) + _mdfe30_stacking_mixin = "mdfe.30.infmdfesupl" + + mdfe30_qrCodMDFe = fields.Char(related="qrcode") diff --git a/l10n_br_mdfe/models/document_type.py b/l10n_br_mdfe/models/document_type.py new file mode 100644 index 000000000000..0e11219b5a62 --- /dev/null +++ b/l10n_br_mdfe/models/document_type.py @@ -0,0 +1,9 @@ +# Copyright (C) 2023 KMEE +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models + + +class DocumentType(models.Model): + _inherit = "l10n_br_fiscal.document.type" + _mdfe_search_keys = ["code"] diff --git a/l10n_br_mdfe/models/modal_aereo.py b/l10n_br_mdfe/models/modal_aereo.py new file mode 100644 index 000000000000..4e638302e99a --- /dev/null +++ b/l10n_br_mdfe/models/modal_aereo.py @@ -0,0 +1,32 @@ +# 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 MDFeModalAereo(spec_models.StackedModel): + _name = "l10n_br_mdfe.modal.aereo" + _inherit = "mdfe.30.aereo" + _description = "Modal Aereo MDFe" + + _mdfe30_odoo_module = ( + "odoo.addons.l10n_br_mdfe_spec.models.v3_0.mdfe_modal_aereo_v3_00" + ) + _mdfe30_binding_module = "nfelib.mdfe.bindings.v3_0.mdfe_modal_aereo_v3_00" + _mdfe30_stacking_mixin = "mdfe.30.aereo" + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + mdfe30_nac = fields.Char(related="document_id.mdfe30_nac") + + mdfe30_matr = fields.Char(related="document_id.mdfe30_matr") + + mdfe30_nVoo = fields.Char(related="document_id.mdfe30_nVoo") + + mdfe30_dVoo = fields.Date(related="document_id.mdfe30_dVoo") + + mdfe30_cAerEmb = fields.Char(related="document_id.mdfe30_cAerEmb") + + mdfe30_cAerDes = fields.Char(related="document_id.mdfe30_cAerDes") diff --git a/l10n_br_mdfe/models/modal_aquaviario.py b/l10n_br_mdfe/models/modal_aquaviario.py new file mode 100644 index 000000000000..8240f5cfb638 --- /dev/null +++ b/l10n_br_mdfe/models/modal_aquaviario.py @@ -0,0 +1,157 @@ +# Copyright 2023 KMEE +# 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 + +from ..constants.modal import MDFE_MODAL_HARBORS + + +class MDFeModalAquaviario(spec_models.StackedModel): + _name = "l10n_br_mdfe.modal.aquaviario" + _inherit = "mdfe.30.aquav" + _description = "Modal Aquaviário MDFe" + + _mdfe30_odoo_module = ( + "odoo.addons.l10n_br_mdfe_spec.models.v3_0.mdfe_modal_aquaviario_v3_00" + ) + _mdfe30_binding_module = "nfelib.mdfe.bindings.v3_0.mdfe_modal_aquaviario_v3_00" + _mdfe30_stacking_mixin = "mdfe.30.aquav" + _mdfe_search_keys = ["mdfe30_irin", "mdfe30_cEmbar", "mdfe30_nViag"] + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + mdfe30_irin = fields.Char(related="document_id.mdfe30_irin") + + mdfe30_cEmbar = fields.Char(related="document_id.mdfe30_cEmbar") + + mdfe30_xEmbar = fields.Char(related="document_id.mdfe30_xEmbar") + + mdfe30_nViag = fields.Char(related="document_id.mdfe30_nViag") + + mdfe30_prtTrans = fields.Char(related="document_id.mdfe30_prtTrans") + + mdfe30_tpNav = fields.Selection(related="document_id.mdfe30_tpNav") + + mdfe30_infTermCarreg = fields.One2many(related="document_id.mdfe30_infTermCarreg") + + mdfe30_infTermDescarreg = fields.One2many( + related="document_id.mdfe30_infTermDescarreg" + ) + + mdfe30_infEmbComb = fields.One2many(related="document_id.mdfe30_infEmbComb") + + mdfe30_infUnidCargaVazia = fields.One2many( + related="document_id.mdfe30_infUnidCargaVazia" + ) + + mdfe30_infUnidTranspVazia = fields.One2many( + related="document_id.mdfe30_infUnidTranspVazia" + ) + + mdfe30_tpEmb = fields.Char(compute="_compute_mdfe30_tpEmb") + + mdfe30_cPrtEmb = fields.Char(compute="_compute_boarding_landing_point") + + mdfe30_cPrtDest = fields.Char(compute="_compute_boarding_landing_point") + + @api.depends("document_id.mdfe30_tpEmb") + def _compute_mdfe30_tpEmb(self): + for record in self: + record.mdfe30_tpEmb = record.document_id.mdfe30_tpEmb + + @api.depends("document_id.mdfe30_cPrtEmb", "document_id.mdfe30_cPrtDest") + def _compute_boarding_landing_point(self): + for record in self: + record.mdfe30_cPrtEmb = record.document_id.mdfe30_cPrtEmb + record.mdfe30_cPrtDest = record.document_id.mdfe30_cPrtDest + + +class MDFeModalAquaviarioCarregamento(spec_models.SpecModel): + _name = "l10n_br_mdfe.modal.aquaviario.carregamento" + _inherit = "mdfe.30.inftermcarreg" + _description = "Carregamento no Modal Aquaviário MDFe" + _mdfe30_binding_module = "nfelib.mdfe.bindings.v3_0.mdfe_modal_aquaviario_v3_00" + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + loading_harbor = fields.Selection( + selection=MDFE_MODAL_HARBORS, string="Loading Harbor", required=True + ) + + mdfe30_cTermCarreg = fields.Char(compute="_compute_loading_harbor") + + mdfe30_xTermCarreg = fields.Char(compute="_compute_loading_harbor") + + @api.depends("loading_harbor") + def _compute_loading_harbor(self): + for record in self: + record.mdfe30_cTermCarreg = record.loading_harbor + record.mdfe30_xTermCarreg = dict( + self._fields["loading_harbor"].selection + ).get(record.loading_harbor) + + +class MDFeModalAquaviarioDescarregamento(spec_models.SpecModel): + _name = "l10n_br_mdfe.modal.aquaviario.descarregamento" + _inherit = "mdfe.30.inftermdescarreg" + _description = "Descarregamento no Modal Aquaviário MDFe" + _mdfe30_binding_module = "nfelib.mdfe.bindings.v3_0.mdfe_modal_aquaviario_v3_00" + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + unloading_harbor = fields.Selection( + selection=MDFE_MODAL_HARBORS, string="Unloading Harbor", required=True + ) + + mdfe30_cTermDescarreg = fields.Char(compute="_compute_unloading_harbor") + + mdfe30_xTermDescarreg = fields.Char(compute="_compute_unloading_harbor") + + @api.depends("unloading_harbor") + def _compute_unloading_harbor(self): + for record in self: + record.mdfe30_cTermDescarreg = record.unloading_harbor + record.mdfe30_xTermDescarreg = dict( + self._fields["unloading_harbor"].selection + ).get(record.unloading_harbor) + + +class MDFeModalAquaviarioComboio(spec_models.SpecModel): + _name = "l10n_br_mdfe.modal.aquaviario.comboio" + _inherit = "mdfe.30.infembcomb" + _description = "Informações de Comboio no Modal Aquaviário MDFe" + _mdfe30_binding_module = "nfelib.mdfe.bindings.v3_0.mdfe_modal_aquaviario_v3_00" + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + mdfe30_cEmbComb = fields.Char(required=True, size=10) + + mdfe30_xBalsa = fields.Char(required=True, size=60) + + +class MDFeModalAquaviarioCargaVazia(spec_models.SpecModel): + _name = "l10n_br_mdfe.modal.aquaviario.carga.vazia" + _inherit = "mdfe.30.infunidcargavazia" + _description = "Informações de Carga Vazia no Modal Aquaviário MDFe" + _mdfe30_binding_module = "nfelib.mdfe.bindings.v3_0.mdfe_modal_aquaviario_v3_00" + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + mdfe30_idUnidCargaVazia = fields.Char(required=True) + + mdfe30_tpUnidCargaVazia = fields.Selection(required=True) + + +class MDFeModalAquaviarioTranporteVazio(spec_models.SpecModel): + _name = "l10n_br_mdfe.modal.aquaviario.transporte.vazio" + _inherit = "mdfe.30.infunidtranspvazia" + _description = "Informações de Transporte Vazio no Modal Aquaviário MDFe" + _mdfe30_binding_module = "nfelib.mdfe.bindings.v3_0.mdfe_modal_aquaviario_v3_00" + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + mdfe30_idUnidTranspVazia = fields.Char(required=True) + + mdfe30_tpUnidTranspVazia = fields.Selection(required=True) diff --git a/l10n_br_mdfe/models/modal_ferroviario.py b/l10n_br_mdfe/models/modal_ferroviario.py new file mode 100644 index 000000000000..d18fecdbe981 --- /dev/null +++ b/l10n_br_mdfe/models/modal_ferroviario.py @@ -0,0 +1,66 @@ +# Copyright 2023 KMEE +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, api, fields +from odoo.exceptions import UserError + +from odoo.addons.spec_driven_model.models import spec_models + + +class MDFeModalFerroviario(spec_models.StackedModel): + _name = "l10n_br_mdfe.modal.ferroviario" + _inherit = "mdfe.30.ferrov" + _description = "Modal Ferroviário MDFe" + + _mdfe30_odoo_module = ( + "odoo.addons.l10n_br_mdfe_spec.models.v3_0.mdfe_modal_ferroviario_v3_00" + ) + _mdfe30_binding_module = "nfelib.mdfe.bindings.v3_0.mdfe_modal_ferroviario_v3_00" + _mdfe30_stacking_mixin = "mdfe.30.ferrov" + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + mdfe30_xPref = fields.Char(related="document_id.mdfe30_xPref") + + mdfe30_dhTrem = fields.Datetime(related="document_id.mdfe30_dhTrem") + + mdfe30_xOri = fields.Char(related="document_id.mdfe30_xOri") + + mdfe30_xDest = fields.Char(related="document_id.mdfe30_xDest") + + mdfe30_qVag = fields.Char(related="document_id.mdfe30_qVag") + + mdfe30_vag = fields.One2many(related="document_id.mdfe30_vag") + + +class MDFeModalFerroviarioVagao(spec_models.SpecModel): + _name = "l10n_br_mdfe.modal.ferroviario.vagao" + _inherit = "mdfe.30.vag" + _description = "Informações do Vagão no Modal Ferroviário MDFe" + _mdfe30_binding_module = "nfelib.mdfe.bindings.v3_0.mdfe_modal_ferroviario_v3_00" + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + mdfe30_pesoBC = fields.Float(required=True) + + mdfe30_pesoR = fields.Float(required=True) + + mdfe30_serie = fields.Char(required=True) + + mdfe30_nVag = fields.Char(required=True) + + mdfe30_TU = fields.Char(required=True) + + @api.constrains("mdfe30_serie") + def check_serie(self): + for _record in self.filtered( + lambda v: v.mdfe30_serie and len(v.mdfe30_serie) != 3 + ): + raise UserError(_("Wagon serie must have exactly 3 digits.")) + + @api.constrains("mdfe30_tpVag") + def check_tp_vag(self): + for _record in self.filtered( + lambda v: v.mdfe30_serie and len(v.mdfe30_tpVag) != 3 + ): + raise UserError(_("Wagon type must have exactly 3 digits.")) diff --git a/l10n_br_mdfe/models/modal_rodoviario.py b/l10n_br_mdfe/models/modal_rodoviario.py new file mode 100644 index 000000000000..58f32abee43d --- /dev/null +++ b/l10n_br_mdfe/models/modal_rodoviario.py @@ -0,0 +1,297 @@ +# Copyright 2023 KMEE +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from erpbrasil.base.misc import punctuation_rm + +from odoo import api, fields + +from odoo.addons.spec_driven_model.models import spec_models + + +class MDFeModalRodoviario(spec_models.StackedModel): + _name = "l10n_br_mdfe.modal.rodoviario" + _inherit = "mdfe.30.rodo" + _description = "Modal Rodoviário MDFe" + _mdfe30_odoo_module = ( + "odoo.addons.l10n_br_mdfe_spec.models.v3_0.mdfe_modal_rodoviario_v3_00" + ) + _mdfe30_binding_module = "nfelib.mdfe.bindings.v3_0.mdfe_modal_rodoviario_v3_00" + _mdfe30_stacking_mixin = "mdfe.30.rodo" + # all m2o at this level will be stacked even if not required: + _mdfe30_stacking_force_paths = ["rodo.infANTT", "rodo.infANTT.ValePed"] + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + mdfe30_codAgPorto = fields.Char(related="document_id.mdfe30_codAgPorto") + + mdfe30_infCIOT = fields.One2many(related="document_id.mdfe30_infCIOT") + + mdfe30_disp = fields.One2many(related="document_id.mdfe30_disp") + + mdfe30_categCombVeic = fields.Selection(related="document_id.mdfe30_categCombVeic") + + mdfe30_infContratante = fields.One2many(related="document_id.mdfe30_infContratante") + + mdfe30_RNTRC = fields.Char(related="document_id.mdfe30_RNTRC") + + mdfe30_infPag = fields.One2many(related="document_id.mdfe30_infPag") + + mdfe30_prop = fields.Many2one(related="document_id.mdfe30_prop") + + mdfe30_condutor = fields.One2many(related="document_id.mdfe30_condutor") + + mdfe30_cInt = fields.Char(related="document_id.mdfe30_cInt") + + mdfe30_RENAVAM = fields.Char(related="document_id.mdfe30_RENAVAM") + + mdfe30_placa = fields.Char(related="document_id.mdfe30_placa") + + mdfe30_tara = fields.Char(related="document_id.mdfe30_tara") + + mdfe30_capKG = fields.Char(related="document_id.mdfe30_capKG") + + mdfe30_capM3 = fields.Char(related="document_id.mdfe30_capM3") + + mdfe30_tpRod = fields.Selection(related="document_id.mdfe30_tpRod") + + mdfe30_tpCar = fields.Selection(related="document_id.mdfe30_tpCar") + + mdfe30_UF = fields.Selection(related="document_id.mdfe30_UF") + + mdfe30_veicReboque = fields.One2many(related="document_id.mdfe30_veicReboque") + + mdfe30_lacRodo = fields.One2many(related="document_id.mdfe30_lacRodo") + + # @api.depends("document_id.mdfe30_infContratante") + # def _compute_contractor(self): + # for record in self: + # record.mdfe30_infContratante = [ + # (6, 0, record.document_id.mdfe30_infContratante.ids) + # ] + + +class MDFeModalRodoviarioCIOT(spec_models.SpecModel): + _name = "l10n_br_mdfe.modal.rodoviario.ciot" + _inherit = "mdfe.30.infciot" + _description = "Informações do CIOT no Modal Rodoviário MDFe" + _mdfe30_binding_module = "nfelib.mdfe.bindings.v3_0.mdfe_modal_rodoviario_v3_00" + _mdfe_search_keys = ["mdfe30_CIOT"] + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + mdfe30_CIOT = fields.Char(required=True, size=12) + + is_company = fields.Boolean(string="É empresa?", required=True) + + mdfe30_CNPJ = fields.Char(string="CNPJ do responsável") + + mdfe30_CPF = fields.Char(string="CPF do responsável") + + mdfe30_choice_responsible = fields.Selection( + selection=[("mdfe30_CNPJ", "CNPJ"), ("mdfe30_CPF", "CPF")], + string="CNPJ/CPF do responsável", + compute="_compute_ciot_choice", + ) + + @api.depends("is_company") + def _compute_ciot_choice(self): + for record in self: + record.mdfe30_choice_responsible = ( + "mdfe30_CNPJ" if record.is_company else "mdfe30_CPF" + ) + + +class MDFeModalRodoviarioValePedagioDispositivo(spec_models.SpecModel): + _name = "l10n_br_mdfe.modal.rodoviario.vale_pedagio.dispositivo" + _inherit = "mdfe.30.disp" + _description = "Informações de Dispositivos do Pedágio no Modal Rodoviário MDFe" + _mdfe30_binding_module = "nfelib.mdfe.bindings.v3_0.mdfe_modal_rodoviario_v3_00" + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + mdfe30_CNPJForn = fields.Char(required=True) + + mdfe30_vValePed = fields.Monetary(required=True) + + +class MDFeModalRodoviarioPagamento(spec_models.StackedModel): + _name = "l10n_br_mdfe.modal.rodoviario.pagamento" + _inherit = "mdfe.30.rodo_infpag" + _description = "Informações do Pagamento do Modal Rodoviário MDFe" + _mdfe30_odoo_module = ( + "odoo.addons.l10n_br_mdfe_spec.models.v3_0.mdfe_modal_rodoviario_v3_00" + ) + _mdfe30_binding_module = "nfelib.mdfe.bindings.v3_0.mdfe_modal_rodoviario_v3_00" + _mdfe30_stacking_mixin = "mdfe.30.rodo_infpag" + # all m2o at this level will be stacked even if not required: + _mdfe30_stacking_force_paths = ["rodo.infANTT", "rodo.infANTT.ValePed"] + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + partner_id = fields.Many2one( + comodel_name="res.partner", string="Responsável pelo pagamento", required=True + ) + + mdfe30_CNPJ = fields.Char(related="partner_id.mdfe30_CNPJ") + + mdfe30_CPF = fields.Char(related="partner_id.mdfe30_CPF") + + mdfe30_xNome = fields.Char(related="partner_id.mdfe30_xNome") + + mdfe30_choice_tresponsible = fields.Selection( + selection=[ + ("mdfe30_CNPJ", "CNPJ"), + ("mdfe30_CPF", "CPF"), + ("mdfe40_idEstrangeiro", "idEstrangeiro"), + ], + string="CNPJ/CPF/Id Estrangeiro do responsável pelo pagamento", + compute="_compute_mdfe_data", + ) + + mdfe30_comp = fields.One2many( + comodel_name="l10n_br_mdfe.modal.rodoviario.pagamento.frete" + ) + + mdfe30_infPrazo = fields.One2many( + comodel_name="l10n_br_mdfe.modal.rodoviario.pagamento.prazo" + ) + + mdfe30_vContrato = fields.Monetary(required=True) + + mdfe30_indPag = fields.Selection(required=True) + + payment_type = fields.Selection( + selection=[ + ("bank", "Banco"), + ("pix", "PIX"), + ], + string="Meio de Pagamento", + default="bank", + ) + + mdfe30_choice_tpayment = fields.Selection( + selection=[ + ("mdfe30_codBanco", "Banco"), + ("mdfe30_codAgencia", "Agencia"), + ("mdfe30_CNPJIPEF", "CNPJ"), + ("mdfe30_PIX", "PIX"), + ], + string="Método de Pagamento", + compute="_compute_payment_type", + ) + + @api.depends("payment_type") + def _compute_payment_type(self): + for record in self: + if record.payment_type == "bank": + record.mdfe30_choice_tpayment = "mdfe30_codBanco" + elif record.payment_type == "pix": + record.mdfe30_choice_tpayment = "mdfe30_PIX" + else: + record.mdfe30_choice_tpayment = False + + @api.depends("partner_id") + def _compute_mdfe_data(self): + for rec in self: + cnpj_cpf = punctuation_rm(rec.partner_id.cnpj_cpf) + if cnpj_cpf: + if rec.partner_id.country_id.code != "BR": + rec.mdfe30_choice_tresponsible = "mdfe40_idEstrangeiro" + elif rec.partner_id.is_company: + rec.mdfe30_choice_tresponsible = "mdfe30_CNPJ" + else: + rec.mdfe30_choice_tresponsible = "mdfe30_CPF" + else: + rec.mdfe30_choice_tresponsible = False + + +class MDFeModalRodoviarioPagamentoFrete(spec_models.SpecModel): + _name = "l10n_br_mdfe.modal.rodoviario.pagamento.frete" + _inherit = "mdfe.30.rodo_comp" + _description = "Informações do Frete no Pagamento do Modal Rodoviário MDFe" + _mdfe30_binding_module = "nfelib.mdfe.bindings.v3_0.mdfe_modal_rodoviario_v3_00" + + mdfe30_tpComp = fields.Selection(required=True) + + mdfe30_vComp = fields.Monetary(required=True) + + +class MDFeModalRodoviarioPagamentoPrazo(spec_models.SpecModel): + _name = "l10n_br_mdfe.modal.rodoviario.pagamento.prazo" + _inherit = "mdfe.30.rodo_infprazo" + _description = "Informações de Prazo de Pagamento do Modal Rodoviário MDFe" + _mdfe30_binding_module = "nfelib.mdfe.bindings.v3_0.mdfe_modal_rodoviario_v3_00" + + mdfe30_nParcela = fields.Char(required=True) + + mdfe30_dVenc = fields.Date(required=True) + + mdfe30_vParcela = fields.Monetary(required=True) + + +class MDFeModalRodoviarioVeiculoCondutor(spec_models.SpecModel): + _name = "l10n_br_mdfe.modal.rodoviario.veiculo.condutor" + _inherit = "mdfe.30.rodo_condutor" + _description = "Informações do Condutor no Modal Rodoviário MDFe" + _mdfe30_binding_module = "nfelib.mdfe.bindings.v3_0.mdfe_modal_rodoviario_v3_00" + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + mdfe30_xNome = fields.Char(required=True) + + mdfe30_CPF = fields.Char(required=True) + + +class MDFeModalRodoviarioReboque(spec_models.SpecModel): + _name = "l10n_br_mdfe.modal.rodoviario.reboque" + _inherit = "mdfe.30.veicreboque" + _description = "Informações de Reboque no Modal Rodoviário MDFe" + _mdfe30_binding_module = "nfelib.mdfe.bindings.v3_0.mdfe_modal_rodoviario_v3_00" + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + mdfe30_prop = fields.Many2one(comodel_name="res.partner") + + mdfe30_placa = fields.Char(required=True) + + mdfe30_tara = fields.Char(required=True) + + mdfe30_capKG = fields.Char(required=True) + + mdfe30_tpCar = fields.Selection(required=True) + + mdfe30_cInt = fields.Char(size=10) + + mdfe30_RENAVAM = fields.Char(size=11) + + +class MDFeModalRodoviarioLacre(spec_models.SpecModel): + _name = "l10n_br_mdfe.modal.rodoviario.lacre" + _inherit = "mdfe.30.lacrodo" + _description = "Lacre MDFe" + _mdfe30_binding_module = "nfelib.mdfe.bindings.v3_0.mdfe_modal_rodoviario_v3_00" + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + mdfe30_nLacre = fields.Char(required=True, size=20) + + +class MDFeModalRodoviarioContratante(spec_models.SpecModel): + _name = "l10n_br_mdfe.modal.rodoviario.contratante" + _inherit = "mdfe.30.infcontratante" + _description = "Contratante MDFe" + _mdfe30_binding_module = "nfelib.mdfe.bindings.v3_0.mdfe_modal_rodoviario_v3_00" + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + partner_id = fields.Many2one( + comodel_name="res.partner", string="Contratante", required=True + ) + + mdfe30_CNPJ = fields.Char(related="partner_id.mdfe30_CNPJ") + + mdfe30_CPF = fields.Char(related="partner_id.mdfe30_CPF") + + mdfe30_idEstrangeiro = fields.Char(related="partner_id.mdfe30_idEstrangeiro") + + mdfe30_xNome = fields.Char(related="partner_id.mdfe30_xNome") diff --git a/l10n_br_mdfe/models/product_product.py b/l10n_br_mdfe/models/product_product.py new file mode 100644 index 000000000000..b3d656797190 --- /dev/null +++ b/l10n_br_mdfe/models/product_product.py @@ -0,0 +1,68 @@ +# Copyright 2023 KMEE +# 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 ProductProduct(spec_models.SpecModel): + _name = "product.product" + _inherit = [ + "product.product", + "mdfe.30.prodpred", + ] + + mdfe30_xProd = fields.Char(related="name") + + mdfe30_cEAN = fields.Char(related="barcode") + + mdfe30_NCM = fields.Char(string="ncm_id.code") + + mdfe30_tpCarga = fields.Selection(default="05") + + mdfe30_infLotacao = fields.Many2one(comodel_name="l10n_br_mdfe.product.lotacao") + + +class MDFeProductLotacao(spec_models.SpecModel): + _name = "l10n_br_mdfe.product.lotacao" + _inherit = "mdfe.30.inflotacao" + _description = "Informações De Lotação MDFe" + + mdfe30_infLocalCarrega = fields.Many2one( + comodel_name="l10n_br_mdfe.product.lotacao.local", + required=True, + ) + + mdfe30_infLocalDescarrega = fields.Many2one( + comodel_name="l10n_br_mdfe.product.lotacao.local", + required=True, + ) + + +class MDFeProductLotacaoLocal(spec_models.SpecModel): + _name = "l10n_br_mdfe.product.lotacao.local" + _inherit = ["mdfe.30.inflocalcarrega", "mdfe.30.inflocaldescarrega"] + _description = "Informações De Localização da Lotação MDFe" + + local_type = fields.Selection( + selection=[ + ("CEP", "CEP"), + ("coord", "Coordenadas"), + ], + default="CEP", + ) + + mdfe30_choice_tlocal = fields.Selection( + selection=[("mdfe30_CEP", "CEP"), ("mdfe30_latitude", "Latitude/Longitude")], + string="Tipo de Local", + compute="_compute_choice", + ) + + @api.depends("local_type") + def _compute_choice(self): + for record in self: + if record.local_type == "CEP": + record.mdfe30_choice_tlocal = "mdfe30_CEP" + else: + record.mdfe30_choice_tlocal = "mdfe30_latitude" diff --git a/l10n_br_mdfe/models/res_company.py b/l10n_br_mdfe/models/res_company.py new file mode 100644 index 000000000000..efe22d09aebf --- /dev/null +++ b/l10n_br_mdfe/models/res_company.py @@ -0,0 +1,114 @@ +# Copyright 2023 KMEE +# 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 + +from ..constants.mdfe import ( + MDFE_EMIT_TYPE_DEFAULT, + MDFE_EMIT_TYPES, + MDFE_ENVIRONMENT_DEFAULT, + MDFE_ENVIRONMENTS, + MDFE_TRANSMISSION_DEFAULT, + MDFE_TRANSMISSIONS, + MDFE_TRANSP_TYPE, + MDFE_TRANSP_TYPE_DEFAULT, + MDFE_VERSION_DEFAULT, + MDFE_VERSIONS, +) + + +class ResCompany(spec_models.SpecModel): + _name = "res.company" + _inherit = [ + "res.company", + "mdfe.30.emit", + ] + _mdfe_search_keys = ["mdfe30_CNPJ", "mdfe30_xNome", "mdfe_xFant"] + + mdfe_version = fields.Selection( + selection=MDFE_VERSIONS, + string="MDFe Version", + default=MDFE_VERSION_DEFAULT, + ) + + mdfe_environment = fields.Selection( + selection=MDFE_ENVIRONMENTS, + string="MDFe Environment", + default=MDFE_ENVIRONMENT_DEFAULT, + ) + + mdfe_emit_type = fields.Selection( + selection=MDFE_EMIT_TYPES, + string="MDFe Emit Type", + default=MDFE_EMIT_TYPE_DEFAULT, + ) + + mdfe_transp_type = fields.Selection( + selection=MDFE_TRANSP_TYPE, + string="MDFe Transp Type", + default=MDFE_TRANSP_TYPE_DEFAULT, + ) + + mdfe_transmission = fields.Selection( + selection=MDFE_TRANSMISSIONS, + string="MDFe Transmission", + copy=False, + default=MDFE_TRANSMISSION_DEFAULT, + ) + + mdfe30_enderEmit = fields.Many2one( + comodel_name="res.partner", + related="partner_id", + string="Endereço Emitente - MDFe", + ) + + mdfe30_CNPJ = fields.Char(related="partner_id.mdfe30_CNPJ") + + mdfe30_CPF = fields.Char(related="partner_id.mdfe30_CPF") + + mdfe30_xNome = fields.Char(related="partner_id.legal_name") + + mdfe30_xFant = fields.Char(related="partner_id.name") + + mdfe30_IE = fields.Char(related="partner_id.mdfe30_IE") + + mdfe30_fone = fields.Char(related="partner_id.mdfe30_fone") + + mdfe30_choice_emit = fields.Selection( + [("mdfe30_CNPJ", "CNPJ"), ("mdfe30_CPF", "CPF")], + string="MDFe emit CNPJ/CPF", + compute="_compute_mdfe_data", + ) + + def _compute_mdfe_data(self): + for rec in self: + if rec.partner_id.is_company: + rec.mdfe30_choice_emit = "mdfe30_CNPJ" + else: + rec.mdfe30_choice_emit = "mdfe30_CPF" + + def _build_attr(self, node, fields, vals, path, attr): + if attr[0] == "enderEmit" and self.env.context.get("edoc_type") == "in": + # we don't want to try build a related partner_id for enderEmit + # when importing an MDFe + # instead later the emit tag will be imported as the + # document partner_id (dest) and the enderEmit data will be + # injected in the same res.partner record. + return + return super()._build_attr(node, fields, vals, path, attr) + + @api.model + def _prepare_import_dict( + self, values, model=None, parent_dict=None, defaults_model=None + ): + # we disable enderEmit related creation with dry_run=True + context = self._context.copy() + context["dry_run"] = True + values = super(ResCompany, self.with_context(**context))._prepare_import_dict( + values, model, parent_dict, defaults_model + ) + if not values.get("name"): + values["name"] = values.get("mdfe30_xFant") or values.get("mdfe30_xNome") + return values diff --git a/l10n_br_mdfe/models/res_config_settings.py b/l10n_br_mdfe/models/res_config_settings.py new file mode 100644 index 000000000000..c53461962393 --- /dev/null +++ b/l10n_br_mdfe/models/res_config_settings.py @@ -0,0 +1,31 @@ +# Copyright (C) 2023 KMEE - Felipe Zago +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + mdfe_version = fields.Selection( + string="MDF-e Version", + related="company_id.mdfe_version", + readonly=False, + ) + + mdfe_environment = fields.Selection( + string="MDF-e Environment", + related="company_id.mdfe_environment", + readonly=False, + ) + + mdfe_transmission = fields.Selection( + string="MDF-e Transmission", + related="company_id.mdfe_transmission", + readonly=False, + ) + + mdfe_version_name = fields.Char( + string="MDF-e Proc Version", + config_parameter="l10n_br_mdfe.version.name", + ) diff --git a/l10n_br_mdfe/models/res_partner.py b/l10n_br_mdfe/models/res_partner.py new file mode 100644 index 000000000000..b25cb3a462dd --- /dev/null +++ b/l10n_br_mdfe/models/res_partner.py @@ -0,0 +1,340 @@ +# 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, punctuation_rm + +from odoo import api, fields + +from odoo.addons.spec_driven_model.models import spec_models + + +class ResPartner(spec_models.SpecModel): + _name = "res.partner" + _inherit = [ + "res.partner", + "mdfe.30.tendereco", + "mdfe.30.tlocal", + "mdfe.30.tendeemi", + "mdfe.30.dest", + "mdfe.30.tresptec", + "mdfe.30.autxml", + "mdfe.30.veicreboque_prop", + "mdfe.30.veictracao_prop", + "mdfe.30.infresp", + "mdfe.30.infseg", + ] + _mdfe_search_keys = ["mdfe30_CNPJ", "mdfe30_CPF", "mdfe_xNome"] + + @api.model + def _prepare_import_dict( + self, values, model=None, parent_dict=None, defaults_model=None + ): + values = super()._prepare_import_dict( + values, model, parent_dict, defaults_model + ) + if not values.get("name") and values.get("legal_name"): + values["name"] = values["legal_name"] + return values + + mdfe30_CNPJ = fields.Char( + compute="_compute_mdfe_data", + inverse="_inverse_mdfe30_CNPJ", + store=True, + compute_sudo=True, + ) + + mdfe30_idEstrangeiro = fields.Char( + compute="_compute_mdfe_data", + inverse="_inverse_mdfe30_idEstrangeiro", + store=True, + compute_sudo=True, + ) + + mdfe30_CPF = fields.Char( + compute="_compute_mdfe_data", + inverse="_inverse_mdfe30_CPF", + store=True, + compute_sudo=True, + ) + + mdfe30_xLgr = fields.Char( + readonly=True, + compute="_compute_mdfe30_ender", + inverse="_inverse_mdfe30_ender", + compute_sudo=True, + string="Logradouro - MDFe", + ) + + mdfe30_nro = fields.Char( + readonly=True, + compute="_compute_mdfe30_ender", + inverse="_inverse_mdfe30_ender", + compute_sudo=True, + string="Número - MDFe", + ) + + mdfe30_xCpl = fields.Char( + readonly=True, + compute="_compute_mdfe30_ender", + inverse="_inverse_mdfe30_ender", + compute_sudo=True, + string="Complemento - MDFe", + ) + + mdfe30_xBairro = fields.Char( + readonly=True, + compute="_compute_mdfe30_ender", + inverse="_inverse_mdfe30_ender", + compute_sudo=True, + string="Bairro - MDFe", + ) + + mdfe30_cMun = fields.Char( + readonly=True, + compute="_compute_mdfe30_ender", + inverse="_inverse_mdfe30_ender", + compute_sudo=True, + string="Código do Município - MDFe", + ) + + mdfe30_xMun = fields.Char( + readonly=True, + compute="_compute_mdfe30_ender", + inverse="_inverse_mdfe30_ender", + compute_sudo=True, + string="Nome do Município - MDFe", + ) + + mdfe30_UF = fields.Char( + compute="_compute_mdfe30_ender", + inverse="_inverse_mdfe30_ender", + compute_sudo=True, + ) + + mdfe30_choice_emit = fields.Selection( + selection=[("mdfe30_CNPJ", "CNPJ"), ("mdfe30_CPF", "CPF")], + string="CNPJ/CPF do Emitente - MDFe", + compute="_compute_mdfe_data", + compute_sudo=True, + ) + + mdfe30_CEP = fields.Char( + compute="_compute_mdfe_data", + inverse="_inverse_mdfe30_CEP", + string="CEP - MDFe", + compute_sudo=True, + ) + + mdfe30_cPais = fields.Char( + compute="_compute_mdfe30_ender", + inverse="_inverse_mdfe30_ender", + compute_sudo=True, + string="Código do País - MDFe", + ) + + mdfe30_xPais = fields.Char( + compute="_compute_mdfe30_ender", + inverse="_inverse_mdfe30_ender", + compute_sudo=True, + string="Nome do País - MDFe", + ) + + mdfe30_fone = fields.Char( + compute="_compute_mdfe_data", + inverse="_inverse_mdfe30_fone", + string="Telefone MDFe", + compute_sudo=True, + ) + + mdfe30_xNome = fields.Char(related="legal_name", string="Nome - MDFe") + + mdfe30_xFant = fields.Char(related="name", string="Nome Fantasia - MDFe") + + mdfe30_IE = fields.Char( + compute="_compute_mdfe_data", + inverse="_inverse_mdfe30_IE", + compute_sudo=True, + ) + + mdfe30_email = fields.Char(related="email") + + mdfe30_xContato = fields.Char(related="legal_name") + + mdfe30_xSeg = fields.Char(related="legal_name") + + mdfe30_respSeg = fields.Selection(default="1") + + mdfe30_tpProp = fields.Selection(default="0") + + mdfe30_choice_autxml = fields.Selection( + selection=[("mdfe30_CNPJ", "CNPJ"), ("mdfe30_CPF", "CPF")], + string="CNPJ/CPF do Parceiro Autorizado - MDFe", + compute="_compute_mdfe_data", + compute_sudo=True, + ) + + mdfe30_choice_trailer_owner = fields.Selection( + selection=[("mdfe30_CNPJ", "CNPJ"), ("mdfe30_CPF", "CPF")], + string="CNPJ/CPF do Proprietário do Reboque", + compute="_compute_mdfe_data", + compute_sudo=True, + ) + + mdfe30_choice_tcontractor = fields.Selection( + selection=[ + ("mdfe30_CNPJ", "CNPJ"), + ("mdfe30_CPF", "CPF"), + ("mdfe30_idEstrangeiro", "idEstrangeiro"), + ], + string="CNPJ/CPF/Id Estrangeiro do Contratante", + compute="_compute_mdfe_data", + compute_sudo=True, + ) + + mdfe30_choice_contractor = fields.Selection( + selection=[("mdfe30_CNPJ", "CNPJ"), ("mdfe30_CPF", "CPF")], + string="CNPJ/CPF do Contratante", + compute="_compute_mdfe_data", + compute_sudo=True, + ) + + mdfe30_choice_insurer = fields.Selection( + selection=[("mdfe30_CNPJ", "CNPJ"), ("mdfe30_CPF", "CPF")], + string="CNPJ/CPF do Responsável pelo Seguro da Carga", + compute="_compute_mdfe_data", + compute_sudo=True, + ) + + @api.depends("company_type", "inscr_est", "cnpj_cpf", "country_id") + def _compute_mdfe_data(self): + """Set schema data which are not just related fields""" + for rec in self: + cnpj_cpf = punctuation_rm(rec.cnpj_cpf) + if cnpj_cpf: + if rec.country_id.code != "BR": + rec.mdfe30_choice_tcontractor = "mdfe30_idEstrangeiro" + rec.mdfe30_idEstrangeiro = rec.vat + elif rec.is_company: + rec.mdfe30_choice_emit = "mdfe30_CNPJ" + rec.mdfe30_choice_autxml = "mdfe30_CNPJ" + rec.mdfe30_choice_trailer_owner = "mdfe30_CNPJ" + rec.mdfe30_choice_tcontractor = "mdfe30_CNPJ" + rec.mdfe30_choice_contractor = "mdfe30_CNPJ" + rec.mdfe30_choice_insurer = "mdfe30_CNPJ" + rec.mdfe30_CNPJ = cnpj_cpf + rec.mdfe30_CPF = None + else: + rec.mdfe30_choice_emit = "mdfe30_CPF" + rec.mdfe30_choice_autxml = "mdfe30_CPF" + rec.mdfe30_choice_trailer_owner = "mdfe30_CPF" + rec.mdfe30_choice_tcontractor = "mdfe30_CPF" + rec.mdfe30_choice_contractor = "mdfe30_CPF" + rec.mdfe30_choice_insurer = "mdfe30_CPF" + rec.mdfe30_CPF = cnpj_cpf + rec.mdfe30_CNPJ = None + else: + rec.mdfe30_choice_emit = False + rec.mdfe30_choice_autxml = False + rec.mdfe30_choice_trailer_owner = False + rec.mdfe30_choice_tcontractor = False + rec.mdfe30_choice_contractor = False + rec.mdfe30_choice_insurer = False + rec.mdfe30_CNPJ = "" + rec.mdfe30_CPF = "" + + if rec.inscr_est: + rec.mdfe30_IE = punctuation_rm(rec.inscr_est) + else: + rec.mdfe30_IE = None + + rec.mdfe30_CEP = punctuation_rm(rec.zip) + rec.mdfe30_fone = punctuation_rm(rec.phone or "").replace(" ", "") + + def _inverse_mdfe30_CNPJ(self): + for rec in self: + if rec.mdfe30_CNPJ: + rec.is_company = True + rec.mdfe30_choice_emit = "mdfe30_CPF" + rec.mdfe30_choice_autxml = "mdfe30_CPF" + rec.mdfe30_choice_trailer_owner = "mdfe30_CPF" + rec.mdfe30_choice_tcontractor = "mdfe30_CPF" + rec.mdfe30_choice_contractor = "mdfe30_CPF" + rec.mdfe30_choice_insurer = "mdfe30_CPF" + rec.cnpj_cpf = cnpj_cpf.formata(str(rec.mdfe30_CNPJ)) + + def _inverse_mdfe30_CPF(self): + for rec in self: + if rec.mdfe30_CPF: + rec.is_company = False + rec.mdfe30_choice_emit = "mdfe30_CNPJ" + rec.mdfe30_choice_autxml = "mdfe30_CNPJ" + rec.mdfe30_choice_trailer_owner = "mdfe30_CNPJ" + rec.mdfe30_choice_tcontractor = "mdfe30_CNPJ" + rec.mdfe30_choice_contractor = "mdfe30_CNPJ" + rec.mdfe30_choice_insurer = "mdfe30_CNPJ" + rec.cnpj_cpf = cnpj_cpf.formata(str(rec.mdfe30_CPF)) + + def _inverse_mdfe30_idEstrangeiro(self): + for rec in self: + if rec.mdfe30_idEstrangeiro: + rec.mdfe30_choice_tcontractor = "mdfe30_CPF" + rec.vat = rec.mdfe30_idEstrangeiro + + def _inverse_mdfe30_IE(self): + for rec in self: + if rec.mdfe30_IE: + rec.inscr_est = str(rec.mdfe30_IE) + + def _inverse_mdfe30_CEP(self): + for rec in self: + if rec.mdfe30_CEP: + country_code = rec.country_id.code if rec.country_id else "BR" + rec.zip = format_zipcode(rec.mdfe30_CEP, country_code) + + def _inverse_mdfe30_fone(self): + for rec in self: + if rec.mdfe30_fone: + rec.phone = rec.mdfe30_fone + + @api.depends( + "street_name", + "street_number", + "street2", + "district", + "city_id", + "state_id", + "country_id", + ) + def _compute_mdfe30_ender(self): + for rec in self: + rec.mdfe30_xLgr = rec.street_name + rec.mdfe30_nro = rec.street_number + rec.mdfe30_xCpl = rec.street2 + rec.mdfe30_xBairro = rec.district + rec.mdfe30_cMun = rec.city_id.ibge_code + rec.mdfe30_xMun = rec.city_id.name + rec.mdfe30_UF = rec.state_id.code + rec.mdfe30_cPais = rec.country_id.bc_code + rec.mdfe30_xPais = rec.country_id.name + + def _inverse_mdfe30_ender(self): + for rec in self: + if rec.mdfe30_cMun and rec.mdfe30_cPais and rec.mdfe30_UF: + city_id = self.env["res.city"].search( + [("ibge_code", "=", rec.mdfe30_cMun)] + ) + country_id = self.env["res.country"].search( + [("bc_code", "=", rec.mdfe30_cPais)] + ) + state_id = self.env["res.country.state"].search( + [("code", "=", rec.mdfe30_UF), ("country_id", "=", country_id.id)] + ) + + rec.street_name = rec.mdfe30_xLgr + rec.street_number = rec.mdfe30_nro + rec.street2 = rec.mdfe30_xCpl + rec.district = rec.mdfe30_xBairro + rec.city_id = city_id + rec.country_id = country_id + rec.state_id = state_id diff --git a/l10n_br_mdfe/models/seguro_carga.py b/l10n_br_mdfe/models/seguro_carga.py new file mode 100644 index 000000000000..7f599a73c526 --- /dev/null +++ b/l10n_br_mdfe/models/seguro_carga.py @@ -0,0 +1,24 @@ +# 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 MDFeSeguroCarga(spec_models.SpecModel): + _name = "l10n_br_mdfe.seguro.carga" + _inherit = "mdfe.30.seg" + _description = "Informações de Seguro na Carga MDFE" + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + mdfe30_infResp = fields.Many2one(comodel_name="res.partner") + + mdfe30_infSeg = fields.Many2one( + comodel_name="res.partner", + domain=[ + ("is_company", "=", True), + ("cnpj_cpf", "!=", False), + ], + ) diff --git a/l10n_br_mdfe/models/transporte.py b/l10n_br_mdfe/models/transporte.py new file mode 100644 index 000000000000..1fae52cc431c --- /dev/null +++ b/l10n_br_mdfe/models/transporte.py @@ -0,0 +1,70 @@ +# 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 MDFeTranporte(spec_models.SpecModel): + _name = "l10n_br_mdfe.transporte" + _inherit = "mdfe.30.infmdfetransp" + _description = "Transporte MDFe" + + mdfe30_chMDFe = fields.Char(required=True) + + mdfe30_infUnidTransp = fields.One2many(comodel_name="l10n_br_mdfe.transporte.inf") + + mdfe30_peri = fields.One2many(comodel_name="l10n_br_mdfe.transporte.perigoso") + + +class MDFeTranporteInf(spec_models.SpecModel): + _name = "l10n_br_mdfe.transporte.inf" + _inherit = "mdfe.30.tunidadetransp" + _description = "Informações de Transporte MDFe" + + mdfe30_tpUnidTransp = fields.Selection(required=True) + + mdfe30_idUnidTransp = fields.Char(required=True) + + mdfe30_lacUnidTransp = fields.One2many(comodel_name="l10n_br_mdfe.transporte.lacre") + + mdfe30_infUnidCarga = fields.One2many( + comodel_name="l10n_br_mdfe.transporte.carga.inf" + ) + + +class MDFeTranporteCargaInf(spec_models.SpecModel): + _name = "l10n_br_mdfe.transporte.carga.inf" + _inherit = "mdfe.30.tunidcarga" + _description = "Informações de Carga MDFe" + + mdfe30_tpUnidCarga = fields.Selection(required=True) + + mdfe30_idUnidCarga = fields.Char(required=True) + + mdfe30_lacUnidCarga = fields.One2many(comodel_name="l10n_br_mdfe.transporte.lacre") + + +class MDFeTranportePerigoso(spec_models.SpecModel): + _name = "l10n_br_mdfe.transporte.perigoso" + _inherit = [ + "mdfe.30.infmdfetransp_peri", + "mdfe.30.infnfe_peri", + "mdfe.30.infcte_peri", + ] + _description = "Transporte Perigoso MDFe" + + mdfe30_nONU = fields.Char(required=True) + + mdfe30_qTotProd = fields.Char(required=True) + + +class MDFeLacre(spec_models.SpecModel): + _name = "l10n_br_mdfe.transporte.lacre" + _inherit = ["mdfe.30.lacunidtransp", "mdfe.30.lacunidcarga"] + _description = "Lacre MDFe" + + document_id = fields.Many2one(comodel_name="l10n_br_fiscal.document") + + mdfe30_nLacre = fields.Char(required=True, size=20) diff --git a/l10n_br_mdfe/readme/CONFIGURE.rst b/l10n_br_mdfe/readme/CONFIGURE.rst new file mode 100644 index 000000000000..d1d7cde17988 --- /dev/null +++ b/l10n_br_mdfe/readme/CONFIGURE.rst @@ -0,0 +1 @@ +To configure this module you need to set a digital certificate on the company, and also set the company edoc processor. diff --git a/l10n_br_mdfe/readme/CONTRIBUTORS.rst b/l10n_br_mdfe/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000000..72283a46afce --- /dev/null +++ b/l10n_br_mdfe/readme/CONTRIBUTORS.rst @@ -0,0 +1,12 @@ +* `KMEE `_: + + * Felipe Zago Rodrigues + * Ygor Carvalho + +* `ESCODOO `_: + + * Marcel Savegnago + +* `AKRETION `_: + + * Raphaël Valyi diff --git a/l10n_br_mdfe/readme/DESCRIPTION.rst b/l10n_br_mdfe/readme/DESCRIPTION.rst new file mode 100644 index 000000000000..777bb841191b --- /dev/null +++ b/l10n_br_mdfe/readme/DESCRIPTION.rst @@ -0,0 +1,6 @@ +Este módulo permite a emissão de MDF-e. + +Mais especificamente ele: + * mapea os campos de MDF-e do módulo ``l10n_br_mdfe_spec`` com os campos Odoo. + * usa a logica do módulo ``spec_driven_model`` para realizar esse mapeamento de forma dinâmica, em especial ele usa o sistema de modelos com várias camadas, ou ``StackedModel``, com os modelos ``l10n_br_fiscal.document`` e ``l10n_br_fiscal.document.related`` que tem varios niveis hierarquicos de elementos XML que estão sendo denormalizados dentro desses modelos Odoo  + * tem wizards para implementar a comunicação SOAP de MDF-e com a SEFAZ (Autorização, Cancelamento, Encerramento...) diff --git a/l10n_br_mdfe/readme/ROADMAP.rst b/l10n_br_mdfe/readme/ROADMAP.rst new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/l10n_br_mdfe/readme/USAGE.rst b/l10n_br_mdfe/readme/USAGE.rst new file mode 100644 index 000000000000..68616e197ec5 --- /dev/null +++ b/l10n_br_mdfe/readme/USAGE.rst @@ -0,0 +1,26 @@ +Para utilizar o módulo `l10n_br_mdfe` em conjunto com o módulo `l10n_br_account`, é necessário configurar uma linha de operação fiscal que não adicione valor ao montante do documento, uma vez que o MDF-e (Manifesto Eletrônico de Documentos Fiscais) não possui valor financeiro. + +**Passo a Passo:** + +1. **Criar uma Fatura:** + - Defina o tipo de documento como **58 (MDFe)**. + +2. **Configurar o Parceiro da Fatura:** + - Configure o parceiro para ser o mesmo da empresa emissora do MDF-e. + +3. **Adicionar uma Linha na Aba Produtos:** + - Adicione uma linha de fatura com a operação fiscal previamente configurada. + - **Não recomedamos que informe um produto** ou utilize um produto que **não possua CFOP** (Código Fiscal de Operações e Prestações), ou que o CFOP esteja configurado para **não gerar valor financeiro** e esteja atento a dados como impostos e afins. + +4. **Acesse os detalhes fiscais da fatura e informe os demais dados necessário para emissão do MDF-e:** + - Preencha os campos obrigatórios para emissão do MDF-e, como UF de descarregamento, município de descarregamento, etc. + +5. **Valide o MDF-e, verifique os dados do XML e envie para a SEFAZ:** + - Após preencher todos os dados necessários, valide o MDF-e e envie para a SEFAZ. + +**Considerações Adicionais** + +- **Operação Fiscal:** Certifique-se de que a operação fiscal esteja parametrizada corretamente para evitar a adição de valores financeiros ao documento. +- **CFOP:** No caso de utilização de um produto cadastrado e que carregue o CFOP para a linha da fatura, verifique a configuração do CFOP para garantir que ele não gere impacto financeiro no montante da fatura. + +Seguindo esses passos, o módulo `l10n_br_mdfe` funcionará corretamente em conjunto com o `l10n_br_account`, permitindo a emissão de MDF-e sem valores financeiros associados. diff --git a/l10n_br_mdfe/security/ir.model.access.csv b/l10n_br_mdfe/security/ir.model.access.csv new file mode 100644 index 000000000000..3050228f54ce --- /dev/null +++ b/l10n_br_mdfe/security/ir.model.access.csv @@ -0,0 +1,38 @@ +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" +l10n_br_mdfe_modal_aereo_user,l10n_br_mdfe_modal_aereo_user,model_l10n_br_mdfe_modal_aereo,base.group_user,1,1,1,1 + +l10n_br_mdfe_modal_rodoviario_user,l10n_br_mdfe_modal_rodoviario_user,model_l10n_br_mdfe_modal_rodoviario,base.group_user,1,1,1,1 +l10n_br_mdfe_modal_rodoviario_ciot_user,l10n_br_mdfe_modal_rodoviario_ciot_user,model_l10n_br_mdfe_modal_rodoviario_ciot,base.group_user,1,1,1,1 +l10n_br_mdfe_modal_rodoviario_vale_pedagio_dispositivo_user,l10n_br_mdfe_modal_rodoviario_vale_pedagio_dispositivo_user,model_l10n_br_mdfe_modal_rodoviario_vale_pedagio_dispositivo,base.group_user,1,1,1,1 +l10n_br_mdfe_modal_rodoviario_pagamento_user,l10n_br_mdfe_modal_rodoviario_pagamento_user,model_l10n_br_mdfe_modal_rodoviario_pagamento,base.group_user,1,1,1,1 +l10n_br_mdfe_modal_rodoviario_pagamento_frete_user,l10n_br_mdfe_modal_rodoviario_pagamento_frete_user,model_l10n_br_mdfe_modal_rodoviario_pagamento_frete,base.group_user,1,1,1,1 +l10n_br_mdfe_modal_rodoviario_pagamento_prazo_user,l10n_br_mdfe_modal_rodoviario_pagamento_prazo_user,model_l10n_br_mdfe_modal_rodoviario_pagamento_prazo,base.group_user,1,1,1,1 +l10n_br_mdfe_modal_rodoviario_veiculo_condutor_user,l10n_br_mdfe_modal_rodoviario_veiculo_condutor_user,model_l10n_br_mdfe_modal_rodoviario_veiculo_condutor,base.group_user,1,1,1,1 +l10n_br_mdfe_modal_rodoviario_reboque_user,l10n_br_mdfe_modal_rodoviario_reboque_user,model_l10n_br_mdfe_modal_rodoviario_reboque,base.group_user,1,1,1,1 +l10n_br_mdfe.modal.rodoviario.lacre_user,l10n_br_mdfe.modal.rodoviario.lacre_user,model_l10n_br_mdfe_modal_rodoviario_lacre,base.group_user,1,1,1,1 +l10n_br_mdfe.modal.rodoviario.contratante_user,l10n_br_mdfe.modal.rodoviario.contratante_user,model_l10n_br_mdfe_modal_rodoviario_contratante,base.group_user,1,1,1,1 + +l10n_br_mdfe_modal_ferroviario_user,l10n_br_mdfe_modal_ferroviario_user,model_l10n_br_mdfe_modal_ferroviario,base.group_user,1,1,1,1 +l10n_br_mdfe_modal_ferroviario_user_vagao,l10n_br_mdfe_modal_ferroviario_user_vagao,model_l10n_br_mdfe_modal_ferroviario_vagao,base.group_user,1,1,1,1 + +l10n_br_mdfe_modal_aquaviario_user,l10n_br_mdfe_modal_aquaviario_user,model_l10n_br_mdfe_modal_aquaviario,base.group_user,1,1,1,1 +l10n_br_mdfe_modal_aquaviario_carregamento_user,l10n_br_mdfe_modal_aquaviario_carregamento_user,model_l10n_br_mdfe_modal_aquaviario_carregamento,base.group_user,1,1,1,1 +l10n_br_mdfe_modal_aquaviario_descarregamento_user,l10n_br_mdfe_modal_aquaviario_descarregamento_user,model_l10n_br_mdfe_modal_aquaviario_descarregamento,base.group_user,1,1,1,1 +l10n_br_mdfe_modal_aquaviario_comboio_user,l10n_br_mdfe_modal_aquaviario_comboio_user,model_l10n_br_mdfe_modal_aquaviario_comboio,base.group_user,1,1,1,1 +l10n_br_mdfe_modal_aquaviario_carga_vazia_user,l10n_br_mdfe_modal_aquaviario_carga_vazia_user,model_l10n_br_mdfe_modal_aquaviario_carga_vazia,base.group_user,1,1,1,1 +l10n_br_mdfe_modal_aquaviario_transporte_vazio_user,l10n_br_mdfe_modal_aquaviario_transporte_vazio_user,model_l10n_br_mdfe_modal_aquaviario_transporte_vazio,base.group_user,1,1,1,1 + +l10n_br_mdfe_municipio_descarga_user,l10n_br_mdfe_municipio_descarga_user,model_l10n_br_mdfe_municipio_descarga,base.group_user,1,1,1,1 + +l10n_br_mdfe_transporte_user,l10n_br_mdfe_transporte_user,model_l10n_br_mdfe_transporte,base.group_user,1,1,1,1 +l10n_br_mdfe_transporte_inf_user,l10n_br_mdfe_transporte_inf_user,model_l10n_br_mdfe_transporte_inf,base.group_user,1,1,1,1 +l10n_br_mdfe_transporte_carga_inf_user,l10n_br_mdfe_transporte_carga_inf_user,model_l10n_br_mdfe_transporte_carga_inf,base.group_user,1,1,1,1 +l10n_br_mdfe_transporte_lacre_user,l10n_br_mdfe_transporte_lacre_user,model_l10n_br_mdfe_transporte_lacre,base.group_user,1,1,1,1 +l10n_br_mdfe_transporte_perigoso_user,l10n_br_mdfe_transporte_perigoso_user,model_l10n_br_mdfe_transporte_perigoso,base.group_user,1,1,1,1 + +l10n_br_mdfe_seguro_carga_user,l10n_br_mdfe_seguro_carga_user,model_l10n_br_mdfe_seguro_carga,base.group_user,1,1,1,1 + +l10n_br_fiscal_document_supplement_user,l10n_br_fiscal_document_supplement_user,model_l10n_br_fiscal_document_supplement,base.group_user,1,1,1,1 + +l10n_br_mdfe_product_lotacao_user,l10n_br_mdfe_product_lotacao_user,model_l10n_br_mdfe_product_lotacao,base.group_user,1,1,1,1 +l10n_br_mdfe_product_lotacao_local_user,l10n_br_mdfe_product_lotacao_local_user,model_l10n_br_mdfe_product_lotacao_local,base.group_user,1,1,1,1 diff --git a/l10n_br_mdfe/static/description/icon.png b/l10n_br_mdfe/static/description/icon.png new file mode 100644 index 000000000000..3a0328b516c4 Binary files /dev/null and b/l10n_br_mdfe/static/description/icon.png differ diff --git a/l10n_br_mdfe/static/description/index.html b/l10n_br_mdfe/static/description/index.html new file mode 100644 index 000000000000..3ee32380a1ed --- /dev/null +++ b/l10n_br_mdfe/static/description/index.html @@ -0,0 +1,483 @@ + + + + + +MDFe + + + +
+

MDFe

+ + +

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

+

Este módulo permite a emissão de MDF-e.

+
+
Mais especificamente ele:
+
    +
  • mapea os campos de MDF-e do módulo l10n_br_mdfe_spec com os campos Odoo.
  • +
  • usa a logica do módulo spec_driven_model para realizar esse mapeamento de forma dinâmica, em especial ele usa o sistema de modelos com várias camadas, ou StackedModel, com os modelos l10n_br_fiscal.document e l10n_br_fiscal.document.related que tem varios niveis hierarquicos de elementos XML que estão sendo denormalizados dentro desses modelos Odoo
  • +
  • tem wizards para implementar a comunicação SOAP de MDF-e com a SEFAZ (Autorização, Cancelamento, Encerramento…)
  • +
+
+
+
+

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

+

To configure this module you need to set a digital certificate on the company, and also set the company edoc processor.

+
+
+

Usage

+

Para utilizar o módulo l10n_br_mdfe em conjunto com o módulo l10n_br_account, é necessário configurar uma linha de operação fiscal que não adicione valor ao montante do documento, uma vez que o MDF-e (Manifesto Eletrônico de Documentos Fiscais) não possui valor financeiro.

+

Passo a Passo:

+
    +
  1. Criar uma Fatura: +- Defina o tipo de documento como 58 (MDFe).
  2. +
  3. Configurar o Parceiro da Fatura: +- Configure o parceiro para ser o mesmo da empresa emissora do MDF-e.
  4. +
  5. Adicionar uma Linha na Aba Produtos: +- Adicione uma linha de fatura com a operação fiscal previamente configurada. +- Não recomedamos que informe um produto ou utilize um produto que não possua CFOP (Código Fiscal de Operações e Prestações), ou que o CFOP esteja configurado para não gerar valor financeiro e esteja atento a dados como impostos e afins.
  6. +
  7. Acesse os detalhes fiscais da fatura e informe os demais dados necessário para emissão do MDF-e: +- Preencha os campos obrigatórios para emissão do MDF-e, como UF de descarregamento, município de descarregamento, etc.
  8. +
  9. Valide o MDF-e, verifique os dados do XML e envie para a SEFAZ: +- Após preencher todos os dados necessários, valide o MDF-e e envie para a SEFAZ.
  10. +
+

Considerações Adicionais

+
    +
  • Operação Fiscal: Certifique-se de que a operação fiscal esteja parametrizada corretamente para evitar a adição de valores financeiros ao documento.
  • +
  • CFOP: No caso de utilização de um produto cadastrado e que carregue o CFOP para a linha da fatura, verifique a configuração do CFOP para garantir que ele não gere impacto financeiro no montante da fatura.
  • +
+

Seguindo esses passos, o módulo l10n_br_mdfe funcionará corretamente em conjunto com o l10n_br_account, permitindo a emissão de MDF-e sem valores financeiros associados.

+
+
+

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
  • +
  • Escodoo
  • +
+
+
+

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.

+

Current maintainers:

+

mileo marcelsavegnago

+

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_mdfe/tests/__init__.py b/l10n_br_mdfe/tests/__init__.py new file mode 100644 index 000000000000..bfd6ae80ae57 --- /dev/null +++ b/l10n_br_mdfe/tests/__init__.py @@ -0,0 +1,6 @@ +from . import test_mdfe_serialize +from . import test_mdfe_serialize_lc +from . import test_mdfe_serialize_sn +from . import test_mdfe_import +from . import test_mdfe_structure +from . import test_mdfe_res_partner diff --git a/l10n_br_mdfe/tests/mdfe/v3_00/leiauteMDFe/MDFe35230905472475000102580200000602011208018449.xml b/l10n_br_mdfe/tests/mdfe/v3_00/leiauteMDFe/MDFe35230905472475000102580200000602011208018449.xml new file mode 100644 index 000000000000..7d18071d480f --- /dev/null +++ b/l10n_br_mdfe/tests/mdfe/v3_00/leiauteMDFe/MDFe35230905472475000102580200000602011208018449.xml @@ -0,0 +1,93 @@ + + + + + 35 + 2 + 2 + 1 + 58 + 1 + 2 + 20801844 + 4 + 2020-01-01T12:00:00+01:00 + 1 + 0 + Odoo Brasil OCA v14 + AC + AC + + 1200013 + Acrelândia + + + PB + + + + 81583054000129 + 078016350838 + Empresa Lucro Presumido Ltda + Empresa Lucro Presumido + + Avenida Paulista + 1 + Bela Vista + 3550308 + São Paulo + 01311000 + SP + 551199999999 + info@suaempresa.com.br + + + + + + TES + 2020-01-01T12:00:00+01:00 + TES + TES + 2 + + + 500.000 + 1.000 + 123 + 123 + 123 + 123 + 1 + + + 500.000 + 1.000 + 321 + 321 + 321 + 321 + 1 + + + + + + 1200013 + Acrelândia + + 41190806117473000150550010000586251016759484 + + + + + 1 + 33.19 + 01 + 10.0000 + + + Documento emitido por: Marc Demo + + + diff --git a/l10n_br_mdfe/tests/mdfe/v3_00/leiauteMDFe/MDFe35230905472475000102580200000602071611554500.xml b/l10n_br_mdfe/tests/mdfe/v3_00/leiauteMDFe/MDFe35230905472475000102580200000602071611554500.xml new file mode 100644 index 000000000000..3e25dd05ecb2 --- /dev/null +++ b/l10n_br_mdfe/tests/mdfe/v3_00/leiauteMDFe/MDFe35230905472475000102580200000602071611554500.xml @@ -0,0 +1,127 @@ + + + + + 35 + 2 + 2 + 58 + 1 + 3 + 20801844 + 1 + 2020-01-01T12:00:00+01:00 + 1 + 0 + Odoo Brasil OCA v14 + AC + AC + + 1200013 + Acrelândia + + + PB + + + + 81583054000129 + 078016350838 + Empresa Lucro Presumido Ltda + Empresa Lucro Presumido + + Avenida Paulista + 1 + Bela Vista + 3550308 + São Paulo + 01311000 + SP + 551199999999 + info@suaempresa.com.br + + + + + + 12345678 + + 123456789101 + 99999999999 + + + + 99999999999999 + 99999999999999 + 1234 + 5.00 + 01 + + 02 + + + Intel do Brasil + 86144802000190 + + 01 + 5.00 + + 5.00 + 0 + + 99999999999999 + + + + + 1 + AAA1233 + 42423325472 + 7500 + 42500 + 300 + + Teste + 99999999999 + + + Teste2 + 99999999999 + + 03 + 00 + AC + + + 2 + AAA4321 + 11557770179 + 7200 + 42500 + 300 + 00 + AC + + 12345678 + + + + + 1200013 + Acrelândia + + 41190806117473000150550010000586251016759484 + + + + + 1 + 33.19 + 01 + 10.0000 + + + Documento emitido por: Marc Demo + + + diff --git a/l10n_br_mdfe/tests/mdfe/v3_00/leiauteMDFe/MDFe35230905472475000102580200000602081550195716.xml b/l10n_br_mdfe/tests/mdfe/v3_00/leiauteMDFe/MDFe35230905472475000102580200000602081550195716.xml new file mode 100644 index 000000000000..87b6a1ae6e94 --- /dev/null +++ b/l10n_br_mdfe/tests/mdfe/v3_00/leiauteMDFe/MDFe35230905472475000102580200000602081550195716.xml @@ -0,0 +1,74 @@ + + + + + 35 + 2 + 2 + 1 + 58 + 1 + 4 + 20801844 + 2 + 2020-01-01T12:00:00+01:00 + 1 + 0 + Odoo Brasil OCA v14 + AC + AC + + 1200013 + Acrelândia + + + PB + + + + 59594315000157 + 755338250133 + TESTE - Simples Nacional + TESTE - Simples Nacional + + Rua Paulo Dias + 586 + Vila Santa Luzia + 3501152 + Alumínio + 18125000 + SP + 2130109965 + info@simplesnacional.example.com + + + + + TEST + TEST + 123456789 + OACI + OACI + 2020-01-01 + + + + + 1200013 + Acrelândia + + 41190806117473000150550010000586251016759484 + + + + + 1 + 33.19 + 01 + 10.0000 + + + Documento emitido por: Marc Demo + + + diff --git a/l10n_br_mdfe/tests/mdfe/v3_00/leiauteMDFe/MDFe35231005472475000102580200000602161434590525.xml b/l10n_br_mdfe/tests/mdfe/v3_00/leiauteMDFe/MDFe35231005472475000102580200000602161434590525.xml new file mode 100644 index 000000000000..29602375b63b --- /dev/null +++ b/l10n_br_mdfe/tests/mdfe/v3_00/leiauteMDFe/MDFe35231005472475000102580200000602161434590525.xml @@ -0,0 +1,91 @@ + + + + + 35 + 2 + 2 + 1 + 58 + 1 + 5 + 20801844 + 3 + 2020-01-01T12:00:00+01:00 + 1 + 0 + Odoo Brasil OCA v14 + AC + AC + + 1200013 + Acrelândia + + + PB + + + + 59594315000157 + 755338250133 + TESTE - Simples Nacional + TESTE - Simples Nacional + + Rua Paulo Dias + 586 + Vila Santa Luzia + 3501152 + Alumínio + 18125000 + SP + 2130109965 + info@simplesnacional.example.com + + + + + 1234567899 + 01 + 123456 + teste + 123456 + BRADR + BRAFU + + BRADR + ANGRA DOS REIS + + + BRANT + ANTONINA + + + BRAFU + AFUÁ + + + BRBZC + BUZIOS + + + + + + 1200013 + Acrelândia + + 41190806117473000150550010000586251016759484 + + + + + 1 + 33.19 + 01 + 10.0000 + + + Documento emitido por: Marc Demo + + + diff --git a/l10n_br_mdfe/tests/mdfe/v3_00/leiauteMDFe/MDFe41190876676436000167580010000500001000437558.xml b/l10n_br_mdfe/tests/mdfe/v3_00/leiauteMDFe/MDFe41190876676436000167580010000500001000437558.xml new file mode 100644 index 000000000000..f338a8115188 --- /dev/null +++ b/l10n_br_mdfe/tests/mdfe/v3_00/leiauteMDFe/MDFe41190876676436000167580010000500001000437558.xml @@ -0,0 +1,94 @@ + + + + + 41 + 2 + 2 + 58 + 1 + 50000 + 00043755 + 8 + 1 + 2019-08-16T15:00:00-02:00 + 1 + 0 + 71411 + PR + SC + + 4101804 + ARAUCARIA + + + + 81583054000129 + 078016350838 + Empresa Lucro Presumido Ltda + Empresa Lucro Presumido + + Avenida Paulista + 1 + Bela Vista + 3550308 + São Paulo + 01311000 + SP + 551199999999 + info@suaempresa.com.br + + + + + + 12345678 + + + 1 + AAA4444 + 15000 + + MAURICIO ROBERTO MOLLER + 03720677940 + + 03 + 00 + PR + + + REE4343 + 7500 + 42500 + 02 + AC + + + RER7878 + 7500 + 42500 + 02 + RS + + + + + + 4209102 + JOINVILLE + + 41190806117473000150550010000586251016759484 + + + + + 1 + 33.19 + 01 + 10.0000 + + + Distancia: 12 KM Passar por: BR105 + + + diff --git a/l10n_br_mdfe/tests/test_mdfe_document.py b/l10n_br_mdfe/tests/test_mdfe_document.py new file mode 100644 index 000000000000..0cdcd6e7de15 --- /dev/null +++ b/l10n_br_mdfe/tests/test_mdfe_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 MDFeAdapter + +from odoo.exceptions import UserError +from odoo.tests import SavepointCase + + +class MDFeDocumentTest(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.mdfe_document_type_id = cls.env.ref("l10n_br_fiscal.document_58") + cls.sn_company_id = cls.env.ref("l10n_br_base.empresa_simples_nacional") + cls.sn_company_id.processador_edoc = "erpbrasil.edoc" + cls.mdfe_id = FiscalDocument.create( + { + "document_type_id": cls.mdfe_document_type_id.id, + "company_id": cls.sn_company_id.id, + "document_number": "70000", + "document_serie": "30", + "document_data": datetime.now(), + } + ) + + def test_mdfe_compute_fields(self): + self.mdfe_id.fiscal_additional_data = "TEST FISCAL ADDITIONAL DATA" + self.mdfe_id.customer_additional_data = "TEST CUSTOMER ADDITIONAL DATA" + + self.assertTrue(self.mdfe_id.mdfe30_infAdFisco) + self.assertTrue(self.mdfe_id.mdfe30_infCpl) + + def test_mdfe_inverse_fields(self): + self.mdfe_id.mdfe30_UFIni = self.acre_state.code + self.mdfe_id.mdfe30_UFFim = self.acre_state.code + self.assertEqual(self.mdfe_id.mdfe_initial_state_id, self.acre_state) + self.assertEqual(self.mdfe_id.mdfe_final_state_id, self.acre_state) + + self.mdfe_id.mdfe30_UF = self.acre_state.ibge_code + self.assertEqual(self.mdfe_id.company_id.partner_id.state_id, self.acre_state) + + self.mdfe_id.mdfe30_infMunCarrega = [ + ( + 0, + 0, + { + "mdfe30_cMunCarrega": "1200013", + "mdfe30_xMunCarrega": "Acrelândia", + }, + ) + ] + self.assertIn( + self.env.ref("l10n_br_base.city_1200013"), + self.mdfe_id.mdfe_loading_city_ids, + ) + + def test_mdfe_processor(self): + processor = self.mdfe_id._edoc_processor() + self.assertTrue(isinstance(processor, MDFeAdapter)) + + self.mdfe_id.document_type_id = False + processor = self.mdfe_id._edoc_processor() + self.assertFalse(isinstance(processor, MDFeAdapter)) + + self.mdfe_id.document_type_id = self.mdfe_document_type_id + + self.mdfe_id.company_id.certificate_nfe_id = False + processor = self.mdfe_id._edoc_processor() + self.assertTrue(isinstance(processor, MDFeAdapter)) + + self.mdfe_id.company_id.certificate_ecnpj_id = False + with self.assertRaises(UserError): + processor = self.mdfe_id._edoc_processor() + + def test_generate_key(self): + self.mdfe_id._generate_key() + self.assertTrue(self.mdfe_id.document_key) + self.assertTrue(self.mdfe_id.key_random_code) + self.assertTrue(self.mdfe_id.key_check_digit) diff --git a/l10n_br_mdfe/tests/test_mdfe_import.py b/l10n_br_mdfe/tests/test_mdfe_import.py new file mode 100644 index 000000000000..e69fe3fe16aa --- /dev/null +++ b/l10n_br_mdfe/tests/test_mdfe_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.mdfe.bindings.v3_0.mdfe_v3_00 import Tmdfe + +from odoo.models import NewId +from odoo.tests import SavepointCase + +_logger = logging.getLogger(__name__) + + +class MDFeImportTest(SavepointCase): + def test_import_in_mdfe_dry_run(self): + res_items = ( + "mdfe", + "samples", + "v3_0", + "50170876063965000276580010000011311421039568-mdfe.xml", + ) + + resource_path = "/".join(res_items) + mdfe_stream = pkg_resources.resource_stream(nfelib.__name__, resource_path) + binding = Tmdfe.from_xml(mdfe_stream.read().decode()) + + mdfe = ( + self.env["mdfe.30.tmdfe_infmdfe"] + .with_context(tracking_disable=True, edoc_type="in") + .build_from_binding("mdfe", "30", binding.infMDFe, dry_run=True) + ) + assert isinstance(mdfe.id, NewId) + self._check_mdfe(mdfe) + + def test_import_in_mdfe(self): + res_items = ( + "mdfe", + "samples", + "v3_0", + "50170876063965000276580010000011311421039568-mdfe.xml", + ) + resource_path = "/".join(res_items) + mdfe_stream = pkg_resources.resource_stream(nfelib.__name__, resource_path) + binding = Tmdfe.from_xml(mdfe_stream.read().decode()) + mdfe = ( + self.env["mdfe.30.tmdfe_infmdfe"] + .with_context(tracking_disable=True, edoc_type="in") + .build_from_binding("mdfe", "30", binding.infMDFe, dry_run=False) + ) + + assert isinstance(mdfe.id, int) + self._check_mdfe(mdfe) + + def _check_mdfe(self, mdfe): + self.assertEqual(type(mdfe)._name, "l10n_br_fiscal.document") + + # ide + self.assertEqual(mdfe.mdfe30_cMDF, "42103956") + self.assertEqual(mdfe.mdfe30_infMunCarrega[0].mdfe30_xMunCarrega, "IVINHEMA") + self.assertEqual(mdfe.mdfe30_UFIni, "MS") + self.assertEqual(mdfe.mdfe30_UFFim, "PR") + + # # modal + # self.assertEqual(mdfe.mdfe30_placa, "XXX1228") + # self.assertEqual(mdfe.mdfe30_tara, "0") + # self.assertEqual(mdfe.mdfe30_condutor[0].mdfe30_xNome, "TESTE") + # self.assertEqual(len(mdfe.mdfe30_veicReboque), 0) + + self.assertEqual(mdfe.mdfe30_verProc, "UNICO V8.0") + + def test_import_out_mdfe(self): + "(can be useful after an ERP migration)" diff --git a/l10n_br_mdfe/tests/test_mdfe_res_partner.py b/l10n_br_mdfe/tests/test_mdfe_res_partner.py new file mode 100644 index 000000000000..5fc809cdaa63 --- /dev/null +++ b/l10n_br_mdfe/tests/test_mdfe_res_partner.py @@ -0,0 +1,68 @@ +# 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 TestMDFeResPartner(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.mdfe30_choice_tcontractor, "mdfe30_idEstrangeiro" + ) + self.assertEqual(self.partner_id.mdfe30_idEstrangeiro, self.partner_id.cnpj_cpf) + + def test_inverse_fields(self): + self.partner_id.mdfe30_idEstrangeiro = "999999999999" + self.assertEqual(self.partner_id.vat, self.partner_id.mdfe30_idEstrangeiro) + + self.partner_id.mdfe30_CNPJ = "97414612000162" + self.assertEqual( + self.partner_id.cnpj_cpf, cnpj_cpf.formata(self.partner_id.mdfe30_CNPJ) + ) + + self.partner_id.mdfe30_CPF = "48737433032" + self.assertEqual( + self.partner_id.cnpj_cpf, cnpj_cpf.formata(self.partner_id.mdfe30_CPF) + ) + + self.partner_id.mdfe30_IE = "630514648079" + self.assertEqual(self.partner_id.inscr_est, self.partner_id.mdfe30_IE) + + self.partner_id.mdfe30_CEP = "04324240" + self.assertEqual( + self.partner_id.zip, format_zipcode(self.partner_id.mdfe30_CEP) + ) + + self.partner_id.mdfe30_fone = "(99) 9999-9999" + self.assertEqual(self.partner_id.phone, self.partner_id.mdfe30_fone) + + self.partner_id.mdfe30_cPais = "1058" + self.partner_id.mdfe30_UF = "SP" + self.partner_id.mdfe30_cMun = "3550308" + self.assertEqual( + self.partner_id.country_id.bc_code, self.partner_id.mdfe30_cPais + ) + self.assertEqual(self.partner_id.state_id.code, self.partner_id.mdfe30_UF) + self.assertEqual(self.partner_id.city_id.ibge_code, self.partner_id.mdfe30_cMun) + + self.partner_id.mdfe30_xLgr = "TESTE" + self.assertEqual(self.partner_id.street_name, self.partner_id.mdfe30_xLgr) + + self.partner_id.mdfe30_nro = "999" + self.assertEqual(self.partner_id.street_number, self.partner_id.mdfe30_nro) + + self.partner_id.mdfe30_xCpl = "TESTE" + self.assertEqual(self.partner_id.street2, self.partner_id.mdfe30_xCpl) + + self.partner_id.mdfe30_xBairro = "TESTE" + self.assertEqual(self.partner_id.district, self.partner_id.mdfe30_xBairro) diff --git a/l10n_br_mdfe/tests/test_mdfe_serialize.py b/l10n_br_mdfe/tests/test_mdfe_serialize.py new file mode 100644 index 000000000000..ccd99ca5285f --- /dev/null +++ b/l10n_br_mdfe/tests/test_mdfe_serialize.py @@ -0,0 +1,236 @@ +# @ 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_mdfe + +_logger = logging.getLogger(__name__) + + +class TestMDFeSerialize(TransactionCase): + def setUp(self, mdfe_list): + super().setUp() + self.mdfe_list = mdfe_list + for mdfe_data in self.mdfe_list: + mdfe = self.env.ref(mdfe_data["record_ref"]) + mdfe_data["mdfe"] = mdfe + self.prepare_test_mdfe(mdfe) + + def prepare_test_mdfe(self, mdfe): + """ + Performs actions necessary to prepare an MDFe of the demo data to + perform the tests + """ + if mdfe.state != "em_digitacao": # 2nd test run + mdfe.action_document_back2draft() + + mdfe._compute_amount() + mdfe.action_document_confirm() + mdfe.document_date = datetime.strptime( + "2020-01-01T11:00:00", "%Y-%m-%dT%H:%M:%S" + ) + mdfe.mdfe30_cMDF = "20801844" + + if mdfe.mdfe_modal == "1": + self.prepare_modal_rodoviario_data(mdfe) + elif mdfe.mdfe_modal == "2": + self.prepare_modal_aereo_data(mdfe) + elif mdfe.mdfe_modal == "3": + self.prepare_modal_aquaviario_data(mdfe) + elif mdfe.mdfe_modal == "4": + self.prepare_modal_ferroviario_data(mdfe) + + mdfe._document_export() + + def prepare_modal_rodoviario_data(self, mdfe): + mdfe.mdfe30_codAgPorto = "12345678" + + # infANTT + mdfe.mdfe30_RNTRC = "12345678" + mdfe.mdfe30_categCombVeic = "02" + mdfe.mdfe30_infCIOT = [ + ( + 0, + 0, + { + "is_company": False, + "mdfe30_CIOT": "123456789101", + "mdfe30_CPF": "99999999999", + }, + ), + ] + mdfe.mdfe30_disp = [ + ( + 0, + 0, + { + "mdfe30_CNPJForn": "99999999999999", + "mdfe30_CNPJPg": "99999999999999", + "mdfe30_nCompra": "1234", + "mdfe30_vValePed": 5, + "mdfe30_tpValePed": "01", + }, + ), + ] + mdfe.mdfe30_infPag = [ + ( + 0, + 0, + { + "partner_id": self.env.ref("l10n_br_base.res_partner_intel").id, + "mdfe30_vContrato": 5, + "mdfe30_indPag": "0", + "payment_type": "pix", + "mdfe30_PIX": "99999999999999", + "mdfe30_comp": [ + ( + 0, + 0, + { + "mdfe30_tpComp": "01", + "mdfe30_vComp": 5, + }, + ) + ], + }, + ), + ] + + # veicTracao + mdfe.mdfe30_cInt = "1" + mdfe.mdfe30_RENAVAM = "42423325472" + mdfe.mdfe30_placa = "AAA1233" + mdfe.mdfe30_tpTransp = False + mdfe.mdfe30_tara = 7500 + mdfe.mdfe30_capKG = 42500 + mdfe.mdfe30_capM3 = 300 + mdfe.mdfe30_tpRod = "03" + mdfe.mdfe30_tpCar = "00" + mdfe.rodo_vehicle_state_id = self.env.ref("base.state_br_ac").id + mdfe.mdfe30_condutor = [ + ( + 0, + 0, + { + "mdfe30_xNome": "Teste", + "mdfe30_CPF": "99999999999", + }, + ), + ( + 0, + 0, + { + "mdfe30_xNome": "Teste2", + "mdfe30_CPF": "99999999999", + }, + ), + ] + + # veicReboque + mdfe.mdfe30_veicReboque = [ + ( + 0, + 0, + { + "mdfe30_cInt": "2", + "mdfe30_placa": "AAA4321", + "mdfe30_RENAVAM": "11557770179", + "mdfe30_tara": 7200, + "mdfe30_capKG": 42500, + "mdfe30_capM3": 300, + "mdfe30_tpCar": "00", + "mdfe30_UF": "AC", + }, + ) + ] + + def prepare_modal_aereo_data(self, mdfe): + mdfe.mdfe30_nac = "TEST" + mdfe.mdfe30_matr = "TEST" + mdfe.mdfe30_nVoo = "123456789" + mdfe.mdfe30_cAerEmb = "OACI" + mdfe.mdfe30_cAerDes = "OACI" + mdfe.mdfe30_dVoo = datetime.strptime("2020-01-01", "%Y-%m-%d") + + def prepare_modal_aquaviario_data(self, mdfe): + mdfe.mdfe30_irin = "1234567899" + mdfe.mdfe30_tpEmb = "01" + mdfe.mdfe30_cEmbar = "123456" + mdfe.mdfe30_xEmbar = "teste" + mdfe.mdfe30_nViag = "123456" + mdfe.mdfe30_cPrtEmb = "BRADR" + mdfe.mdfe30_cPrtDest = "BRAFU" + mdfe.mdfe30_infTermCarreg = [ + (0, 0, {"loading_harbor": "BRADR"}), + (0, 0, {"loading_harbor": "BRANT"}), + ] + mdfe.mdfe30_infTermDescarreg = [ + (0, 0, {"unloading_harbor": "BRAFU"}), + (0, 0, {"unloading_harbor": "BRBZC"}), + ] + + def prepare_modal_ferroviario_data(self, mdfe): + mdfe.mdfe30_dhTrem = datetime.strptime( + "2020-01-01T11:00:00", "%Y-%m-%dT%H:%M:%S" + ) + mdfe.mdfe30_xPref = "TES" + mdfe.mdfe30_xOri = "TES" + mdfe.mdfe30_xDest = "TES" + mdfe.mdfe30_qVag = 2 + mdfe.mdfe30_vag = [ + ( + 0, + 0, + { + "mdfe30_pesoBC": 500, + "mdfe30_pesoR": 1, + "mdfe30_tpVag": 123, + "mdfe30_serie": 123, + "mdfe30_nVag": 123, + "mdfe30_nSeq": 123, + "mdfe30_TU": 1, + }, + ), + ( + 0, + 0, + { + "mdfe30_pesoBC": 500, + "mdfe30_pesoR": 1, + "mdfe30_tpVag": 321, + "mdfe30_serie": 321, + "mdfe30_nVag": 321, + "mdfe30_nSeq": 321, + "mdfe30_TU": 1, + }, + ), + ] + + def serialize_xml(self, mdfe_data): + mdfe = mdfe_data["mdfe"] + xml_path = os.path.join( + l10n_br_mdfe.__path__[0], + "tests", + "mdfe", + "v3_00", + "leiauteMDFe", + mdfe_data["xml_file"], + ) + output = os.path.join( + config["data_dir"], + "filestore", + self.cr.dbname, + mdfe.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_mdfe/tests/test_mdfe_serialize_lc.py b/l10n_br_mdfe/tests/test_mdfe_serialize_lc.py new file mode 100644 index 000000000000..9c3a8c1c6fe3 --- /dev/null +++ b/l10n_br_mdfe/tests/test_mdfe_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_mdfe_serialize import TestMDFeSerialize + +_logger = logging.getLogger(__name__) + + +class TestMDFeExportLC(TestMDFeSerialize): + def setUp(self): + mdfe_list = [ + { + "record_ref": "l10n_br_mdfe.demo_mdfe_lc_modal_ferroviario", + "xml_file": "MDFe35230905472475000102580200000602011208018449.xml", + }, + { + "record_ref": "l10n_br_mdfe.demo_mdfe_lc_modal_rodoviario", + "xml_file": "MDFe35230905472475000102580200000602071611554500.xml", + }, + ] + super().setUp(mdfe_list) + + def test_serialize_xml(self): + for mdfe_data in self.mdfe_list: + diff = self.serialize_xml(mdfe_data) + _logger.info("Diff with expected XML (if any): %s" % (diff,)) + assert len(diff) == 0 diff --git a/l10n_br_mdfe/tests/test_mdfe_serialize_sn.py b/l10n_br_mdfe/tests/test_mdfe_serialize_sn.py new file mode 100644 index 000000000000..56356b3bd4a7 --- /dev/null +++ b/l10n_br_mdfe/tests/test_mdfe_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_mdfe_serialize import TestMDFeSerialize + +_logger = logging.getLogger(__name__) + + +class TestMDFeExportSN(TestMDFeSerialize): + def setUp(self): + mdfe_list = [ + { + "record_ref": "l10n_br_mdfe.demo_mdfe_sn_modal_aereo", + "xml_file": "MDFe35230905472475000102580200000602081550195716.xml", + }, + { + "record_ref": "l10n_br_mdfe.demo_mdfe_sn_modal_aquaviario", + "xml_file": "MDFe35231005472475000102580200000602161434590525.xml", + }, + ] + + super().setUp(mdfe_list) + + def test_serialize_xml(self): + for mdfe_data in self.mdfe_list: + diff = self.serialize_xml(mdfe_data) + _logger.info("Diff with expected XML (if any): %s" % (diff,)) + assert len(diff) == 0 diff --git a/l10n_br_mdfe/tests/test_mdfe_structure.py b/l10n_br_mdfe/tests/test_mdfe_structure.py new file mode 100644 index 000000000000..211676ac67f5 --- /dev/null +++ b/l10n_br_mdfe/tests/test_mdfe_structure.py @@ -0,0 +1,162 @@ +# @ 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 MDFe + + +class MDFeStructure(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_mdfe_spec.models.v3_0.mdfe_tipos_basico_v3_00" + ) + spec_prefix = "mdfe30" + 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 "mdfe30_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["mdfe.30.infmuncarrega"].search( + [("mdfe30_cMunCarrega", "=", "NO_RECORD")] + ) + ), + 0, + ) + + def test_m2o_concrete_to_concrete_spec(self): + self.assertEqual( + self.env["mdfe.30.infcte"] + ._fields["mdfe30_infCTe_infMunDescarga_id"] + .comodel_name, + "mdfe.30.infmundescarga", + ) + + def test_o2m_concrete_to_concrete_spec(self): + self.assertEqual( + self.env["mdfe.30.ide"]._fields["mdfe30_infMunCarrega"].comodel_name, + "mdfe.30.infmuncarrega", + ) + + def test_m2o_stacked_to_odoo(self): + self.assertEqual( + self.env["l10n_br_fiscal.document"]._fields["mdfe30_prodPred"].comodel_name, + "product.product", + ) + + def test_o2m_to_odoo(self): + self.assertEqual( + self.env["l10n_br_fiscal.document"] + ._fields["mdfe30_infEmbComb"] + .comodel_name, + "l10n_br_mdfe.modal.aquaviario.comboio", + ) + self.assertEqual( + len( + self.env["l10n_br_mdfe.modal.aquaviario.comboio"].search( + [("mdfe30_cEmbComb", "=", "NO_RECORD")] + ) + ), + 0, + ) + + def test_m2o_stacked_to_concrete(self): + # not stacked because optional + model = ( + self.env["l10n_br_fiscal.document"] + ._fields["mdfe30_infSolicNFF"] + .comodel_name + ) + self.assertEqual(model, "mdfe.30.infsolicnff") + + # def test_m2o_stacked(self): + # # not stacked because optional + # mdfe_model = self.env["l10n_br_fiscal.document"] + # # mdfe30_cana is optional so its fields shoudn't be stacked + # assert "mdfe30_XXX" not in mdfe_model._fields.keys() + + def test_doc_stacking_points(self): + doc_keys = [ + "mdfe30_ide", + "mdfe30_infModal", + "mdfe30_infDoc", + "mdfe30_tot", + "mdfe30_infAdic", + # "mdfe30_trem", + # "mdfe30_infANTT", + # "mdfe30_valePed", + # "mdfe30_veicTracao", + # "mdfe30_infBanc", + ] + keys = [ + k + for k in self.env["l10n_br_fiscal.document"] + .with_context(spec_schema="mdfe", 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, MDFe.INFMDFE_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_mdfe/views/document.xml b/l10n_br_mdfe/views/document.xml new file mode 100644 index 000000000000..2aa6002cdfc7 --- /dev/null +++ b/l10n_br_mdfe/views/document.xml @@ -0,0 +1,465 @@ + + + + + + mdfe.document.form.view (in l10n_br_mdfe) + l10n_br_fiscal.document + + 5 + + + {'invisible': [('document_type', 'in', ['58'])]} + + + + {'invisible': [('document_type', 'in', ['58'])]} + + + + {'invisible': [('document_type', 'in', ['58'])]} + + + + {'invisible': [('document_type', 'in', ['58'])]} + + + + {'invisible': [('document_type', 'in', ['58'])]} + + + + {'invisible': [('document_type', 'in', ['58'])]} + + + + {'invisible': [('document_type', 'in', ['58'])]} + + + + {'invisible': [('document_type', 'in', ['58'])]} + + + + {'invisible': [('document_type', 'in', ['58'])]} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + l10n_br_nfe_edi.document.form.inherit + l10n_br_fiscal.document + 5 + + + + + + + + + + +
+ + + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+
+
+ + + +
+ +
+
+ + + +
diff --git a/l10n_br_mdfe/views/mdfe_action.xml b/l10n_br_mdfe/views/mdfe_action.xml new file mode 100644 index 000000000000..c80449be2a35 --- /dev/null +++ b/l10n_br_mdfe/views/mdfe_action.xml @@ -0,0 +1,47 @@ + + + + + + MDF-e + ir.actions.act_window + l10n_br_fiscal.document + tree,form + + + + +

+ Add a new MDF-e +

+
+
+ +
diff --git a/l10n_br_mdfe/views/mdfe_menu.xml b/l10n_br_mdfe/views/mdfe_menu.xml new file mode 100644 index 000000000000..7eb08deee1db --- /dev/null +++ b/l10n_br_mdfe/views/mdfe_menu.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/l10n_br_mdfe/views/modal/modal_aquaviario.xml b/l10n_br_mdfe/views/modal/modal_aquaviario.xml new file mode 100644 index 000000000000..7f635e68698f --- /dev/null +++ b/l10n_br_mdfe/views/modal/modal_aquaviario.xml @@ -0,0 +1,65 @@ + + + + + modal.aquaviario.carregamento.tree.view (in l10n_br_mdfe) + l10n_br_mdfe.modal.aquaviario.carregamento + + + + + + + + + modal.aquaviario.descarregamento.tree.view (in l10n_br_mdfe) + l10n_br_mdfe.modal.aquaviario.descarregamento + + + + + + + + + modal.aquaviario.comboio.tree.view (in l10n_br_mdfe) + l10n_br_mdfe.modal.aquaviario.comboio + + + + + + + + + + modal.aquaviario.carga.vazia.tree.view (in l10n_br_mdfe) + l10n_br_mdfe.modal.aquaviario.carga.vazia + + + + + + + + + + modal.aquaviario.transporte.vazio.tree.view (in l10n_br_mdfe) + l10n_br_mdfe.modal.aquaviario.transporte.vazio + + + + + + + + diff --git a/l10n_br_mdfe/views/modal/modal_ferroviario.xml b/l10n_br_mdfe/views/modal/modal_ferroviario.xml new file mode 100644 index 000000000000..cf5a1c671009 --- /dev/null +++ b/l10n_br_mdfe/views/modal/modal_ferroviario.xml @@ -0,0 +1,40 @@ + + + + + modal.ferroviario.vagao.tree.view + l10n_br_mdfe.modal.ferroviario.vagao + + + + + + + + + + + modal.ferroviario.vagao.form.view (in l10n_br_mdfe) + l10n_br_mdfe.modal.ferroviario.vagao + +
+ + + + + + + + + + + + + + + +
+
+
+
diff --git a/l10n_br_mdfe/views/modal/modal_rodoviario.xml b/l10n_br_mdfe/views/modal/modal_rodoviario.xml new file mode 100644 index 000000000000..f31175d8ed3c --- /dev/null +++ b/l10n_br_mdfe/views/modal/modal_rodoviario.xml @@ -0,0 +1,408 @@ + + + + + modal.rodoviario.reboque.form.view (in l10n_br_mdfe) + l10n_br_mdfe.modal.rodoviario.reboque + +
+ + + + + + + + + + + + + + + + + + +
+
+
+ + + modal.rodoviario.reboque.tree.view (in l10n_br_mdfe) + l10n_br_mdfe.modal.rodoviario.reboque + + + + + + + + + + + res.partner.reboque.prop.form.view + res.partner + +
+ + + + + + + + + + + + + + + + + + +
+
+
+ + + modal.rodoviario.ciot.form.view (in l10n_br_mdfe) + l10n_br_mdfe.modal.rodoviario.ciot + +
+ + + + + + + + +
+
+
+ + + modal.rodoviario.ciot.tree.view (in l10n_br_mdfe) + l10n_br_mdfe.modal.rodoviario.ciot + + + + + + + + + modal.rodoviario.vale_pedagio.dispositivo.form.view (in l10n_br_mdfe) + l10n_br_mdfe.modal.rodoviario.vale_pedagio.dispositivo + +
+ + + + + + + + + + + + + + +
+
+
+ + + modal.rodoviario.vale_pedagio.dispositivo.tree.view (in l10n_br_mdfe) + l10n_br_mdfe.modal.rodoviario.vale_pedagio.dispositivo + + + + + + + + + + + res.partner.contratante.form.view (in l10n_br_mdfe) + res.partner + +
+ + + + + + + +
+
+
+ + + res.partner.contratante.tree.view (in l10n_br_mdfe) + res.partner + + + + + + + + + + modal.rodoviario.pagamento.form.view (in l10n_br_mdfe) + l10n_br_mdfe.modal.rodoviario.pagamento + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + modal.rodoviario.pagamento.tree.view (in l10n_br_mdfe) + l10n_br_mdfe.modal.rodoviario.pagamento + + + + + + + + + + + modal.rodoviario.pagamento.prazo.form.view (in l10n_br_mdfe) + l10n_br_mdfe.modal.rodoviario.pagamento.prazo + +
+ + + + + + + +
+
+
+ + + modal.rodoviario.pagamento.prazo.tree.view (in l10n_br_mdfe) + l10n_br_mdfe.modal.rodoviario.pagamento.prazo + + + + + + + + + + + modal.rodoviario.pagamento.frete.form.view (in l10n_br_mdfe) + l10n_br_mdfe.modal.rodoviario.pagamento.frete + +
+ + + + + + + +
+
+
+ + + modal.rodoviario.pagamento.frete.tree.view (in l10n_br_mdfe) + l10n_br_mdfe.modal.rodoviario.pagamento.frete + + + + + + + + + + + modal.rodoviario.veiculo.condutor.tree.view (in l10n_br_mdfe) + l10n_br_mdfe.modal.rodoviario.veiculo.condutor + + + + + + + + + + modal.rodoviario.veiculo.condutor.form.view (in l10n_br_mdfe) + l10n_br_mdfe.modal.rodoviario.veiculo.condutor + +
+ + + + + + +
+
+
+ + + modal.rodoviario.lacre.tree.view (in l10n_br_mdfe) + l10n_br_mdfe.modal.rodoviario.lacre + + + + + + + + + modal.rodoviario.lacre.form.view (in l10n_br_mdfe) + l10n_br_mdfe.modal.rodoviario.lacre + +
+ + + + + +
+
+
+ + + + modal.rodoviario.contratante.form.view (in l10n_br_mdfe) + l10n_br_mdfe.modal.rodoviario.contratante + +
+ + + + + + + + +
+
+
+ + +
diff --git a/l10n_br_mdfe/views/product_product.xml b/l10n_br_mdfe/views/product_product.xml new file mode 100644 index 000000000000..1cc09a479dad --- /dev/null +++ b/l10n_br_mdfe/views/product_product.xml @@ -0,0 +1,62 @@ + + + + + + mdfe.product.product.form (in l10n_br_mdfe) + product.product + + + + + + + + + + + + + + + product.lotacao.form.view (in l10n_br_mdfe) + l10n_br_mdfe.product.lotacao + +
+ + + + + + +
+
+
+ + + product.lotacao.local.form.view (in l10n_br_mdfe) + l10n_br_mdfe.product.lotacao.local + +
+ + + + + + + + +
+
+
+
diff --git a/l10n_br_mdfe/views/res_company.xml b/l10n_br_mdfe/views/res_company.xml new file mode 100644 index 000000000000..4b12f3f6ca00 --- /dev/null +++ b/l10n_br_mdfe/views/res_company.xml @@ -0,0 +1,24 @@ + + + + + + res.company.form (in l10n_br_mdfe) + res.company + + + + + + + + + + + + + + + + diff --git a/l10n_br_mdfe/views/res_partner.xml b/l10n_br_mdfe/views/res_partner.xml new file mode 100644 index 000000000000..2fc3ffc450ff --- /dev/null +++ b/l10n_br_mdfe/views/res_partner.xml @@ -0,0 +1,26 @@ + + + + + + mdfe.res.partner.form (in l10n_br_mdfe) + res.partner + + + + + + + + + + + + + + + + + + diff --git a/l10n_br_mdfe/views/transporte.xml b/l10n_br_mdfe/views/transporte.xml new file mode 100644 index 000000000000..2b447a648663 --- /dev/null +++ b/l10n_br_mdfe/views/transporte.xml @@ -0,0 +1,14 @@ + + + + + transporte.lacre.tree.view (in l10n_br_mdfe) + l10n_br_mdfe.transporte.lacre + + + + + + + diff --git a/l10n_br_mdfe_spec/README.rst b/l10n_br_mdfe_spec/README.rst index 146c2695ba45..bdda06a9e876 100644 --- a/l10n_br_mdfe_spec/README.rst +++ b/l10n_br_mdfe_spec/README.rst @@ -28,7 +28,7 @@ mdfe spec |badge1| |badge2| |badge3| |badge4| |badge5| -Este módulo contem a estrutura de dados do ​Manifesto Eletrônico de Documentos Fiscais (CT-e). +Este módulo contem a estrutura de dados do ​Manifesto Eletrônico de Documentos Fiscais (MDF-e). Este módulo não faz nada sozinho, ele precisaria de um modulo `l10n_br_mdfe` que mapearia esses mixins nos documentos fiscais Odoo de forma semlhante a forma como o módulo `l10n_br_nfe` faz como o módulo `l10n_br_nfe_spec`. diff --git a/l10n_br_mdfe_spec/__manifest__.py b/l10n_br_mdfe_spec/__manifest__.py index f5b1b34abe89..a827b52a7398 100644 --- a/l10n_br_mdfe_spec/__manifest__.py +++ b/l10n_br_mdfe_spec/__manifest__.py @@ -1,10 +1,10 @@ { "name": "mdfe spec", - "version": "14.0.1.0.0", + "version": "14.0.1.1.0", "author": "Akretion, Odoo Community Association (OCA)", "license": "LGPL-3", "category": "Accounting", - "summary": "CT-e spec", + "summary": "MDF-e abstract models generated by xsdata-odoo from the oficial xsd", "depends": ["base"], "installable": True, "application": False, diff --git a/l10n_br_mdfe_spec/models/__init__.py b/l10n_br_mdfe_spec/models/__init__.py index 5fa2504e4d68..f284bb3711ff 100644 --- a/l10n_br_mdfe_spec/models/__init__.py +++ b/l10n_br_mdfe_spec/models/__init__.py @@ -1,2 +1,2 @@ -from . import spec_models +from . import spec_mixin from . import v3_0 diff --git a/l10n_br_mdfe_spec/models/spec_models.py b/l10n_br_mdfe_spec/models/spec_mixin.py similarity index 73% rename from l10n_br_mdfe_spec/models/spec_models.py rename to l10n_br_mdfe_spec/models/spec_mixin.py index dde5ec995383..2dce2d1c2460 100644 --- a/l10n_br_mdfe_spec/models/spec_models.py +++ b/l10n_br_mdfe_spec/models/spec_mixin.py @@ -7,13 +7,10 @@ class MdfeSpecMixin(models.AbstractModel): _description = "Abstract Model" _name = "spec.mixin.mdfe" - _field_prefix = "mdfe30_" - _schema_name = "mdfe" - _schema_version = "3.0.0" - _odoo_module = "l10n_br_mdfe" - _spec_module = "odoo.addons.l10n_br_mdfe_spec.models.v3_0.mdfe_tipos_basico_v3_00" - _binding_module = "nfelib.mdfe.bindings.v3_0.mdfe_tipos_basico_v3_00" - _spec_tab_name = "mdfe" + _mdfe30_odoo_module = ( + "odoo.addons.l10n_br_mdfe_spec.models.v3_0.mdfe_tipos_basico_v3_00" + ) + _mdfe30_binding_module = "nfelib.mdfe.bindings.v3_0.mdfe_tipos_basico_v3_00" brl_currency_id = fields.Many2one( comodel_name="res.currency", diff --git a/l10n_br_mdfe_spec/readme/DESCRIPTION.rst b/l10n_br_mdfe_spec/readme/DESCRIPTION.rst index 2d39303fa404..573abfb9f05c 100644 --- a/l10n_br_mdfe_spec/readme/DESCRIPTION.rst +++ b/l10n_br_mdfe_spec/readme/DESCRIPTION.rst @@ -1,4 +1,4 @@ -Este módulo contem a estrutura de dados do ​Manifesto Eletrônico de Documentos Fiscais (CT-e). +Este módulo contem a estrutura de dados do ​Manifesto Eletrônico de Documentos Fiscais (MDF-e). Este módulo não faz nada sozinho, ele precisaria de um modulo `l10n_br_mdfe` que mapearia esses mixins nos documentos fiscais Odoo de forma semlhante a forma como o módulo `l10n_br_nfe` faz como o módulo `l10n_br_nfe_spec`. diff --git a/l10n_br_mdfe_spec/static/description/index.html b/l10n_br_mdfe_spec/static/description/index.html index 1cd0f667e9d1..daff91f5250a 100644 --- a/l10n_br_mdfe_spec/static/description/index.html +++ b/l10n_br_mdfe_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 { @@ -370,7 +370,7 @@

mdfe spec

!! source digest: sha256:038c9a5640e9b9d315bb079de305b68c4afe07ac6dab7876d7aeea004ce1e29d !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

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

-

Este módulo contem a estrutura de dados do ​Manifesto Eletrônico de Documentos Fiscais (CT-e). +

Este módulo contem a estrutura de dados do ​Manifesto Eletrônico de Documentos Fiscais (MDF-e). Este módulo não faz nada sozinho, ele precisaria de um modulo l10n_br_mdfe que mapearia esses mixins nos documentos fiscais Odoo de forma semlhante a forma como o módulo l10n_br_nfe faz como o módulo l10n_br_nfe_spec.

Este módulo inclui os leiautes persistantes dos modos de transporte do MDF-e:

@@ -427,7 +427,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_mdfe_spec/tests/test_mdfe_import.py b/l10n_br_mdfe_spec/tests/test_mdfe_import.py index fd324dde943a..be02bd7d05b4 100644 --- a/l10n_br_mdfe_spec/tests/test_mdfe_import.py +++ b/l10n_br_mdfe_spec/tests/test_mdfe_import.py @@ -6,13 +6,12 @@ import nfelib import pkg_resources -from nfelib.mdfe.bindings.v3_0.mdfe_v3_00 import Mdfe -from xsdata.formats.dataclass.parsers import XmlParser +from nfelib.mdfe.bindings.v3_0.mdfe_v3_00 import Tmdfe from odoo import api -from odoo.tests import TransactionCase +from odoo.tests import SavepointCase -from ..models import spec_models +from ..models import spec_mixin tz_datetime = re.compile(r".*[-+]0[0-9]:00$") @@ -33,15 +32,12 @@ def build_attrs_fake(self, node, create_m2o=False): fields = self.fields_get() vals = self.default_get(fields.keys()) for fname, fspec in node.__dataclass_fields__.items(): - if fname == "any_element": + if fname == "any_element": # FIXME in spec_driven_model continue value = getattr(node, fname) if value is None: continue - key = "%s%s" % ( - self._field_prefix, - fspec.metadata.get("name", fname), - ) + key = f"mdfe30_{fspec.metadata.get('name', fname)}" if ( fspec.type == str or not any(["." in str(i) for i in fspec.type.__args__]) ) and not str(fspec.type).startswith("typing.List"): @@ -66,12 +62,8 @@ def build_attrs_fake(self, node, create_m2o=False): key = fields[key]["related"][0] comodel_name = fields[key]["relation"] else: - clean_type = binding_type.lower() # TODO double check - comodel_name = "%s.%s.%s" % ( - self._schema_name, - self._schema_version.replace(".", "")[0:2], - clean_type.split(".")[-1], - ) + clean_type = binding_type.lower() + comodel_name = f"mdfe.30.{clean_type.split('.')[-1]}" comodel = self.env.get(comodel_name) if comodel is None: # example skip ICMS100 class continue @@ -114,13 +106,12 @@ def match_or_create_m2o_fake(self, comodel, new_value, create_m2o=False): return comodel.new(new_value).id -# spec_models.CteSpecMixin._update_cache = _update_cache -spec_models.MdfeSpecMixin.build_fake = build_fake -spec_models.MdfeSpecMixin.build_attrs_fake = build_attrs_fake -spec_models.MdfeSpecMixin.match_or_create_m2o_fake = match_or_create_m2o_fake +spec_mixin.MdfeSpecMixin.build_fake = build_fake +spec_mixin.MdfeSpecMixin.build_attrs_fake = build_attrs_fake +spec_mixin.MdfeSpecMixin.match_or_create_m2o_fake = match_or_create_m2o_fake -class MdfeImportTest(TransactionCase): +class MdfeImportTest(SavepointCase): def test_import_mdfe(self): res_items = ( "mdfe", @@ -129,13 +120,14 @@ def test_import_mdfe(self): "41190876676436000167580010000500001000437558-mdfe.xml", ) resource_path = "/".join(res_items) - nfe_stream = pkg_resources.resource_stream(nfelib.__name__, resource_path) - parser = XmlParser() - binding = parser.from_string(nfe_stream.read().decode(), Mdfe) - inf_mdfe = self.env["mdfe.30.tmdfe_infmdfe"].build_fake( - binding.infMDFe, create=False + mdfe_stream = pkg_resources.resource_stream(nfelib.__name__, resource_path) + binding = Tmdfe.from_xml(mdfe_stream.read().decode()) + mdfe = ( + self.env["mdfe.30.tmdfe_infmdfe"] + .with_context(tracking_disable=True, edoc_type="in", lang="pt_BR") + .build_fake(binding.infMDFe, create=False) ) - self.assertEqual(inf_mdfe.mdfe30_emit.mdfe30_CNPJ, "76676436000167") + self.assertEqual(mdfe.mdfe30_emit.mdfe30_CNPJ, "76676436000167") def test_import_mdfe2(self): res_items = ( @@ -145,10 +137,11 @@ def test_import_mdfe2(self): "50170876063965000276580010000011311421039568-mdfe.xml", ) resource_path = "/".join(res_items) - nfe_stream = pkg_resources.resource_stream(nfelib.__name__, resource_path) - parser = XmlParser() - binding = parser.from_string(nfe_stream.read().decode(), Mdfe) - inf_mdfe = self.env["mdfe.30.tmdfe_infmdfe"].build_fake( - binding.infMDFe, create=False + mdfe_stream = pkg_resources.resource_stream(nfelib.__name__, resource_path) + binding = Tmdfe.from_xml(mdfe_stream.read().decode()) + mdfe = ( + self.env["mdfe.30.tmdfe_infmdfe"] + .with_context(tracking_disable=True, edoc_type="in", lang="pt_BR") + .build_fake(binding.infMDFe, create=False) ) - self.assertEqual(inf_mdfe.mdfe30_emit.mdfe30_xNome, "TESTE") + self.assertEqual(mdfe.mdfe30_emit.mdfe30_xNome, "TESTE") diff --git a/l10n_br_nfe/README.rst b/l10n_br_nfe/README.rst index 1383b66d73e6..51da6f1371bb 100644 --- a/l10n_br_nfe/README.rst +++ b/l10n_br_nfe/README.rst @@ -52,6 +52,12 @@ Usage O menu de NF-e se encontra dentro do menu fiscal. +Known issues / Roadmap +====================== + +* Implementar a transmissão do MDF-e +* Implementar o cancelamento do MDF-e + Bug Tracker =========== diff --git a/l10n_br_nfe/__manifest__.py b/l10n_br_nfe/__manifest__.py index d9293aa80765..356ffeeb2af0 100644 --- a/l10n_br_nfe/__manifest__.py +++ b/l10n_br_nfe/__manifest__.py @@ -11,7 +11,7 @@ "maintainers": ["rvalyi", "renatonlima"], "website": "https://github.com/OCA/l10n-brazil", "development_status": "Beta", - "version": "14.0.19.0.0", + "version": "14.0.19.1.0", "depends": [ "l10n_br_fiscal_edi", "l10n_br_fiscal_certificate", diff --git a/l10n_br_nfe/models/document.py b/l10n_br_nfe/models/document.py index c854ae327ae8..dda45af340f6 100644 --- a/l10n_br_nfe/models/document.py +++ b/l10n_br_nfe/models/document.py @@ -906,7 +906,10 @@ def _serialize(self, edocs): edocs.append(nfe) return edocs - def _processador(self): + def _edoc_processor(self): + if not self.filtered(filter_processador_edoc_nfe): + return super()._edoc_processor() + self._check_nfe_environment() certificado = self.company_id._get_br_ecertificate() session = Session() @@ -949,7 +952,7 @@ def _document_export(self, pretty_print=True): result = super()._document_export() for record in self.filtered(filter_processador_edoc_nfe): edoc = record.serialize()[0] - processador = record._processador() + processador = record._edoc_processor() xml_file = processador.render_edoc_xsdata(edoc, pretty_print=pretty_print)[ 0 ] @@ -971,7 +974,7 @@ def _document_export(self, pretty_print=True): ) record.authorization_event_id = event_id xml_assinado = processador.assina_raiz(edoc, edoc.infNFe.Id) - self._valida_xml(xml_assinado) + self._validate_xml(xml_assinado) return result def _nfe_update_status_and_save_data(self, process): @@ -1045,8 +1048,12 @@ def _nfe_save_protocol(self, inf_prot, nfe_proc_xml=None): file_response_xml=nfe_proc_xml, ) - def _valida_xml(self, xml_file): + def _validate_xml(self, xml_file): self.ensure_one() + + if not self.filtered(filter_processador_edoc_nfe): + return super()._validate_xml(xml_file) + erros = Nfe.schema_validation(xml_file) erros = "\n".join(erros) self.write({"xml_error_message": erros or False}) @@ -1069,7 +1076,12 @@ def _exec_after_SITUACAO_EDOC_AUTORIZADA(self, old_state, new_state): return super()._exec_after_SITUACAO_EDOC_AUTORIZADA(old_state, new_state) def _generate_key(self): - super()._generate_key() + if self.document_type_id.code not in [ + MODELO_FISCAL_NFE, + MODELO_FISCAL_NFCE, + ]: + return super()._generate_key() + for record in self.filtered(filter_processador_edoc_nfe): required_fields_gen_edoc = [] if not record.company_cnpj_cpf: @@ -1105,7 +1117,7 @@ def _generate_key(self): def _nfe_consult_receipt(self): self.ensure_one() - processor = self._processador() + processor = self._edoc_processor() # Consult receipt and process the response rec_num = self.authorization_event_id.lot_receipt_number receipt_process = processor.consulta_recibo(numero=rec_num) @@ -1158,7 +1170,7 @@ def _nfe_create_proc(self, prot_nfe_element): ) return None - processor = self._processador() + processor = self._edoc_processor() # Extract the tag from the `enviNFe` message, which represents the NF-e nfe_send_xml = base64.b64decode(self.send_file_id.datas) @@ -1197,7 +1209,7 @@ def _is_nfe_found(c_stat): """ return c_stat in ["100", "101", "110"] - nfe_manager = self._processador() + nfe_manager = self._edoc_processor() check_response = nfe_manager.consulta_documento(chave=self.document_key) status = check_response.resposta.xMotivo @@ -1239,7 +1251,7 @@ def _nfe_send_for_authorization(self): Serialize and send a NFe for authorizaion """ serialized_nfe = self.serialize()[0] - nfe_manager = self._processador() + nfe_manager = self._edoc_processor() authorization_response = None for service_response in nfe_manager.processar_documento(serialized_nfe): if service_response.webservice not in [ @@ -1384,7 +1396,7 @@ def _need_compute_nfe_tags(self): def _nfe_cancel(self): self.ensure_one() - processador = self._processador() + processador = self._edoc_processor() if not self.authorization_protocol: raise UserError(_("Authorization Protocol Not Found!")) @@ -1440,7 +1452,7 @@ def _document_correction(self, justificative): def _nfe_correction(self, justificative): self.ensure_one() - processador = self._processador() + processador = self._edoc_processor() numeros = self.event_ids.filtered( lambda e: e.type == "14" and e.state == "done" @@ -1500,7 +1512,7 @@ def _update_nfce_for_offline_contingency(self): def get_nfce_qrcode(self): if self.document_type != MODELO_FISCAL_NFCE: return - processador = self._processador() + processador = self._edoc_processor() if self.nfe_transmission == "1": return processador.monta_qrcode(self.document_key) @@ -1512,7 +1524,7 @@ def get_nfce_qrcode_url(self): if self.document_type != MODELO_FISCAL_NFCE: return - return self._processador().consulta_qrcode_url + return self._edoc_processor().consulta_qrcode_url def _prepare_payments_for_nfce(self): for rec in self.filtered(lambda d: d.document_type == MODELO_FISCAL_NFCE): diff --git a/l10n_br_nfe/models/document_supplement.py b/l10n_br_nfe/models/document_supplement.py index a11ded954257..ace5a084b5a5 100644 --- a/l10n_br_nfe/models/document_supplement.py +++ b/l10n_br_nfe/models/document_supplement.py @@ -7,10 +7,13 @@ from odoo.addons.spec_driven_model.models import spec_models -class NFeSupplement(spec_models.SpecModel): +class NFeSupplement(spec_models.StackedModel): _name = "l10n_br_fiscal.document.supplement" _inherit = ["l10n_br_fiscal.document.supplement", "nfe.40.infnfesupl"] + _nfe40_odoo_module = "odoo.addons.l10n_br_nfe_spec.models.v4_0.leiaute_nfe_v4_00" + _nfe40_stacking_mixin = "nfe.40.infnfesupl" + nfe40_qrCode = fields.Char(related="qrcode") nfe40_urlChave = fields.Char(related="url_key") diff --git a/l10n_br_nfe/models/invalidate_number.py b/l10n_br_nfe/models/invalidate_number.py index e4f55023a759..fe45271ae024 100644 --- a/l10n_br_nfe/models/invalidate_number.py +++ b/l10n_br_nfe/models/invalidate_number.py @@ -17,7 +17,7 @@ class InvalidateNumber(models.Model): _inherit = "l10n_br_fiscal.invalidate.number" - def _processador(self): + def _edoc_processor(self): certificado = self.env.company._get_br_ecertificate() session = Session() session.verify = False @@ -38,7 +38,7 @@ def _processador(self): return edoc_nfe(**params) def _invalidate(self, document_id=False): - processador = self._processador() + processador = self._edoc_processor() evento = processador.inutilizacao( cnpj=punctuation_rm(self.company_id.cnpj_cpf), mod=self.document_type_id.code, diff --git a/l10n_br_nfe/models/product_product.py b/l10n_br_nfe/models/product_product.py index fff8573fe260..d2f52f5378b0 100644 --- a/l10n_br_nfe/models/product_product.py +++ b/l10n_br_nfe/models/product_product.py @@ -7,6 +7,7 @@ class ProductProduct(models.Model): _inherit = "product.product" + _nfe40_odoo_module = "odoo.addons.l10n_br_nfe_spec.models.v4_0.leiaute_nfe_v4_00" _nfe_search_keys = ["default_code", "barcode"] def match_or_create_m2o(self, rec_dict, parent_dict, model=None): diff --git a/l10n_br_nfe/readme/ROADMAP.rst b/l10n_br_nfe/readme/ROADMAP.rst index e69de29bb2d1..228ab3230f08 100644 --- a/l10n_br_nfe/readme/ROADMAP.rst +++ b/l10n_br_nfe/readme/ROADMAP.rst @@ -0,0 +1,2 @@ +* Implementar a transmissão do MDF-e +* Implementar o cancelamento do MDF-e diff --git a/l10n_br_nfe/static/description/index.html b/l10n_br_nfe/static/description/index.html index ed908c0f4856..5bb6f46d27b0 100644 --- a/l10n_br_nfe/static/description/index.html +++ b/l10n_br_nfe/static/description/index.html @@ -392,11 +392,12 @@

NF-e

+
+

Known issues / Roadmap

+
    +
  • Implementar a transmissão do MDF-e
  • +
  • Implementar o cancelamento do MDF-e
  • +
+
-

Bug Tracker

+

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 @@ -414,16 +422,16 @@

Bug Tracker

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

-

Credits

+

Credits

-

Authors

+

Authors

  • Akretion
  • KMEE
-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association diff --git a/l10n_br_nfse_focus/__manifest__.py b/l10n_br_nfse_focus/__manifest__.py index 3d72e52c9d4b..8df79efd9820 100644 --- a/l10n_br_nfse_focus/__manifest__.py +++ b/l10n_br_nfse_focus/__manifest__.py @@ -5,7 +5,7 @@ "name": "NFS-e (FocusNFE)", "summary": """ NFS-e (FocusNFE)""", - "version": "14.0.1.2.1", + "version": "14.0.1.2.2", "license": "AGPL-3", "author": "KMEE, Escodoo, Odoo Community Association (OCA)", "maintainers": [ diff --git a/l10n_br_nfse_focus/tests/test_l10n_br_nfse_focus.py b/l10n_br_nfse_focus/tests/test_l10n_br_nfse_focus.py index e0346143eeda..285b15d8fc59 100644 --- a/l10n_br_nfse_focus/tests/test_l10n_br_nfse_focus.py +++ b/l10n_br_nfse_focus/tests/test_l10n_br_nfse_focus.py @@ -380,38 +380,27 @@ def test_serialize(self): def test_document_export(self): """Tests export of document data.""" - record = self.nfse_demo - record.processador_edoc = PROCESSADOR_OCA # Setting document processor to OCA - record.document_type_id.code = MODELO_FISCAL_NFE # Setting document type to NFe # Testing with non-filtered conditions record = self.nfse_demo - record.company_id.provedor_nfse = None # Resetting NFSe provider + record.processador_edoc = PROCESSADOR_OCA + record.company_id.provedor_nfse = None - record._document_export() # Exporting document data + record.action_document_confirm() - self.assertFalse( - record.company_id.provedor_nfse - ) # Asserting NFSe provider not set + self.assertFalse(record.company_id.provedor_nfse) + + self.assertEqual(record.state, SITUACAO_EDOC_A_ENVIAR) # Testing with filtered conditions record = self.nfse_demo - record.company_id.provedor_nfse = ( - "focusnfe" # Setting NFSe provider to focusnfe - ) - record.processador_edoc = PROCESSADOR_OCA # Setting processor to OCA - record.document_type_id.code = ( - MODELO_FISCAL_NFSE # Setting document type to NFSe - ) + record.company_id.provedor_nfse = "focusnfe" + + record._document_export() - record._document_export() # Exporting document data again + self.assertTrue(record.company_id.provedor_nfse) - self.assertTrue( - record.company_id.provedor_nfse - ) # Asserting NFSe provider is set - self.assertTrue( - record.authorization_event_id - ) # Asserting authorization event is set + self.assertTrue(record.authorization_event_id) @patch( "odoo.addons.l10n_br_nfse_focus.models.document.FocusnfeNfse.query_focus_nfse_by_rps" diff --git a/setup/l10n_br_mdfe/odoo/addons/l10n_br_mdfe b/setup/l10n_br_mdfe/odoo/addons/l10n_br_mdfe new file mode 120000 index 000000000000..4b3c90b7dcb2 --- /dev/null +++ b/setup/l10n_br_mdfe/odoo/addons/l10n_br_mdfe @@ -0,0 +1 @@ +../../../../l10n_br_mdfe \ No newline at end of file diff --git a/setup/l10n_br_mdfe/setup.py b/setup/l10n_br_mdfe/setup.py new file mode 100644 index 000000000000..28c57bb64031 --- /dev/null +++ b/setup/l10n_br_mdfe/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)