diff --git a/l10n_br_account/README.rst b/l10n_br_account/README.rst new file mode 100644 index 000000000000..735c45b1be44 --- /dev/null +++ b/l10n_br_account/README.rst @@ -0,0 +1,125 @@ +============================== +Brazilian Localization Account +============================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:14a6f5c1a28387de33b045146f1d6960b79bf51fae3a0c6674ec20c8d0109dd3 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |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/15.0/l10n_br_account + :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-15-0/l10n-brazil-15-0-l10n_br_account + :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=15.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module was written to extend the functionality of ... to support ... +and allow you to ... + +**Table of contents** + +.. contents:: + :local: + +Installation +============ + +To install this module, you need to: + +* do this ... + +Configuration +============= + +To configure this module, you need to: + +* go to ... + +Usage +===== + +To use this module, you need to: + +* go to ... + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Akretion + +Contributors +~~~~~~~~~~~~ + +* `Akretion `_: + + * Renato Lima + * Raphaël Valyi + +* `KMEE `_: + + * Luis Felipe Mileo + +* `Escodoo `_: + + * Marcel Savegnago + +* `Engenere `_: + + * Antônio S. Pereira Neto + * Felipe Motter Pereira + +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-renatonlima| image:: https://github.com/renatonlima.png?size=40px + :target: https://github.com/renatonlima + :alt: renatonlima +.. |maintainer-rvalyi| image:: https://github.com/rvalyi.png?size=40px + :target: https://github.com/rvalyi + :alt: rvalyi + +Current `maintainers `__: + +|maintainer-renatonlima| |maintainer-rvalyi| + +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_account/__init__.py b/l10n_br_account/__init__.py new file mode 100644 index 000000000000..202eeaed3b6b --- /dev/null +++ b/l10n_br_account/__init__.py @@ -0,0 +1,5 @@ +from .hooks import post_init_hook + +from . import models +from . import wizards +from . import report diff --git a/l10n_br_account/__manifest__.py b/l10n_br_account/__manifest__.py new file mode 100644 index 000000000000..bfb394d2f996 --- /dev/null +++ b/l10n_br_account/__manifest__.py @@ -0,0 +1,51 @@ +# Copyright (C) 2009 - TODAY Renato Lima - Akretion +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +{ + "name": "Brazilian Localization Account", + "category": "Localisation", + "license": "AGPL-3", + "author": "Akretion, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/l10n-brazil", + "version": "16.0.1.0.0", + "development_status": "Beta", + "maintainers": ["renatonlima", "rvalyi"], + "depends": [ + "l10n_br_coa", + "l10n_br_fiscal", + "l10n_br_account_due_list", + ], + "data": [ + # security + "security/ir.model.access.csv", + # data + "data/account_tax_group.xml", + "data/account_tax_template.xml", + # Views + "views/account_tax_view.xml", + "views/account_tax_template_view.xml", + "views/fiscal_operation_view.xml", + "views/fiscal_operation_line_view.xml", + "views/account_invoice_view.xml", + "views/document_line_view.xml", + "views/document_view.xml", + "views/fiscal_invoice_view.xml", + "views/fiscal_invoice_line_view.xml", + # Wizards + "wizards/account_move_reversal_view.xml", + "wizards/wizard_document_status.xml", + # Actions + "views/l10n_br_account_action.xml", + # Menus + "views/l10n_br_account_menu.xml", + # Report + # "report/account_invoice_report_view.xml", + "views/res_partner_view.xml", + ], + "demo": [ + "demo/res_users_demo.xml", + ], + "post_init_hook": "post_init_hook", + "installable": True, + "auto_install": False, +} diff --git a/l10n_br_account/data/account_tax_group.xml b/l10n_br_account/data/account_tax_group.xml new file mode 100644 index 000000000000..96cef1b96c7f --- /dev/null +++ b/l10n_br_account/data/account_tax_group.xml @@ -0,0 +1,125 @@ + + + + + + IPI + + + + + II + + + + + ICMS + + + + + ICMS SN + + + + + ICMS ST + + + + + ICMS FCP + + + + + ICMS FCP ST + + + + + PIS + + + + + PIS ST + + + + + PIS WH + + + + + COFINS + + + + + COFINS ST + + + + + COFINS WH + + + + + ISSQN + + + + + ISSQN WH + + + + + CSLL + + + + + CSLL WH + + + + + IR + + + + + IRPJ + + + + + IRPJ WH + + + + + INSS + + + + + INSS WH + + + + + Simples Nacional + + + + + Outros + + + + diff --git a/l10n_br_account/data/account_tax_template.xml b/l10n_br_account/data/account_tax_template.xml new file mode 100644 index 000000000000..93e3aee4cc0c --- /dev/null +++ b/l10n_br_account/data/account_tax_template.xml @@ -0,0 +1,307 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n_br_account/demo/res_users_demo.xml b/l10n_br_account/demo/res_users_demo.xml new file mode 100644 index 000000000000..c0f720546785 --- /dev/null +++ b/l10n_br_account/demo/res_users_demo.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/l10n_br_account/hooks.py b/l10n_br_account/hooks.py new file mode 100644 index 000000000000..9f582fcf7775 --- /dev/null +++ b/l10n_br_account/hooks.py @@ -0,0 +1,15 @@ +# Copyright (C) 2019 - Raphaël Valyi Akretion +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import SUPERUSER_ID, api + + +def post_init_hook(cr, registry): + """Relate fiscal taxes to account taxes.""" + env = api.Environment(cr, SUPERUSER_ID, {}) + l10n_br_coa_charts = env["account.chart.template"].search( + [("parent_id", "=", env.ref("l10n_br_coa.l10n_br_coa_template").id)] + ) + + for l10n_br_coa_chart in l10n_br_coa_charts: + l10n_br_coa_chart.load_fiscal_taxes() diff --git a/l10n_br_account/i18n/l10n_br_account.pot b/l10n_br_account/i18n/l10n_br_account.pot new file mode 100644 index 000000000000..8c98d7ff6a5f --- /dev/null +++ b/l10n_br_account/i18n/l10n_br_account.pot @@ -0,0 +1,885 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_br_account +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.invoice_form +msgid "(-) Discount" +msgstr "" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.invoice_form +msgid "(-) Tax Withholding" +msgstr "" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.invoice_form +msgid "Fiscal Details" +msgstr "" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.fiscal_operation_form +msgid "Account" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_account_chart_template +msgid "Account Chart Template" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_operation__journal_id +#: model_terms:ir.ui.view,arch_db:l10n_br_account.fiscal_operation_search +msgid "Account Journal" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_account_move_reversal +msgid "Account Move Reversal" +msgstr "" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.fiscal_invoice_search +#: model_terms:ir.ui.view,arch_db:l10n_br_account.fiscal_invoice_tree +#: model_terms:ir.ui.view,arch_db:l10n_br_account.invoice_search +msgid "Account Number" +msgstr "" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.fiscal_invoice_line_form +#: model_terms:ir.ui.view,arch_db:l10n_br_account.invoice_form +msgid "Accounting" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields.selection,name:l10n_br_account.selection__account_move__fiscal_operation_type__all +msgid "All" +msgstr "" + +#. module: l10n_br_account +#: model:ir.actions.act_window,name:l10n_br_account.fiscal_invoice_all_action +msgid "All Documents" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__allow_csll_irpj +msgid "Allow Csll Irpj" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__amount_total +msgid "Amount Total" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__amount_untaxed +msgid "Amount Untaxed" +msgstr "" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.invoice_form +msgid "Amounts" +msgstr "" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.l10n_br_account_partner_form +msgid "Brazilian Instant Payment Keys (PIX)" +msgstr "" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.l10n_br_account_tax_form +#: model_terms:ir.ui.view,arch_db:l10n_br_account.l10n_br_account_tax_template_form +msgid "Brazilian Tax" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__cfop_destination +msgid "CFOP Destination" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_cnpj_cpf +msgid "CNPJ" +msgstr "" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.fiscal_invoice_search +#: model_terms:ir.ui.view,arch_db:l10n_br_account.invoice_search +msgid "CNPJ/CPF" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__cofins_cst_code +msgid "COFINS CST Code" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__cofinsst_cst_code +msgid "COFINS ST CST Code" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,help:l10n_br_account.field_account_move__partner_is_company +msgid "Check if the contact is a company, otherwise it is a person" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_id +msgid "Company" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_cnpj_cpf +msgid "Company CNPJ" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_city_id +msgid "Company City" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_country_id +msgid "Company Country" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_district +msgid "Company District" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_legal_name +msgid "Company Legal Name" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_cnae_main_id +msgid "Company Main CNAE" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_inscr_mun +msgid "Company Municipal Tax Number" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_name +msgid "Company Name" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_number +msgid "Company Number" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_phone +msgid "Company Phone" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_state_id +msgid "Company State" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_inscr_est +msgid "Company State Tax Number" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_street +msgid "Company Street" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_street2 +msgid "Company Street2" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_suframa +msgid "Company Suframa" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_tax_framework +msgid "Company Tax Framework" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__partner_company_type +msgid "Company Type" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_zip +msgid "Company ZIP" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_ind_ie_dest +msgid "Contribuinte do ICMS" +msgstr "" + +#. module: l10n_br_account +#: model_terms:ir.actions.act_window,help:l10n_br_account.fiscal_invoice_all_action +#: model_terms:ir.actions.act_window,help:l10n_br_account.fiscal_invoice_in_action +#: model_terms:ir.actions.act_window,help:l10n_br_account.fiscal_invoice_out_action +msgid "Create a new Document" +msgstr "" + +#. module: l10n_br_account +#: model_terms:ir.actions.act_window,help:l10n_br_account.fiscal_invoice_nfe_action +msgid "Create a new NF-e" +msgstr "" + +#. module: l10n_br_account +#: model_terms:ir.actions.act_window,help:l10n_br_account.fiscal_invoice_nfse_action +msgid "Create a new NFS-e" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_bank_statement_line__date_in_out +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__date_in_out +#: model:ir.model.fields,field_description:l10n_br_account.field_account_payment__date_in_out +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document__date_in_out +msgid "Date IN/OUT" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_operation__deductible_taxes +msgid "Deductible Taxes" +msgstr "" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.fiscal_invoice_form +msgid "Devolver" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__discount +msgid "Discount (%)" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_chart_template__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_account_incoterms__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_reversal__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_account_tax__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_account_tax_group__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_account_tax_template__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_ir_model_data__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_base_wizard_mixin__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_cancel_wizard__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_correction_wizard__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_line__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_invalidate_number_wizard__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_operation__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_operation_line__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_tax__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_tax_group__display_name +msgid "Display Name" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_bank_statement_line__document_date +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__document_date +#: model:ir.model.fields,field_description:l10n_br_account.field_account_payment__document_date +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document__document_date +msgid "Document Date" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_bank_statement_line__fiscal_line_ids +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__fiscal_line_ids +#: model:ir.model.fields,field_description:l10n_br_account.field_account_payment__fiscal_line_ids +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document__fiscal_line_ids +msgid "Document Lines" +msgstr "" + +#. module: l10n_br_account +#: model:ir.actions.act_window,name:l10n_br_account.wizard_document_status_act_multi +msgid "Document Status" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__document_type_id +msgid "Document Type" +msgstr "" + +#. module: l10n_br_account +#: code:addons/l10n_br_account/models/account_move.py:0 +#, python-format +msgid "" +"Document without Return Fiscal Operation! \n" +" Force one!" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_bank_statement_line__document_electronic +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__document_electronic +#: model:ir.model.fields,field_description:l10n_br_account.field_account_payment__document_electronic +msgid "Electronic?" +msgstr "" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.invoice_form +msgid "Extra Info" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__ind_final +msgid "Final Consumption Operation" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_l10n_br_fiscal_base_wizard_mixin +msgid "Fiscal Base Wizard Mixin" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_bank_statement_line__fiscal_company_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__fiscal_company_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_payment__fiscal_company_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document__fiscal_company_id +msgid "Fiscal Company" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_bank_statement_line__fiscal_currency_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__fiscal_currency_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_payment__fiscal_currency_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document__fiscal_currency_id +msgid "Fiscal Currency" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_l10n_br_fiscal_document +#: model:ir.model.fields,field_description:l10n_br_account.field_account_bank_statement_line__fiscal_document_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__fiscal_document_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_payment__fiscal_document_id +msgid "Fiscal Document" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_l10n_br_fiscal_document_cancel_wizard +msgid "Fiscal Document Cancel Wizard" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_l10n_br_fiscal_document_correction_wizard +msgid "Fiscal Document Correction Wizard" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_l10n_br_fiscal_document_line +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__fiscal_document_line_id +msgid "Fiscal Document Line" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_bank_statement_line__incoterm_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__incoterm_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_payment__incoterm_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document__incoterm_id +msgid "Fiscal Inconterm" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__fiscal_name +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_line__fiscal_name +msgid "Fiscal Name" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_l10n_br_fiscal_operation +msgid "Fiscal Operation" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_l10n_br_fiscal_operation_line +msgid "Fiscal Operation Line" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_bank_statement_line__fiscal_operation_type +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__fiscal_operation_type +#: model:ir.model.fields,field_description:l10n_br_account.field_account_payment__fiscal_operation_type +msgid "Fiscal Operation Type" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_bank_statement_line__fiscal_partner_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__fiscal_partner_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_payment__fiscal_partner_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document__fiscal_partner_id +msgid "Fiscal Partner" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_bank_statement_line__fiscal_partner_shipping_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__fiscal_partner_shipping_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_payment__fiscal_partner_shipping_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document__fiscal_partner_shipping_id +msgid "Fiscal Partner Shipping" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_operation__fiscal_position_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_operation_line__fiscal_position_id +msgid "Fiscal Position" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__fiscal_price_unit +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_line__fiscal_price_unit +msgid "Fiscal Price Unit" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__fiscal_product_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_line__fiscal_product_id +msgid "Fiscal Product" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__fiscal_genre_code +msgid "Fiscal Product Genre Code" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__fiscal_quantity +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_line__fiscal_quantity +#: model_terms:ir.ui.view,arch_db:l10n_br_account.invoice_form +msgid "Fiscal Quantity" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_l10n_br_fiscal_tax +msgid "Fiscal Tax" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_tax_group__fiscal_tax_group_id +msgid "Fiscal Tax Group" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_tax__fiscal_tax_ids +#: model:ir.model.fields,field_description:l10n_br_account.field_account_tax_template__fiscal_tax_ids +msgid "Fiscal Taxes" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__fiscal_uom_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_line__fiscal_uom_id +msgid "Fiscal UOM" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_bank_statement_line__fiscal_user_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__fiscal_user_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_payment__fiscal_user_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document__fiscal_user_id +msgid "Fiscal User" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_reversal__force_fiscal_operation_id +msgid "Force Fiscal Operation" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__icms_cst_code +msgid "ICMS CST Code" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_chart_template__id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_incoterms__id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_reversal__id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_tax__id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_tax_group__id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_tax_template__id +#: model:ir.model.fields,field_description:l10n_br_account.field_ir_model_data__id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_base_wizard_mixin__id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document__id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_cancel_wizard__id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_correction_wizard__id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_line__id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_invalidate_number_wizard__id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_operation__id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_operation_line__id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_tax__id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_tax_group__id +msgid "ID" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__ipi_cst_code +msgid "IPI CST Code" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,help:l10n_br_account.field_account_move_line__cfop_destination +msgid "Identifies the operation destination." +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields.selection,name:l10n_br_account.selection__account_move__fiscal_operation_type__in +msgid "In" +msgstr "" + +#. module: l10n_br_account +#: model:ir.actions.act_window,name:l10n_br_account.fiscal_invoice_in_action +msgid "Incomming Document" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_account_incoterms +msgid "Incoterms" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,help:l10n_br_account.field_account_move_line__fiscal_tax_line_id +msgid "Indicates that this journal item is a tax line" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,help:l10n_br_account.field_account_move_line__partner_is_public_entity +msgid "" +"Indicates whether the entity in question is a public organization or " +"government-related entity. It encompasses a range of entities such as " +"municipal governments, state-owned enterprises (where the government is the " +"largest shareholder), and other government-controlled organizations." +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,help:l10n_br_account.field_account_bank_statement_line__incoterm_id +#: model:ir.model.fields,help:l10n_br_account.field_account_move__incoterm_id +#: model:ir.model.fields,help:l10n_br_account.field_account_payment__incoterm_id +#: model:ir.model.fields,help:l10n_br_account.field_l10n_br_fiscal_document__incoterm_id +msgid "" +"International Commercial Terms are a series of predefined commercial terms " +"used in international transactions." +msgstr "" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_l10n_br_fiscal_invalidate_number_wizard +msgid "Invalidate Number Wizard" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_base_wizard_mixin__move_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_cancel_wizard__move_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_correction_wizard__move_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_import_wizard_mixin__move_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_status_wizard__move_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_invalidate_number_wizard__move_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_nfe_import_xml__move_id +msgid "Invoice" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__account_line_ids +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_line__account_line_ids +msgid "Invoice Lines" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_bank_statement_line__move_ids +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__move_ids +#: model:ir.model.fields,field_description:l10n_br_account.field_account_payment__move_ids +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document__move_ids +msgid "Invoices" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_account_move +msgid "Journal Entry" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_account_move_line +msgid "Journal Item" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_chart_template____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_account_incoterms____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_reversal____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_account_tax____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_account_tax_group____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_account_tax_template____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_ir_model_data____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_base_wizard_mixin____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_cancel_wizard____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_correction_wizard____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_line____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_invalidate_number_wizard____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_operation____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_operation_line____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_tax____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_tax_group____last_update +msgid "Last Modified on" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_legal_name +#: model_terms:ir.ui.view,arch_db:l10n_br_account.fiscal_invoice_search +#: model_terms:ir.ui.view,arch_db:l10n_br_account.invoice_search +msgid "Legal Name" +msgstr "" + +#. module: l10n_br_account +#: code:addons/l10n_br_account/models/account_move.py:0 +#, python-format +msgid "" +"Line without Return Fiscal Operation! \n" +"\n" +" Please force one! \n" +"{}" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_cnae_main_id +msgid "Main CNAE" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_ir_model_data +msgid "Model Data" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_inscr_mun +msgid "Municipal Tax Number" +msgstr "" + +#. module: l10n_br_account +#: model:ir.actions.act_window,name:l10n_br_account.fiscal_invoice_nfe_action +msgid "NF-e" +msgstr "" + +#. module: l10n_br_account +#: model:ir.actions.act_window,name:l10n_br_account.fiscal_invoice_nfse_action +msgid "NFS-e" +msgstr "" + +#. module: l10n_br_account +#: model_terms:ir.actions.act_window,help:l10n_br_account.fiscal_invoice_all_action +#: model_terms:ir.actions.act_window,help:l10n_br_account.fiscal_invoice_in_action +#: model_terms:ir.actions.act_window,help:l10n_br_account.fiscal_invoice_nfe_action +#: model_terms:ir.actions.act_window,help:l10n_br_account.fiscal_invoice_nfse_action +#: model_terms:ir.actions.act_window,help:l10n_br_account.fiscal_invoice_out_action +msgid "" +"Odoo helps you easily track all activities\n" +" related to a fiscal operation." +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__fiscal_tax_line_id +msgid "Originator Fiscal Tax" +msgstr "" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.invoice_form +msgid "Others" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields.selection,name:l10n_br_account.selection__account_move__fiscal_operation_type__out +msgid "Out" +msgstr "" + +#. module: l10n_br_account +#: model:ir.actions.act_window,name:l10n_br_account.fiscal_invoice_out_action +msgid "Outgoing Document" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__pis_cst_code +msgid "PIS CST Code" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__pisst_cst_code +msgid "PIS ST CST Code" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_id +msgid "Partner" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_city_id +msgid "Partner City" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_country_id +msgid "Partner Country" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_district +msgid "Partner District" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_is_company +msgid "Partner Is Company?" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_name +msgid "Partner Name" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_number +msgid "Partner Number" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_phone +msgid "Partner Phone" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_state_id +msgid "Partner State" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_street +msgid "Partner Street" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_street2 +msgid "Partner Street2" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_zip +msgid "Partner Zip" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__processador_edoc +msgid "Processador documentos eletrônicos" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__partner_is_public_entity +msgid "Public Entity" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_inscr_est +#: model_terms:ir.ui.view,arch_db:l10n_br_account.fiscal_invoice_search +#: model_terms:ir.ui.view,arch_db:l10n_br_account.invoice_search +msgid "State Tax Number" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_suframa +msgid "Suframa" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_account_tax +msgid "Tax" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_tax_framework +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__tax_framework +msgid "Tax Framework" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_account_tax_group +#: model:ir.model,name:l10n_br_account.model_l10n_br_fiscal_tax_group +msgid "Tax Group" +msgstr "" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.invoice_form +msgid "Taxes" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_account_tax_template +msgid "Templates for Taxes" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,help:l10n_br_account.field_account_move__company_legal_name +#: model:ir.model.fields,help:l10n_br_account.field_account_move__partner_legal_name +msgid "Used in fiscal documents" +msgstr "" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.fiscal_invoice_form +msgid "Validate" +msgstr "" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.fiscal_invoice_form +msgid "Voltar para Digitação" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__wh_move_line_id +msgid "WH Account Move Line" +msgstr "" + +#. module: l10n_br_account +#: code:addons/l10n_br_account/models/account_move.py:0 +#, python-format +msgid "" +"You can't set this document number: {} to draft because this document is " +"cancelled in SEFAZ" +msgstr "" + +#. module: l10n_br_account +#: code:addons/l10n_br_account/models/fiscal_document.py:0 +#, python-format +msgid "You cannot delete a fiscal document which is not draft state." +msgstr "" + +#. module: l10n_br_account +#: code:addons/l10n_br_account/models/account_move_line.py:0 +#, python-format +msgid "You cannot edit an invoice related to a withholding entry" +msgstr "" + +#. module: l10n_br_account +#: code:addons/l10n_br_account/models/account_move.py:0 +#, python-format +msgid "You cannot set a document type when the move has no Fiscal Document!" +msgstr "" diff --git a/l10n_br_account/i18n/pt_BR.po b/l10n_br_account/i18n/pt_BR.po new file mode 100644 index 000000000000..62246f933305 --- /dev/null +++ b/l10n_br_account/i18n/pt_BR.po @@ -0,0 +1,1031 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_br_account +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-04-07 09:02+0000\n" +"PO-Revision-Date: 2023-06-10 20:09+0000\n" +"Last-Translator: Adriano Prado \n" +"Language-Team: \n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.invoice_form +msgid "(-) Discount" +msgstr "(-) Desconto" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.invoice_form +msgid "(-) Tax Withholding" +msgstr "(-) Retenção de Imposto" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.invoice_form +msgid "Fiscal Details" +msgstr "Detalhes Fiscais" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.fiscal_operation_form +msgid "Account" +msgstr "Conta" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_account_chart_template +msgid "Account Chart Template" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_operation__journal_id +#: model_terms:ir.ui.view,arch_db:l10n_br_account.fiscal_operation_search +msgid "Account Journal" +msgstr "Diário de Conta" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_account_move_reversal +msgid "Account Move Reversal" +msgstr "Reversão de Movimento Contábil" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.fiscal_invoice_search +#: model_terms:ir.ui.view,arch_db:l10n_br_account.fiscal_invoice_tree +#: model_terms:ir.ui.view,arch_db:l10n_br_account.invoice_search +msgid "Account Number" +msgstr "Número de Conta" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.fiscal_invoice_line_form +#: model_terms:ir.ui.view,arch_db:l10n_br_account.invoice_form +msgid "Accounting" +msgstr "Contabilidade" + +#. module: l10n_br_account +#: model:ir.model.fields.selection,name:l10n_br_account.selection__account_move__fiscal_operation_type__all +msgid "All" +msgstr "Todos" + +#. module: l10n_br_account +#: model:ir.actions.act_window,name:l10n_br_account.fiscal_invoice_all_action +msgid "All Documents" +msgstr "Todos os Documentos" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__allow_csll_irpj +msgid "Allow Csll Irpj" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__amount_total +msgid "Amount Total" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__amount_untaxed +msgid "Amount Untaxed" +msgstr "Total sem Impostos" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.invoice_form +msgid "Amounts" +msgstr "Totais" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.l10n_br_account_partner_form +msgid "Brazilian Instant Payment Keys (PIX)" +msgstr "Chave de Pagamento Instantâneo Brasileiro (PIX)" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.l10n_br_account_tax_form +#: model_terms:ir.ui.view,arch_db:l10n_br_account.l10n_br_account_tax_template_form +msgid "Brazilian Tax" +msgstr "Impostos Brasileiros" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__cfop_destination +msgid "CFOP Destination" +msgstr "CFOP Destino" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_cnpj_cpf +msgid "CNPJ" +msgstr "CNPJ" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.fiscal_invoice_search +#: model_terms:ir.ui.view,arch_db:l10n_br_account.invoice_search +msgid "CNPJ/CPF" +msgstr "CNPJ/CPF" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__cofins_cst_code +msgid "COFINS CST Code" +msgstr "Código CST de COFINS" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__cofinsst_cst_code +msgid "COFINS ST CST Code" +msgstr "Código CST ST COFINS" + +#. module: l10n_br_account +#: model:ir.model.fields,help:l10n_br_account.field_account_move__partner_is_company +msgid "Check if the contact is a company, otherwise it is a person" +msgstr "Verifique se o conta é uma empresa, senão é uma pessoa" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_id +msgid "Company" +msgstr "Empresa" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_cnpj_cpf +msgid "Company CNPJ" +msgstr "CNPJ da Empresa" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_city_id +msgid "Company City" +msgstr "Cidade da empresa" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_country_id +msgid "Company Country" +msgstr "País da Empresa" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_district +msgid "Company District" +msgstr "Bairro da Empresa" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_legal_name +msgid "Company Legal Name" +msgstr "Razão Social da Empresa" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_cnae_main_id +msgid "Company Main CNAE" +msgstr "CNAE Principal da Empresa" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_inscr_mun +msgid "Company Municipal Tax Number" +msgstr "Número de Inscrição Municipal da Empresa" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_name +msgid "Company Name" +msgstr "Nome da Empresa" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_number +msgid "Company Number" +msgstr "Número da Empresa" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_phone +msgid "Company Phone" +msgstr "Telefone da Empresa" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_state_id +msgid "Company State" +msgstr "Estado da Empresa" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_inscr_est +msgid "Company State Tax Number" +msgstr "Número da Inscrição Estadual da Empresa" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_street +msgid "Company Street" +msgstr "Rua da Empresa" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_street2 +msgid "Company Street2" +msgstr "Completo do Endereço da Empresa" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_suframa +msgid "Company Suframa" +msgstr "Suframa da Empresa" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_tax_framework +msgid "Company Tax Framework" +msgstr "Estrutura Tributária da Empresa" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__partner_company_type +msgid "Company Type" +msgstr "Tipo de Empresa" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__company_zip +msgid "Company ZIP" +msgstr "Cep da Empresa" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_ind_ie_dest +msgid "Contribuinte do ICMS" +msgstr "Contribuinte do ICMS" + +#. module: l10n_br_account +#: model_terms:ir.actions.act_window,help:l10n_br_account.fiscal_invoice_all_action +#: model_terms:ir.actions.act_window,help:l10n_br_account.fiscal_invoice_in_action +#: model_terms:ir.actions.act_window,help:l10n_br_account.fiscal_invoice_out_action +msgid "Create a new Document" +msgstr "Criar um novo documento" + +#. module: l10n_br_account +#: model_terms:ir.actions.act_window,help:l10n_br_account.fiscal_invoice_nfe_action +msgid "Create a new NF-e" +msgstr "Criar uma nova NF-e" + +#. module: l10n_br_account +#: model_terms:ir.actions.act_window,help:l10n_br_account.fiscal_invoice_nfse_action +msgid "Create a new NFS-e" +msgstr "Criar uma nova NFS-e" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_bank_statement_line__date_in_out +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__date_in_out +#: model:ir.model.fields,field_description:l10n_br_account.field_account_payment__date_in_out +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document__date_in_out +msgid "Date IN/OUT" +msgstr "Data de Entrada/Saída" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_operation__deductible_taxes +msgid "Deductible Taxes" +msgstr "Impostos Dedutíveis" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.fiscal_invoice_form +msgid "Devolver" +msgstr "Devolver" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__discount +msgid "Discount (%)" +msgstr "Desconto (%)" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_chart_template__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_account_incoterms__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_reversal__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_account_tax__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_account_tax_group__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_account_tax_template__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_ir_model_data__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_base_wizard_mixin__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_cancel_wizard__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_correction_wizard__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_line__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_invalidate_number_wizard__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_operation__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_operation_line__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_tax__display_name +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_tax_group__display_name +msgid "Display Name" +msgstr "Nome Exibido" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_bank_statement_line__document_date +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__document_date +#: model:ir.model.fields,field_description:l10n_br_account.field_account_payment__document_date +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document__document_date +msgid "Document Date" +msgstr "Data do Documento" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_bank_statement_line__fiscal_line_ids +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__fiscal_line_ids +#: model:ir.model.fields,field_description:l10n_br_account.field_account_payment__fiscal_line_ids +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document__fiscal_line_ids +msgid "Document Lines" +msgstr "Linhas de documentos" + +#. module: l10n_br_account +#: model:ir.actions.act_window,name:l10n_br_account.wizard_document_status_act_multi +msgid "Document Status" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__document_type_id +msgid "Document Type" +msgstr "Tipo do Documento" + +#. module: l10n_br_account +#: code:addons/l10n_br_account/models/account_move.py:0 +#, python-format +msgid "" +"Document without Return Fiscal Operation! \n" +" Force one!" +msgstr "" +"Documento sem Operação Fiscal de Retorno! \n" +" Force uma!" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_bank_statement_line__document_electronic +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__document_electronic +#: model:ir.model.fields,field_description:l10n_br_account.field_account_payment__document_electronic +msgid "Electronic?" +msgstr "Eletrônico?" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.invoice_form +msgid "Extra Info" +msgstr "Informação Extra" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__ind_final +msgid "Final Consumption Operation" +msgstr "Operação de Consumo Final" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_l10n_br_fiscal_base_wizard_mixin +#, fuzzy +msgid "Fiscal Base Wizard Mixin" +msgstr "Assistente de Base Fiscal Mixin" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_bank_statement_line__fiscal_company_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__fiscal_company_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_payment__fiscal_company_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document__fiscal_company_id +msgid "Fiscal Company" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_bank_statement_line__fiscal_currency_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__fiscal_currency_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_payment__fiscal_currency_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document__fiscal_currency_id +msgid "Fiscal Currency" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_l10n_br_fiscal_document +#: model:ir.model.fields,field_description:l10n_br_account.field_account_bank_statement_line__fiscal_document_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__fiscal_document_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_payment__fiscal_document_id +msgid "Fiscal Document" +msgstr "Documento Fiscal" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_l10n_br_fiscal_document_cancel_wizard +msgid "Fiscal Document Cancel Wizard" +msgstr "Assistente de Cancelamento de Documento Fiscal" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_l10n_br_fiscal_document_correction_wizard +msgid "Fiscal Document Correction Wizard" +msgstr "Assistente de Correção de Documento Fiscal" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_l10n_br_fiscal_document_line +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__fiscal_document_line_id +msgid "Fiscal Document Line" +msgstr "Linha do Documento Fiscal" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_bank_statement_line__incoterm_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__incoterm_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_payment__incoterm_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document__incoterm_id +msgid "Fiscal Inconterm" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__fiscal_name +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_line__fiscal_name +msgid "Fiscal Name" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_l10n_br_fiscal_operation +msgid "Fiscal Operation" +msgstr "Operação Fiscal" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_l10n_br_fiscal_operation_line +msgid "Fiscal Operation Line" +msgstr "Linha de Operação Fiscal" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_bank_statement_line__fiscal_operation_type +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__fiscal_operation_type +#: model:ir.model.fields,field_description:l10n_br_account.field_account_payment__fiscal_operation_type +msgid "Fiscal Operation Type" +msgstr "Tipo de Operação Fiscal" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_bank_statement_line__fiscal_partner_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__fiscal_partner_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_payment__fiscal_partner_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document__fiscal_partner_id +msgid "Fiscal Partner" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_bank_statement_line__fiscal_partner_shipping_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__fiscal_partner_shipping_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_payment__fiscal_partner_shipping_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document__fiscal_partner_shipping_id +msgid "Fiscal Partner Shipping" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_operation__fiscal_position_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_operation_line__fiscal_position_id +msgid "Fiscal Position" +msgstr "Posição Fiscal" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__fiscal_price_unit +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_line__fiscal_price_unit +msgid "Fiscal Price Unit" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__fiscal_product_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_line__fiscal_product_id +msgid "Fiscal Product" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__fiscal_genre_code +msgid "Fiscal Product Genre Code" +msgstr "Código do Gênero Fiscal do Produto" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__fiscal_quantity +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_line__fiscal_quantity +#: model_terms:ir.ui.view,arch_db:l10n_br_account.invoice_form +msgid "Fiscal Quantity" +msgstr "Quantidade Fiscal" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_l10n_br_fiscal_tax +msgid "Fiscal Tax" +msgstr "Impostos" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_tax_group__fiscal_tax_group_id +msgid "Fiscal Tax Group" +msgstr "Grupos de impostos" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_tax__fiscal_tax_ids +#: model:ir.model.fields,field_description:l10n_br_account.field_account_tax_template__fiscal_tax_ids +msgid "Fiscal Taxes" +msgstr "Impostos Fiscais" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__fiscal_uom_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_line__fiscal_uom_id +msgid "Fiscal UOM" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_bank_statement_line__fiscal_user_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__fiscal_user_id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_payment__fiscal_user_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document__fiscal_user_id +msgid "Fiscal User" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_reversal__force_fiscal_operation_id +msgid "Force Fiscal Operation" +msgstr "Forçar Operação Fiscal" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__icms_cst_code +msgid "ICMS CST Code" +msgstr "Código CST do ICMS" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_chart_template__id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_incoterms__id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_reversal__id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_tax__id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_tax_group__id +#: model:ir.model.fields,field_description:l10n_br_account.field_account_tax_template__id +#: model:ir.model.fields,field_description:l10n_br_account.field_ir_model_data__id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_base_wizard_mixin__id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document__id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_cancel_wizard__id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_correction_wizard__id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_line__id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_invalidate_number_wizard__id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_operation__id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_operation_line__id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_tax__id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_tax_group__id +msgid "ID" +msgstr "ID" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__ipi_cst_code +msgid "IPI CST Code" +msgstr "Código CST de IPI" + +#. module: l10n_br_account +#: model:ir.model.fields,help:l10n_br_account.field_account_move_line__cfop_destination +msgid "Identifies the operation destination." +msgstr "Identifica o destino da operação." + +#. module: l10n_br_account +#: model:ir.model.fields.selection,name:l10n_br_account.selection__account_move__fiscal_operation_type__in +msgid "In" +msgstr "Entrada" + +#. module: l10n_br_account +#: model:ir.actions.act_window,name:l10n_br_account.fiscal_invoice_in_action +msgid "Incomming Document" +msgstr "Documento de Entrada" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_account_incoterms +msgid "Incoterms" +msgstr "Incoterms" + +#. module: l10n_br_account +#: model:ir.model.fields,help:l10n_br_account.field_account_move_line__fiscal_tax_line_id +msgid "Indicates that this journal item is a tax line" +msgstr "Indica que este item de diário é uma linha de imposto" + +#. module: l10n_br_account +#: model:ir.model.fields,help:l10n_br_account.field_account_move_line__partner_is_public_entity +msgid "" +"Indicates whether the entity in question is a public organization or " +"government-related entity. It encompasses a range of entities such as " +"municipal governments, state-owned enterprises (where the government is the " +"largest shareholder), and other government-controlled organizations." +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,help:l10n_br_account.field_account_bank_statement_line__incoterm_id +#: model:ir.model.fields,help:l10n_br_account.field_account_move__incoterm_id +#: model:ir.model.fields,help:l10n_br_account.field_account_payment__incoterm_id +#: model:ir.model.fields,help:l10n_br_account.field_l10n_br_fiscal_document__incoterm_id +msgid "" +"International Commercial Terms are a series of predefined commercial terms " +"used in international transactions." +msgstr "" +"International Commercial Terms são uma série de termos comerciais " +"predefinidos utilizados em transações internacionais." + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_l10n_br_fiscal_invalidate_number_wizard +msgid "Invalidate Number Wizard" +msgstr "Assistente de Número Invalidado" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_base_wizard_mixin__move_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_cancel_wizard__move_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_correction_wizard__move_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_import_wizard_mixin__move_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_status_wizard__move_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_invalidate_number_wizard__move_id +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_nfe_import_xml__move_id +msgid "Invoice" +msgstr "Fatura" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__account_line_ids +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_line__account_line_ids +msgid "Invoice Lines" +msgstr "Linhas da Fatura" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_bank_statement_line__move_ids +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__move_ids +#: model:ir.model.fields,field_description:l10n_br_account.field_account_payment__move_ids +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document__move_ids +msgid "Invoices" +msgstr "Faturas" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_account_move +msgid "Journal Entry" +msgstr "Lançamento de Diário" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_account_move_line +msgid "Journal Item" +msgstr "Item de Diário" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_chart_template____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_account_incoterms____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_reversal____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_account_tax____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_account_tax_group____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_account_tax_template____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_ir_model_data____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_base_wizard_mixin____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_cancel_wizard____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_correction_wizard____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_document_line____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_invalidate_number_wizard____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_operation____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_operation_line____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_tax____last_update +#: model:ir.model.fields,field_description:l10n_br_account.field_l10n_br_fiscal_tax_group____last_update +msgid "Last Modified on" +msgstr "Última Modificação em" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_legal_name +#: model_terms:ir.ui.view,arch_db:l10n_br_account.fiscal_invoice_search +#: model_terms:ir.ui.view,arch_db:l10n_br_account.invoice_search +msgid "Legal Name" +msgstr "Razão Social" + +#. module: l10n_br_account +#: code:addons/l10n_br_account/models/account_move.py:0 +#, python-format +msgid "" +"Line without Return Fiscal Operation! \n" +"\n" +" Please force one! \n" +"{}" +msgstr "" +"Linha sem Operação Fiscal de Retorno! \n" +"\n" +" Por favor force uma! \n" +"{}" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_cnae_main_id +msgid "Main CNAE" +msgstr "CNAE Principal" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_ir_model_data +msgid "Model Data" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_inscr_mun +msgid "Municipal Tax Number" +msgstr "Numero Inscrição Municipal" + +#. module: l10n_br_account +#: model:ir.actions.act_window,name:l10n_br_account.fiscal_invoice_nfe_action +msgid "NF-e" +msgstr "NF-e" + +#. module: l10n_br_account +#: model:ir.actions.act_window,name:l10n_br_account.fiscal_invoice_nfse_action +msgid "NFS-e" +msgstr "NFS-e" + +#. module: l10n_br_account +#: model_terms:ir.actions.act_window,help:l10n_br_account.fiscal_invoice_all_action +#: model_terms:ir.actions.act_window,help:l10n_br_account.fiscal_invoice_in_action +#: model_terms:ir.actions.act_window,help:l10n_br_account.fiscal_invoice_nfe_action +#: model_terms:ir.actions.act_window,help:l10n_br_account.fiscal_invoice_nfse_action +#: model_terms:ir.actions.act_window,help:l10n_br_account.fiscal_invoice_out_action +msgid "" +"Odoo helps you easily track all activities\n" +" related to a fiscal operation." +msgstr "" +"Odoo ajuda você rastrear facilmente todas as atividades\n" +" relacionadas com a operação fiscal." + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__fiscal_tax_line_id +#, fuzzy +msgid "Originator Fiscal Tax" +msgstr "Origem do tributo fiscal" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.invoice_form +msgid "Others" +msgstr "Outros" + +#. module: l10n_br_account +#: model:ir.model.fields.selection,name:l10n_br_account.selection__account_move__fiscal_operation_type__out +msgid "Out" +msgstr "Saída" + +#. module: l10n_br_account +#: model:ir.actions.act_window,name:l10n_br_account.fiscal_invoice_out_action +msgid "Outgoing Document" +msgstr "Documento de Saída" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__pis_cst_code +msgid "PIS CST Code" +msgstr "Código CST do PIS" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__pisst_cst_code +msgid "PIS ST CST Code" +msgstr "Código CST do PIS ST" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_id +msgid "Partner" +msgstr "Parceiro" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_city_id +msgid "Partner City" +msgstr "Cidade do Parceiro" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_country_id +msgid "Partner Country" +msgstr "País Parceiro" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_district +msgid "Partner District" +msgstr "Bairro do Parceiro" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_is_company +msgid "Partner Is Company?" +msgstr "Parceiro é Empresa?" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_name +msgid "Partner Name" +msgstr "Nome do Parceiro" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_number +msgid "Partner Number" +msgstr "Número do parceiro" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_phone +msgid "Partner Phone" +msgstr "Telefone Parceiro" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_state_id +msgid "Partner State" +msgstr "Estado do Parceiro" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_street +msgid "Partner Street" +msgstr "Rua do Parceiro" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_street2 +msgid "Partner Street2" +msgstr "Complemento da Rua do Parceiro" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_zip +msgid "Partner Zip" +msgstr "Cep do Parceiro" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__processador_edoc +msgid "Processador documentos eletrônicos" +msgstr "Processador documentos eletrônicos" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__partner_is_public_entity +msgid "Public Entity" +msgstr "" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_inscr_est +#: model_terms:ir.ui.view,arch_db:l10n_br_account.fiscal_invoice_search +#: model_terms:ir.ui.view,arch_db:l10n_br_account.invoice_search +msgid "State Tax Number" +msgstr "Número da Inscrição Estadual" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_suframa +msgid "Suframa" +msgstr "Suframa" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_account_tax +msgid "Tax" +msgstr "Imposto" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move__partner_tax_framework +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__tax_framework +msgid "Tax Framework" +msgstr "Estrutura Tributária" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_account_tax_group +#: model:ir.model,name:l10n_br_account.model_l10n_br_fiscal_tax_group +msgid "Tax Group" +msgstr "Grupo de Imposto" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.invoice_form +msgid "Taxes" +msgstr "Impostos" + +#. module: l10n_br_account +#: model:ir.model,name:l10n_br_account.model_account_tax_template +msgid "Templates for Taxes" +msgstr "Modelos para Impostos" + +#. module: l10n_br_account +#: model:ir.model.fields,help:l10n_br_account.field_account_move__company_legal_name +#: model:ir.model.fields,help:l10n_br_account.field_account_move__partner_legal_name +msgid "Used in fiscal documents" +msgstr "Usado em documentos fiscais" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.fiscal_invoice_form +msgid "Validate" +msgstr "Validar" + +#. module: l10n_br_account +#: model_terms:ir.ui.view,arch_db:l10n_br_account.fiscal_invoice_form +msgid "Voltar para Digitação" +msgstr "Voltar para Digitação" + +#. module: l10n_br_account +#: model:ir.model.fields,field_description:l10n_br_account.field_account_move_line__wh_move_line_id +msgid "WH Account Move Line" +msgstr "Linha de Movimento Contábil RET" + +#. module: l10n_br_account +#: code:addons/l10n_br_account/models/account_move.py:0 +#, python-format +msgid "" +"You can't set this document number: {} to draft because this document is " +"cancelled in SEFAZ" +msgstr "" +"Você não pode definir este número de documento: {} para rascunho pois este " +"documento está cancelado no SEFAZ" + +#. module: l10n_br_account +#: code:addons/l10n_br_account/models/fiscal_document.py:0 +#, python-format +msgid "You cannot delete a fiscal document which is not draft state." +msgstr "" +"Você não pode excluir um documento fiscal que não esteja no estado de " +"rascunho." + +#. module: l10n_br_account +#: code:addons/l10n_br_account/models/account_move_line.py:0 +#, python-format +msgid "You cannot edit an invoice related to a withholding entry" +msgstr "" +"Você não pode editar um documento relacionado a um lançamento de retenção" + +#. module: l10n_br_account +#: code:addons/l10n_br_account/models/account_move.py:0 +#, python-format +msgid "You cannot set a document type when the move has no Fiscal Document!" +msgstr "" + +#~ msgid "Currency" +#~ msgstr "Moeda" + +#~ msgid "Incoterm" +#~ msgstr "Incoterm" + +#~ msgid "Name" +#~ msgstr "Nome" + +#~ msgid "Price Unit" +#~ msgstr "Preço Unitário" + +#~ msgid "Product" +#~ msgstr "Produto" + +#~ msgid "Quantity" +#~ msgstr "Quantidade" + +#~ msgid "Shipping Address" +#~ msgstr "Endereço de Entrega" + +#~ msgid "UOM" +#~ msgstr "UDM" + +#~ msgid "User" +#~ msgstr "Usuário" + +#~ msgid "Amount Taxed" +#~ msgstr "Montante Tributado" + +#~ msgid "Account Document Line Mixin" +#~ msgstr "Mixin de Linha de Documento da Conta" + +#~ msgid "Active" +#~ msgstr "Ativo" + +#~ msgid "Companies" +#~ msgstr "Empresas" + +#, python-format +#~ msgid "" +#~ "Document line dummy not found. Please contact your system administrator." +#~ msgstr "" +#~ "Linha do documento fictício não encontrada. Entre em contato com o " +#~ "administrador do sistema." + +#~ msgid "Fiscal Dummy Document" +#~ msgstr "Documento Fictício Fiscal" + +#~ msgid "Adapted Legal Name" +#~ msgstr "Razão Social" + +#~ msgid "Adapted State Tax Number" +#~ msgstr "Inscrição Estadual" + +#~ msgid "Document Code" +#~ msgstr "Código do Documento" + +#~ msgid "Credit Note" +#~ msgstr "Anotação de crédito" + +#~ msgid "Document line without Operation !" +#~ msgstr "Linha do documento s/ Operação !" + +#~ msgid "Document without Operation !" +#~ msgstr "Documento s/ Operação!" + +#~ msgid "Fiscal Operation: There is not Return Operation for %s !" +#~ msgstr "" +#~ "Operação Fiscal: Não esta definida a operação de devolução para %s !" + +#~ msgid "Invoice Line" +#~ msgstr "Linha da Fatura" + +#~ msgid "Outros Custos" +#~ msgstr "Outros Custos" + +#~ msgid "Tax Account" +#~ msgstr "Conta de imposto" + +#~ msgid "Tax Account on Credit Notes" +#~ msgstr "Conta de Impostos em Notas de Crédito" + +#~ msgid "l10n_br_account.tax.template" +#~ msgstr "l10n_br_account.tax.template" + +#~ msgid "Inscr. Estadual" +#~ msgstr "Inscr. Estadual" + +#~ msgid "Razão Social" +#~ msgstr "Razão Social" + +#~ msgid "Fiscal Doc Discount Value" +#~ msgstr "Valor do desconto do documento fiscal" + +#~ msgid "Fiscal Doc Company" +#~ msgstr "Empresa do documento fiscal" + +#~ msgid "Fiscal Doc Currency" +#~ msgstr "Moeda do documento fiscal" + +#~ msgid "Fiscal Doc Date" +#~ msgstr "Data do documento fiscal" + +#~ msgid "Fiscal Doc Name" +#~ msgstr "Nome do documento fiscal" + +#~ msgid "Fiscal Doc Partner" +#~ msgstr "Parceiro do documento fiscal" + +#~ msgid "Fiscal Doc Price Unit" +#~ msgstr "Preço unitário do documento fiscal" + +#~ msgid "Fiscal Doc Product" +#~ msgstr "Produto do documento fiscal" + +#~ msgid "Fiscal Doc Quantity" +#~ msgstr "Quantidade do documento fiscal" + +#~ msgid "Fiscal Doc State" +#~ msgstr "Situação do documento fiscal" + +#~ msgid "Fiscal Doc UOM" +#~ msgstr "UOM do documento fiscal" diff --git a/l10n_br_account/images/l10n_br-hover.png b/l10n_br_account/images/l10n_br-hover.png new file mode 100644 index 000000000000..4d33b9bb7c63 Binary files /dev/null and b/l10n_br_account/images/l10n_br-hover.png differ diff --git a/l10n_br_account/images/l10n_br.png b/l10n_br_account/images/l10n_br.png new file mode 100644 index 000000000000..d65fda9da278 Binary files /dev/null and b/l10n_br_account/images/l10n_br.png differ diff --git a/l10n_br_account/models/__init__.py b/l10n_br_account/models/__init__.py new file mode 100644 index 000000000000..b9e7070fc96b --- /dev/null +++ b/l10n_br_account/models/__init__.py @@ -0,0 +1,16 @@ +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from . import account_chart_template +from . import account_tax_group +from . import fiscal_tax_group +from . import account_tax_template +from . import account_tax +from . import fiscal_tax +from . import fiscal_operation +from . import fiscal_operation_line +from . import account_move +from . import account_move_line +from . import fiscal_document +from . import fiscal_document_line +from . import account_incoterms +from . import ir_model_data diff --git a/l10n_br_account/models/account_chart_template.py b/l10n_br_account/models/account_chart_template.py new file mode 100644 index 000000000000..09aacf635d11 --- /dev/null +++ b/l10n_br_account/models/account_chart_template.py @@ -0,0 +1,70 @@ +# Copyright (C) 2019 Renato Lima - Akretion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models + + +class AccountChartTemplate(models.Model): + _inherit = "account.chart.template" + + def _load(self, company): + """ + Avoid to populate account_sale/purchase_tax_id + with tax that cannot be applied in Brazil and confuses the users. + """ + res = super()._load(company) + if self.parent_id and self.parent_id == self.env.ref( + "l10n_br_coa.l10n_br_coa_template" + ): + company.account_sale_tax_id = None + company.account_purchase_tax_id = None + return res + + def _load_template( + self, company, code_digits=None, account_ref=None, taxes_ref=None + ): + """ + If the CoA is installed before l10n_br_account, at least we trigger + load_fiscal_taxes from the l10n_br_account/hooks.py for the demo CoA's. + With this override, we also ensure these demo CoA or any custom CoA + will get its account taxes properly linked to fiscal taxes when it is + installed after l10n_br_account. + """ + self.ensure_one() + account_ref, taxes_ref = super()._load_template( + company, code_digits, account_ref, taxes_ref + ) + + if self.parent_id and self.parent_id == self.env.ref( + "l10n_br_coa.l10n_br_coa_template" + ): + self.load_fiscal_taxes() + return account_ref, taxes_ref + + def load_fiscal_taxes(self): + """ + Relate account taxes with fiscal taxes to enable the Brazilian + tax engine to kick in with the installed chart of account. + """ + for coa_tpl in self: + companies = self.env["res.company"].search( + [("chart_template_id", "=", coa_tpl.id)] + ) + + for company in companies: + taxes = self.env["account.tax"].search( + [("company_id", "=", company.id)] + ) + + for tax in taxes: + if tax.get_external_id(): + tax_ref = tax.get_external_id().get(tax.id) + ref_module, ref_name = tax_ref.split(".") + ref_name = ref_name.replace(str(company.id) + "_", "") + template_source_ref = ".".join(["l10n_br_coa", ref_name]) + template_source = self.env.ref(template_source_ref) + tax_source_ref = ".".join([ref_module, ref_name]) + tax_template = self.env.ref(tax_source_ref) + tax.fiscal_tax_ids = ( + tax_template.fiscal_tax_ids + ) = template_source.fiscal_tax_ids diff --git a/l10n_br_account/models/account_incoterms.py b/l10n_br_account/models/account_incoterms.py new file mode 100644 index 000000000000..b87610d50191 --- /dev/null +++ b/l10n_br_account/models/account_incoterms.py @@ -0,0 +1,26 @@ +# Copyright (C) 2021-Today - Akretion (). +# @author Magno Costa +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models + + +class AccountIncoterms(models.Model): + _inherit = "account.incoterms" + + def name_get(self): + # No Brasil muitas pessoas conhecem os tipos de frete mais pelo + # Codigo do que pela descrição, por isso aqui está sendo feito + # "Codigo - Descrição" ex.: + # CIF - Custo, Seguro e Frete; FOB - Gratis a Bordo, etc + result = [] + for record in self: + name = record.name + # Caso o name seja muito grande ao mostrar o campo na + # visão acaba ficando fora da tela o que dificulta a + # visualização, ao clicar em Pesquisar é mostrado o + # name completo + if len(record.name) > 150: + name = record.name[:150] + " ..." + result.append((record.id, "%s - %s" % (record.code, name))) + return result diff --git a/l10n_br_account/models/account_journal.py b/l10n_br_account/models/account_journal.py new file mode 100644 index 000000000000..db27b228064f --- /dev/null +++ b/l10n_br_account/models/account_journal.py @@ -0,0 +1,12 @@ +# Copyright (C) 2009 - TODAY Renato Lima - Akretion +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import fields, models + + +class AccountJournal(models.Model): + _inherit = "account.journal" + + revenue_expense = fields.Boolean( + string="Gera Financeiro", + ) diff --git a/l10n_br_account/models/account_move.py b/l10n_br_account/models/account_move.py new file mode 100644 index 000000000000..66e224389fee --- /dev/null +++ b/l10n_br_account/models/account_move.py @@ -0,0 +1,775 @@ +# Copyright (C) 2009 - TODAY Renato Lima - Akretion +# Copyright (C) 2019 - TODAY Raphaël Valyi - Akretion +# Copyright (C) 2020 - TODAY Luis Felipe Mileo - KMEE +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + + +from contextlib import contextmanager + +from odoo import _, api, fields, models +from odoo.exceptions import UserError +from odoo.tests.common import Form +from odoo.tools import frozendict, mute_logger + +from odoo.addons.l10n_br_fiscal.constants.fiscal import ( + DOCUMENT_ISSUER_COMPANY, + DOCUMENT_ISSUER_PARTNER, + FISCAL_IN_OUT_ALL, + FISCAL_OUT, + MODELO_FISCAL_NFE, + SITUACAO_EDOC_AUTORIZADA, + SITUACAO_EDOC_CANCELADA, + SITUACAO_EDOC_EM_DIGITACAO, +) + +MOVE_TO_OPERATION = { + "out_invoice": "out", + "in_invoice": "in", + "out_refund": "in", + "in_refund": "out", + "out_receipt": "out", + "in_receipt": "in", +} + +REFUND_TO_OPERATION = { + "out_invoice": "in", + "in_invoice": "out", + "out_refund": "out", + "in_refund": "in", +} + +FISCAL_TYPE_REFUND = { + "out": ["purchase_refund", "in_return"], + "in": ["sale_refund", "out_return"], +} + +MOVE_TAX_USER_TYPE = { + "out_invoice": "sale", + "in_invoice": "purchase", + "out_refund": "sale", + "in_refund": "purchase", +} + +# l10n_br_fiscal.document field names that are shadowed +# by account.move fields: +SHADOWED_FIELDS = ["company_id", "currency_id", "user_id", "partner_id"] + + +class InheritsCheckMuteLogger(mute_logger): + """ + Mute the Model#_inherits_check warning + because the _inherits field is not required. + (some account.move may have no fiscal document) + """ + + def filter(self, record): + msg = record.getMessage() + if "Field definition for _inherits reference" in msg: + return 0 + return super().filter(record) + + +class AccountMove(models.Model): + _name = "account.move" + _inherit = [ + _name, + "l10n_br_fiscal.document.move.mixin", + ] + + # an account.move has normally 0 or 1 related fiscal document: + # - 0 when it is not related to a Brazilian company for instance. + # - 1 otherwise (usually). In this case the _inherits system + # makes it easy to edit all the fiscal document (lines) fields + # through the account.move form. + # in some rare cases an account.move may have several fiscal + # documents (1 on each account.move.line). In this case + # fiscal_document_id might be used only to sync the "main" fiscal + # document (or the one currently imported or edited). In this case, + # fiscal_document_ids contains all the line fiscal documents. + _inherits = {"l10n_br_fiscal.document": "fiscal_document_id"} + + _order = "date DESC, name DESC" + + document_electronic = fields.Boolean( + related="document_type_id.electronic", + string="Electronic?", + ) + + fiscal_document_id = fields.Many2one( + comodel_name="l10n_br_fiscal.document", + string="Fiscal Document", + copy=False, + ondelete="cascade", + store=True, + compute="_compute_fiscal_document_id", + ) + + fiscal_document_ids = fields.One2many( + comodel_name="l10n_br_fiscal.document", + string="Fiscal Documents", + compute="_compute_fiscal_document_ids", + help="""In some rare cases (NFS-e, CT-e...) a single account.move + may have several different fiscal documents related to its account.move.lines. + """, + ) + + fiscal_operation_type = fields.Selection( + selection=FISCAL_IN_OUT_ALL, + related=None, + compute="_compute_fiscal_operation_type", + ) + + @api.onchange("document_type_id") + def _inverse_document_type_id(self): + if (self.document_type_id and not self.fiscal_document_id) or ( + not self.document_type_id and self.fiscal_document_id + ): + self.env.add_to_compute(self._fields["fiscal_document_id"], self) + + def _compute_fiscal_document_id(self): + for move in self: + if move.document_type_id and not move.fiscal_document_id: + fiscal_doc_vals = {} + for field in self._shadowed_fields(): + fiscal_doc_vals[f"fiscal_{field}"] = getattr(move, field) + move.fiscal_document_id = ( + self.env["l10n_br_fiscal.document"].create(fiscal_doc_vals).id + ) + elif not move.document_type_id and move.fiscal_document_id: + bad_fiscal_doc = move.fiscal_document_id + move.fiscal_document_id = False + bad_fiscal_doc.action_document_cancel() + + @api.constrains("fiscal_document_id", "document_type_id") + def _check_fiscal_document_type(self): + for rec in self: + if rec.document_type_id and not rec.fiscal_document_id: + raise UserError( + _( + "You cannot set a document type when the move has no Fiscal Document!" + ) + ) + + @api.depends("line_ids", "invoice_line_ids") + def _compute_fiscal_document_ids(self): + for move in self: + docs = set() + for line in move.invoice_line_ids: + docs.add(line.document_id.id) + move.fiscal_document_ids = list(docs) + + @api.depends("move_type", "fiscal_operation_id") + def _compute_fiscal_operation_type(self): + for inv in self: + if inv.move_type == "entry": + # if it is a Journal Entry there is nothing to do. + inv.fiscal_operation_type = False + continue + if inv.fiscal_operation_id: + inv.fiscal_operation_type = ( + inv.fiscal_operation_id.fiscal_operation_type + ) + else: + inv.fiscal_operation_type = MOVE_TO_OPERATION[inv.move_type] + + def _get_amount_lines(self): + """Get object lines instances used to compute fields""" + return self.mapped("invoice_line_ids") + + @api.model + def _inherits_check(self): + """ + Overriden to avoid the super method to set the fiscal_document_id + field as required (because some account.move may not have any fiscal document). + """ + with InheritsCheckMuteLogger("odoo.models"): # mute spurious warnings + res = super()._inherits_check() + field = self._fields.get("fiscal_document_id") + field.required = False # unset the required = True assignement + return res + + @api.model + def _shadowed_fields(self): + """Return the list of shadowed fields that are synchronized + from account.move.""" + return SHADOWED_FIELDS + + @api.model + def _inject_shadowed_fields(self, vals_list): + for vals in vals_list: + for field in self._shadowed_fields(): + if field in vals: + vals["fiscal_%s" % (field,)] = vals[field] + + def ensure_one_doc(self): + self.ensure_one() + if len(self.fiscal_document_ids) > 1: + raise UserError( + _( + "More than 1 fiscal document!" + "You should open the fiscal view" + "and perform the action on each document!" + ) + ) + + @api.model + def default_get(self, fields_list): + defaults = super().default_get(fields_list) + move_type = self.env.context.get("default_move_type", "out_invoice") + if not move_type == "entry": + defaults["fiscal_operation_type"] = MOVE_TO_OPERATION[move_type] + if defaults["fiscal_operation_type"] == FISCAL_OUT: + defaults["issuer"] = DOCUMENT_ISSUER_COMPANY + else: + defaults["issuer"] = DOCUMENT_ISSUER_PARTNER + return defaults + + @api.model + def _get_view(self, view_id=None, view_type="form", **options): + arch, view = super()._get_view(view_id, view_type, **options) + if self.env.company.country_id.code != "BR": + return arch, view + if view_type == "form": + view = self.env["ir.ui.view"] + + if view_id == self.env.ref("l10n_br_account.fiscal_invoice_form").id: + invoice_line_form_id = self.env.ref( + "l10n_br_account.fiscal_invoice_line_form" + ).id + sub_form_node, _sub_view = self.env["account.move.line"]._get_view( + view_id=invoice_line_form_id, view_type="form" + ) + self.env["account.move.line"].inject_fiscal_fields(sub_form_node) + + # TODO FIXME test this part: + for original_sub_form_node in arch.xpath( + "//field[@name='invoice_line_ids']/form" + ): + parent = original_sub_form_node.parent + parent.remove(original_sub_form_node) + parent.append(sub_form_node) + + else: + for sub_form_node in arch.xpath( + "//field[@name='invoice_line_ids']/form" + ): + self.env["account.move.line"].inject_fiscal_fields(sub_form_node) + for sub_form_node in arch.xpath("//field[@name='line_ids']/tree"): + self.env["account.move.line"].inject_fiscal_fields(sub_form_node) + # TODO kanban?? + # for sub_form_node in arch.xpath("//field[@name='line_ids']/kanban"): + # self.env["account.move.line"].inject_fiscal_fields(sub_form_node) + # + + return arch, view + + @api.depends( + "line_ids.matched_debit_ids.debit_move_id.move_id.payment_id.is_matched", + "line_ids.matched_debit_ids.debit_move_id.move_id.line_ids.amount_residual", + "line_ids.matched_debit_ids.debit_move_id.move_id.line_ids.amount_residual_currency", + "line_ids.matched_credit_ids.credit_move_id.move_id.payment_id.is_matched", + "line_ids.matched_credit_ids.credit_move_id.move_id.line_ids.amount_residual", + "line_ids.matched_credit_ids.credit_move_id.move_id.line_ids.amount_residual_currency", + "line_ids.balance", + "line_ids.currency_id", + "line_ids.amount_currency", + "line_ids.amount_residual", + "line_ids.amount_residual_currency", + "line_ids.payment_id.state", + "line_ids.full_reconcile_id", + "state", + "ind_final", + ) + def _compute_amount(self): + for move in self.filtered(lambda m: m.fiscal_operation_id): + for line in move.line_ids: + if ( + move.is_invoice(include_receipts=True) + and line.display_type == "product" + ): + line._update_fiscal_taxes() + + result = super()._compute_amount() + for move in self.filtered(lambda m: m.fiscal_operation_id): + if move.move_type == "entry" or move.is_outbound(): + sign = -1 + else: + sign = 1 + inv_line_ids = move.line_ids.filtered( + lambda line: line.display_type == "product" + ) + move.amount_untaxed = sum(inv_line_ids.mapped("amount_untaxed")) + move.amount_tax = sum(inv_line_ids.mapped("amount_tax")) + move.amount_untaxed_signed = sign * sum( + inv_line_ids.mapped("amount_untaxed") + ) + move.amount_tax_signed = sign * sum(inv_line_ids.mapped("amount_tax")) + + return result + + @api.depends( + "invoice_payment_term_id", + "invoice_date", + "currency_id", + "amount_total_in_currency_signed", + "invoice_date_due", + ) + def _compute_needed_terms(self): + for invoice in self: + is_draft = invoice.id != invoice._origin.id + invoice.needed_terms = {} + invoice.needed_terms_dirty = True + sign = 1 if invoice.is_inbound(include_receipts=True) else -1 + if invoice.is_invoice(True) and invoice.invoice_line_ids: + if invoice.invoice_payment_term_id: + if is_draft: + tax_amount_currency = 0.0 + untaxed_amount_currency = 0.0 + for line in invoice.invoice_line_ids: + if line.cfop_id and not line.cfop_id.finance_move: + pass + else: + untaxed_amount_currency += line.price_subtotal + for tax_result in (line.compute_all_tax or {}).values(): + tax_amount_currency += -sign * tax_result.get( + "amount_currency", 0.0 + ) + untaxed_amount = untaxed_amount_currency + tax_amount = tax_amount_currency + else: + tax_amount_currency = invoice.amount_tax * sign + tax_amount = invoice.amount_tax_signed + if invoice.fiscal_operation_id: + if invoice.fiscal_operation_id.deductible_taxes: + amount_currency = ( + invoice.amount_total + + invoice.amount_tax_withholding + ) + else: + amount_currency = ( + invoice.amount_total - invoice.amount_ipi_value + ) * sign + untaxed_amount_currency = amount_currency * sign + untaxed_amount = amount_currency * sign + + else: + untaxed_amount_currency = invoice.amount_untaxed * sign + untaxed_amount = invoice.amount_untaxed_signed + invoice_payment_terms = ( + invoice.invoice_payment_term_id._compute_terms( + date_ref=invoice.invoice_date + or invoice.date + or fields.Date.context_today(invoice), + currency=invoice.currency_id, + tax_amount_currency=tax_amount_currency, + tax_amount=tax_amount, + untaxed_amount_currency=untaxed_amount_currency, + untaxed_amount=untaxed_amount, + company=invoice.company_id, + sign=sign, + ) + ) + for term in invoice_payment_terms: + key = frozendict( + { + "move_id": invoice.id, + "date_maturity": fields.Date.to_date(term.get("date")), + "discount_date": term.get("discount_date"), + "discount_percentage": term.get("discount_percentage"), + } + ) + values = { + "balance": term["company_amount"], + "amount_currency": term["foreign_amount"], + "discount_amount_currency": term["discount_amount_currency"] + or 0.0, + "discount_balance": term["discount_balance"] or 0.0, + "discount_date": term["discount_date"], + "discount_percentage": term["discount_percentage"], + } + if key not in invoice.needed_terms: + invoice.needed_terms[key] = values + else: + invoice.needed_terms[key]["balance"] += values["balance"] + invoice.needed_terms[key]["amount_currency"] += values[ + "amount_currency" + ] + else: + invoice.needed_terms[ + frozendict( + { + "move_id": invoice.id, + "date_maturity": fields.Date.to_date( + invoice.invoice_date_due + ), + "discount_date": False, + "discount_percentage": 0, + } + ) + ] = { + "balance": invoice.amount_total_signed, + "amount_currency": invoice.amount_total_in_currency_signed, + } + + @contextmanager + def _sync_dynamic_lines(self, container): + with self._disable_recursion(container, "skip_invoice_sync") as disabled: + if disabled: + yield + return + with super()._sync_dynamic_lines(container): + yield + self.update_payment_term_number() + + def update_payment_term_number(self): + payment_term_lines = self.line_ids.filtered( + lambda l: l.display_type == "payment_term" + ) + payment_term_lines_sorted = payment_term_lines.sorted( + key=lambda l: l.date_maturity + ) + for idx, line in enumerate(payment_term_lines_sorted, start=1): + line.with_context(skip_invoice_sync=True).write( + { + "payment_term_number": f"{idx}-{len(payment_term_lines_sorted)}", + } + ) + + @api.model_create_multi + def create(self, vals_list): + self._inject_shadowed_fields(vals_list) + invoice = super(AccountMove, self.with_context(create_from_move=True)).create( + vals_list + ) + return invoice + + def write(self, values): + self._inject_shadowed_fields([values]) + result = super().write(values) + return result + + def unlink(self): + """Allow to delete draft or cancelled invoices""" + unlink_moves = self.env["account.move"] + unlink_documents = self.env["l10n_br_fiscal.document"] + for move in self: + if not move.exists(): + continue + if move.fiscal_document_id and move.fiscal_document_id: + unlink_documents |= move.fiscal_document_id + unlink_moves |= move + result = super(AccountMove, unlink_moves).unlink() + unlink_documents.unlink() + self.clear_caches() + return result + + @api.onchange("fiscal_operation_id") + def _onchange_fiscal_operation_id(self): + result = super()._onchange_fiscal_operation_id() + if self.fiscal_operation_id and self.fiscal_operation_id.journal_id: + self.journal_id = self.fiscal_operation_id.journal_id + return result + + def open_fiscal_document(self): + """ + If there is only 1 fiscal document (usual case), open + the fiscal form view for it. + Open the tree view in the case of several fiscal documents. + """ + self.ensure_one() + + # doubt: is this in/out/all action selection relevant? + if self.env.context.get("move_type") == "out_invoice": + xmlid = "l10n_br_fiscal.document_out_action" + elif self.env.context.get("move_type") == "in_invoice": + xmlid = "l10n_br_fiscal.document_in_action" + else: + xmlid = "l10n_br_fiscal.document_all_action" + action = self.env["ir.actions.act_window"]._for_xml_id(xmlid) + + if len(self.fiscal_document_ids) == 1: + form_view = [(self.env.ref("l10n_br_fiscal.document_form").id, "form")] + if "views" in action: + action["views"] = form_view + [ + (state, view) for state, view in action["views"] if view != "form" + ] + else: + action["views"] = form_view + action["res_id"] = self.fiscal_document_ids[0].id + else: + action["domain"] = [("id", "in", self.fiscal_document_ids.ids)] + return action + + def button_draft(self): + for move in self.filtered(lambda d: d.document_type_id): + if move.state_edoc == SITUACAO_EDOC_CANCELADA: + if move.issuer == DOCUMENT_ISSUER_COMPANY: + raise UserError( + _( + "You can't set this document number: {} to draft " + "because this document is cancelled in SEFAZ" + ).format(move.document_number) + ) + move.fiscal_document_ids.filtered( + lambda d: d.state_edoc != SITUACAO_EDOC_EM_DIGITACAO + ).document_back2draft() + return super().button_draft() + + def action_document_send(self): + for invoice in self.filtered(lambda d: d.document_type_id): + invoice.fiscal_document_ids.action_document_send() + # FIXME: na migração para a v14 foi permitido o post antes do envio + # para destravar a migração, mas poderia ser cogitado de obrigar a + # transmissão antes do post novamente como na v12. + # for invoice in invoices: + # invoice.move_id.post(invoice=invoice) + + def action_document_cancel(self): + for move in self.filtered(lambda d: d.document_type_id): + move.ensure_one_doc() + return move.fiscal_document_id.action_document_cancel() + + def action_document_correction(self): + for move in self.filtered(lambda d: d.document_type_id): + move.ensure_one_doc() + return move.fiscal_document_id.action_document_correction() + + def action_document_invalidate(self): + for move in self.filtered(lambda d: d.document_type_id): + move.ensure_one_doc() + return move.fiscal_document_id.action_document_invalidate() + + def action_document_back2draft(self): + """Sets fiscal document to draft state and cancel and set to draft + the related invoice for both documents remain equivalent state.""" + for move in self.filtered(lambda d: d.document_type_id): + move.button_cancel() + move.button_draft() + + def action_post(self): + result = super().action_post() + + self.mapped("fiscal_document_id").filtered( + lambda d: d.document_type_id + )._document_confirm_to_send() + + # TODO FIXME + # Deixar a migração das funcionalidades do refund por último. + # Verificar se ainda haverá necessidade desse código. + + # for record in self.filtered(lambda i: i.refund_move_id): + # if record.state == "open": + # # Ao confirmar uma fatura/documento fiscal se é uma devolução + # # é feito conciliado com o documento de origem para abater + # # o valor devolvido pelo documento de refund + # to_reconcile_lines = self.env["account.move.line"] + # for line in record.move_id.line_ids: + # if line.account_id.id == record.account_id.id: + # to_reconcile_lines += line + # if line.reconciled: + # line.remove_move_reconcile() + # for line in record.refund_move_id.move_id.line_ids: + # if line.account_id.id == record.refund_move_id.account_id.id: + # to_reconcile_lines += line + + # to_reconcile_lines.filtered(lambda l: l.reconciled).reconcile() + + return result + + def view_xml(self): + self.ensure_one_doc() + return self.fiscal_document_id.view_xml() + + def view_pdf(self): + self.ensure_one_doc() + return self.fiscal_document_id.view_pdf() + + def action_send_email(self): + self.ensure_one_doc() + return self.fiscal_document_id.action_send_email() + + @api.onchange("document_type_id") + def _onchange_document_type_id(self): + # We need to ensure that invoices without a fiscal document have the + # document_number blank, as all invoices without a fiscal document share this + # same field, they are linked to the same dummy fiscal document. + # Otherwise, in the tree view, this field will be displayed with the same value + # for all these invoices. + if not self.document_type_id: + self.document_number = "" + + def _reverse_moves(self, default_values_list=None, cancel=False): + new_moves = super()._reverse_moves( + default_values_list=default_values_list, cancel=cancel + ) + force_fiscal_operation_id = False + if self.env.context.get("force_fiscal_operation_id"): + force_fiscal_operation_id = self.env["l10n_br_fiscal.operation"].browse( + self.env.context.get("force_fiscal_operation_id") + ) + for record in new_moves.filtered(lambda i: i.document_type_id): + if ( + not force_fiscal_operation_id + and not record.fiscal_operation_id.return_fiscal_operation_id + ): + raise UserError( + _("""Document without Return Fiscal Operation! \n Force one!""") + ) + + record.fiscal_operation_id = ( + force_fiscal_operation_id + or record.fiscal_operation_id.return_fiscal_operation_id + ) + record._onchange_fiscal_operation_id() + + for line in record.invoice_line_ids: + if ( + not force_fiscal_operation_id + and not line.fiscal_operation_id.return_fiscal_operation_id + ): + raise UserError( + _( + """Line without Return Fiscal Operation! \n + Please force one! \n%(name)s""", + name=line.name, + ) + ) + + line.fiscal_operation_id = ( + force_fiscal_operation_id + or line.fiscal_operation_id.return_fiscal_operation_id + ) + line._onchange_fiscal_operation_id() + + # Adds the related document to the NF-e. + # this is required for correct xml validation + if record.document_type_id and record.document_type_id.code in ( + MODELO_FISCAL_NFE + ): + record.fiscal_document_id._document_reference( + record.reversed_entry_id.fiscal_document_id + ) + + return new_moves + + def _finalize_invoices(self, invoices): + for invoice in invoices: + invoice.compute_taxes() + for line in invoice.line_ids: + # Use additional field helper function (for account extensions) + line._set_additional_fields(invoice) + invoice._onchange_cash_rounding() + + def post(self, invoice=False): + # TODO FIXME migrate: no more invoice keyword + result = super().post() + if invoice: + if ( + invoice.document_type_id + and invoice.document_electronic + and invoice.issuer == DOCUMENT_ISSUER_COMPANY + and invoice.state_edoc != SITUACAO_EDOC_AUTORIZADA + ): + self.button_cancel() + return result + + def button_cancel(self): + for doc in self.filtered(lambda d: d.document_type_id): + doc.fiscal_document_id.action_document_cancel() + return super().button_cancel() + + # TODO: Por ora esta solução contorna o problema + # AttributeError: 'Boolean' object has no attribute 'depends_context' + # Este erro está relacionado com o campo active implementado via localização + # nos modelos account.move.line e l10n_br_fiscal.document.line + # Este problema começou após este commit: + # https://github.com/oca/ocb/commit/1dcd071b27779e7d6d8f536c7dce7002d27212ba + def _get_integrity_hash_fields_and_subfields(self): + return self._get_integrity_hash_fields() + [ + f"line_ids.{subfield}" + for subfield in self.env["account.move.line"]._get_integrity_hash_fields() + ] + + def button_import_fiscal_document(self): + """ + Import move fields and invoice lines from + the fiscal_document_id record if there is any new line + to import. + You can typically set fiscal_document_id to some l10n_br_fiscal.document + record that was imported previously and import its lines into the + current move. + """ + for move in self: + if move.state != "draft": + raise UserError(_("Cannot import in non draft Account Move!")) + elif ( + move.partner_id + and move.partner_id != move.fiscal_document_id.partner_id + ): + raise UserError(_("Partner mismatch!")) + elif ( + MOVE_TO_OPERATION[move.move_type] + != move.fiscal_document_id.fiscal_operation_type + ): + raise UserError(_("Fiscal Operation Type mismatch!")) + elif move.company_id != move.fiscal_document_id.company_id: + raise UserError(_("Company mismatch!")) + + move_fiscal_lines = set( + move.invoice_line_ids.mapped("fiscal_document_line_id") + ) + fiscal_doc_lines = set(move.fiscal_document_id.fiscal_line_ids) + if move_fiscal_lines == fiscal_doc_lines: + raise UserError(_("No new Fiscal Document Line to import!")) + + self.import_fiscal_document(move.fiscal_document_id, move_id=move.id) + + @api.model + def import_fiscal_document( + self, + fiscal_document, + move_id=None, + move_type="in_invoice", + ): + """ + Import the data from an existing fiscal document into a new + invoice or into an existing invoice. + First it transfers the "shadowed" fields and fill the other + mandatory invoice fields. + The account.move onchanges of these fields are properly + triggered as if the invoice was filled manually. + Then it creates each account.move.line and fill them using + their fiscal_document_id onchange. + """ + if move_id: + move = self.env["account.move"].browse(move_id) + else: + move = self.env["account.move"] + move_form = Form( + move.with_context( + default_move_type=move_type, + account_predictive_bills_disable_prediction=True, + ) + ) + if not move_id or not move.fiscal_document_id: + move_form.invoice_date = fiscal_document.document_date + move_form.date = fiscal_document.document_date + for field in self._shadowed_fields(): + if field in ("company_id", "user_id"): # (readonly fields) + continue + if not move_form._view["fields"].get(field): + continue + setattr(move_form, field, getattr(fiscal_document, field)) + move_form.document_type_id = fiscal_document.document_type_id + move_form.fiscal_document_id = fiscal_document + move_form.fiscal_operation_id = fiscal_document.fiscal_operation_id + + for line in fiscal_document.fiscal_line_ids: + with move_form.invoice_line_ids.new() as line_form: + line_form.cfop_id = ( + line.cfop_id + ) # required if we disable some fiscal tax updates + line_form.fiscal_operation_id = self.fiscal_operation_id + line_form.fiscal_document_line_id = line + move_form.save() + return move_form diff --git a/l10n_br_account/models/account_move_line.py b/l10n_br_account/models/account_move_line.py new file mode 100644 index 000000000000..18f16da40d04 --- /dev/null +++ b/l10n_br_account/models/account_move_line.py @@ -0,0 +1,559 @@ +# Copyright (C) 2009 - TODAY Renato Lima - Akretion +# Copyright (C) 2019 - TODAY Raphaël Valyi - Akretion +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html +# pylint: disable=api-one-deprecated + +from contextlib import contextmanager + +from odoo import _, api, fields, models +from odoo.tools import frozendict + +from .account_move import InheritsCheckMuteLogger + +# These fields have the same name in account.move.line +# and l10n_br_fiscal.document.line. So they wouldn't get updated +# by the _inherits system. An alternative would be changing their name +# in l10n_br_fiscal but that would make the code unreadable and fiscal mixin +# methods would fail to do what we expect from them in the Odoo objects +# where they are injected. +# Fields that are related in l10n_br_fiscal.document.line like partner_id or company_id +# don't need to be written through the account.move.line write. +SHADOWED_FIELDS = [ + "product_id", + "name", + "quantity", + "price_unit", +] + +ACCOUNTING_FIELDS = ("debit", "credit", "amount_currency") +BUSINESS_FIELDS = ("price_unit", "quantity", "discount", "tax_ids") + + +class AccountMoveLine(models.Model): + _name = "account.move.line" + _inherit = [_name, "l10n_br_fiscal.document.line.mixin.methods"] + _inherits = {"l10n_br_fiscal.document.line": "fiscal_document_line_id"} + + fiscal_document_line_id = fields.Many2one( + comodel_name="l10n_br_fiscal.document.line", + string="Fiscal Document Line", + copy=False, + ondelete="cascade", + ) + + document_type_id = fields.Many2one( + comodel_name="l10n_br_fiscal.document.type", + related="move_id.document_type_id", + ) + + tax_framework = fields.Selection( + related="move_id.company_id.tax_framework", + string="Tax Framework", + ) + + cfop_destination = fields.Selection( + related="cfop_id.destination", string="CFOP Destination" + ) + + partner_company_type = fields.Selection(related="partner_id.company_type") + + ind_final = fields.Selection(related="move_id.ind_final") + + fiscal_genre_code = fields.Char( + related="fiscal_genre_id.code", + string="Fiscal Product Genre Code", + ) + + # The following fields belong to the fiscal document line mixin + # but they are redefined here to ensure they are recomputed in the + # account.move.line views. + icms_cst_code = fields.Char( + related="icms_cst_id.code", + string="ICMS CST Code", + ) + + ipi_cst_code = fields.Char( + related="ipi_cst_id.code", + string="IPI CST Code", + ) + + cofins_cst_code = fields.Char( + related="cofins_cst_id.code", + string="COFINS CST Code", + ) + + cofinsst_cst_code = fields.Char( + related="cofinsst_cst_id.code", + string="COFINS ST CST Code", + ) + + pis_cst_code = fields.Char( + related="pis_cst_id.code", + string="PIS CST Code", + ) + + pisst_cst_code = fields.Char( + related="pisst_cst_id.code", + string="PIS ST CST Code", + ) + + partner_is_public_entity = fields.Boolean(related="partner_id.is_public_entity") + + allow_csll_irpj = fields.Boolean( + compute="_compute_allow_csll_irpj", + ) + + discount = fields.Float( + compute="_compute_discounts", + store=True, + ) + + payment_term_number = fields.Char( + help="Stores the installment number in the format 'current-total'. For example, " + "'1-3' for the first of three installments, '2-3' for the second, and '3-3'" + " for the last installment.", + ) + + @api.depends( + "quantity", + "price_unit", + "discount_value", + ) + def _compute_discounts(self): + for line in self: + line.discount = (line.discount_value * 100) / ( + line.quantity * line.price_unit or 1 + ) + + @api.depends("product_id", "payment_term_number") + def _compute_name(self): + """ + Override to set 'name' with 'document_number/payment_term_number' for + payment term lines. For other lines, it calls the superclass method. + """ + payment_term_lines = self.filtered( + lambda line: line.display_type == "payment_term" + and line.document_type_id + and line.move_id.document_number + and line.payment_term_number + ) + for line in payment_term_lines: + # set label for payment term lines. Ex: '0001/1-3' + line.name = f"{line.move_id.document_number}/{line.payment_term_number}" + + other_lines = self - payment_term_lines + if other_lines: + return super()._compute_name() + return True + + @api.model + def _inherits_check(self): + """ + Overriden to avoid the super method to set the fiscal_document_line_id + field as required. + """ + with InheritsCheckMuteLogger("odoo.models"): # mute spurious warnings + res = super()._inherits_check() + field = self._fields.get("fiscal_document_line_id") + field.required = False # unset the required = True assignement + return res + + @api.model + def _shadowed_fields(self): + """Return the list of shadowed fields that are synchronized + from account.move.line.""" + return SHADOWED_FIELDS + + @api.model + def _inject_shadowed_fields(self, vals_list): + for vals in vals_list: + for field in self._shadowed_fields(): + if field in vals: + vals["fiscal_%s" % (field,)] = vals[field] + + @api.model_create_multi + def create(self, vals_list): + for values in vals_list: + if values.get("fiscal_document_line_id"): + fiscal_line_data = ( + self.env["l10n_br_fiscal.document.line"] + .browse(values["fiscal_document_line_id"]) + .read(self._shadowed_fields())[0] + ) + for k, v in fiscal_line_data.items(): + if isinstance(v, tuple): # m2o + values[k] = v[0] + else: + values[k] = v + continue + + if values.get("exclude_from_invoice_tab"): # FIXME MIGRATE + continue + + move_id = self.env["account.move"].browse(values["move_id"]) + fiscal_doc_id = move_id.fiscal_document_id.id + + if not fiscal_doc_id: + continue + + values.update( + self._update_fiscal_quantity( + values.get("product_id"), + values.get("price_unit"), + values.get("quantity"), + values.get("product_uom_id"), + values.get("uot_id"), + ) + ) + values["uom_id"] = values.get("product_uom_id") + values["document_id"] = fiscal_doc_id # pass through the _inherits system + + self._inject_shadowed_fields(vals_list) + + # This reordering bellow is crucial to ensure accurate linkage between + # account.move.line (aml) and the fiscal document line. In the fiscal create a + # fiscal document line, leaving only those that should be created. Proper + # ordering is essential as mismatches between the order of amls and the + # manipulated vals_list of fiscal documents can lead to incorrect linkages. + # For example, if vals_list[0] in amls does not match vals_list[0] in the + # fiscal document (which is a manipulated vals_list), it results in erroneous + # associations. + + # Add index to each dictionary in vals_list + indexed_vals_list = [(idx, val) for idx, val in enumerate(vals_list)] + + # Reorder vals_list so lines with fiscal_operation_line_id will + # be created first + sorted_indexed_vals_list = sorted( + indexed_vals_list, + key=lambda x: not x[1].get("fiscal_operation_line_id"), + ) + original_indexes = [idx for idx, _ in sorted_indexed_vals_list] + vals_list = [val for _, val in sorted_indexed_vals_list] + + # Create the records + result = super( + AccountMoveLine, self.with_context(create_from_move_line=True) + ).create(vals_list) + + # Initialize the inverted index list with the same length as the original list + inverted_index = [0] * len(original_indexes) + + # Iterate over the original_indexes list and fill the inverted_index list accordingly + for i, val in enumerate(original_indexes): + inverted_index[val] = i + + # Re-order the result according to the initial vals_list order + sorted_result = self.env["account.move.line"] + for idx in inverted_index: + sorted_result |= result[idx] + + # TODO MIGRATE, see https://github.com/OCA/l10n-brazil/pull/3037 + # for line in sorted_result: + # Forces the recalculation of price_total and price_subtotal fields which are + # recalculated by super + # if line.move_id.company_id.country_id.code == "BR": + # line.update(line._get_price_total_and_subtotal()) + + return sorted_result + + def unlink(self): + unlink_fiscal_lines = self.env["l10n_br_fiscal.document.line"] + for inv_line in self: + if not inv_line.exists(): + continue + if inv_line.fiscal_document_line_id: + unlink_fiscal_lines |= inv_line.fiscal_document_line_id + result = super().unlink() + unlink_fiscal_lines.unlink() + self.clear_caches() + return result + + @contextmanager + def _sync_invoice(self, container): + """ + Almost the same as the super method from the account module. + Overriden only to change one line where country_id.code is compared with "BR" + """ + if container["records"].env.context.get("skip_invoice_line_sync"): + yield + return # avoid infinite recursion + + def existing(): + return { + line: { + "amount_currency": line.currency_id.round(line.amount_currency), + "balance": line.company_id.currency_id.round(line.balance), + "currency_rate": line.currency_rate, + "price_subtotal": line.currency_id.round(line.price_subtotal), + "move_type": line.move_id.move_type, + } + for line in container["records"] + .with_context( + skip_invoice_line_sync=True, + ) + .filtered(lambda l: l.move_id.is_invoice(True)) + } + + def changed(fname): + return line not in before or before[line][fname] != after[line][fname] + + before = existing() + yield + after = existing() + for line in after: + if line.display_type == "product" and ( + not changed("amount_currency") or line not in before + ): + if not line.move_id.fiscal_operation_id: + amount_currency = ( + line.move_id.direction_sign + * line.currency_id.round(line.price_subtotal) + ) + else: # BRAZIL CASE: + if line.cfop_id and not line.cfop_id.finance_move: + amount_currency = 0 + else: + if line.move_id.fiscal_operation_id.deductible_taxes: + amount_currency = ( + line.amount_total + line.amount_tax_withholding + ) + else: + amount_total = ( + line.amount_total + line.amount_tax_withholding + ) + amount_currency = ( + line.move_id.direction_sign + * line.currency_id.round( + amount_total + - ( + line.amount_tax_included + - line.amount_tax_withholding + ) + - line.amount_tax_not_included + - line.icms_relief_value + ) + ) + if line.amount_currency != amount_currency or line not in before: + line.amount_currency = amount_currency + if line.currency_id == line.company_id.currency_id: + line.balance = amount_currency + + after = existing() + for line in after: + if ( + changed("amount_currency") + or changed("currency_rate") + or changed("move_type") + ) and (not changed("balance") or (line not in before and not line.balance)): + balance = line.company_id.currency_id.round( + line.amount_currency / line.currency_rate + ) + line.balance = balance + + @api.depends( + "quantity", "discount", "price_unit", "tax_ids", "currency_id", "discount" + ) # TODO complete! + def _compute_totals(self): + """ + Overriden to pass all the Brazilian parameters we need + to the account.tax#compute_all method. + """ + result = super()._compute_totals() + if not self.move_id.fiscal_operation_id: + return result + + for line in self: + if line.display_type != "product": + continue # handled in super method + + line_discount_price_unit = line.price_unit * (1 - (line.discount / 100.0)) + + # Compute 'price_total'. + if line.tax_ids: + # force_sign = ( + # -1 + # if line.move_type in ("out_invoice", "in_refund", "out_receipt") + # else 1 + # ) + taxes_res = line.tax_ids._origin.with_context( + # force_sign=force_sign + ).compute_all( + line_discount_price_unit, + currency=line.currency_id, + quantity=line.quantity, + product=line.product_id, + partner=line.partner_id, + is_refund=line.move_type in ("out_refund", "in_refund"), + handle_price_include=True, # FIXME + fiscal_taxes=line.fiscal_tax_ids, + operation_line=line.fiscal_operation_line_id, + cfop=line.cfop_id or None, + ncm=line.ncm_id, + nbs=line.nbs_id, + nbm=line.nbm_id, + cest=line.cest_id, + discount_value=line.discount_value, + insurance_value=line.insurance_value, + other_value=line.other_value, + ii_customhouse_charges=line.ii_customhouse_charges, + freight_value=line.freight_value, + fiscal_price=line.fiscal_price, + fiscal_quantity=line.fiscal_quantity, + uot_id=line.uot_id, + icmssn_range=line.icmssn_range_id, + icms_origin=line.icms_origin, + ind_final=line.ind_final, + ) + + line.price_subtotal = taxes_res["total_excluded"] + line.price_total = taxes_res["total_included"] + line._compute_balance() + + line.price_total += ( + line.insurance_value + + line.other_value + + line.freight_value + - line.icms_relief_value + ) + # TODO MIGRATE v16 (that is make icms_relief_value really work), + # for icms_relief_value see https://github.com/OCA/l10n-brazil/pull/3037 + return result + + @api.depends( + "tax_ids", + "currency_id", + "partner_id", + "analytic_distribution", + "balance", + "partner_id", + "move_id.partner_id", + "price_unit", + ) + def _compute_all_tax(self): + """ + Overriden to pass all the extra Brazilian parameters we need + to the account.tax#compute_all method. + """ + # TODO seems we should use sign in account_tax#compute_all + # so base and amount are negative if move is in. + if not self.move_id.fiscal_operation_id: + return super()._compute_all_tax() + + for line in self: + sign = line.move_id.direction_sign + if line.display_type == "tax": + line.compute_all_tax = {} + line.compute_all_tax_dirty = False + continue + if line.display_type == "product" and line.move_id.is_invoice(True): + amount_currency = sign * line.price_unit * (1 - line.discount / 100) + handle_price_include = True + quantity = line.quantity + else: + amount_currency = line.amount_currency + handle_price_include = False + quantity = 1 + compute_all_currency = line.tax_ids.compute_all( + amount_currency, + currency=line.currency_id, + quantity=quantity, + product=line.product_id, + partner=line.move_id.partner_id or line.partner_id, + is_refund=line.is_refund, + handle_price_include=handle_price_include, + include_caba_tags=line.move_id.always_tax_exigible, + fixed_multiplicator=sign, + fiscal_taxes=line.fiscal_tax_ids, + operation_line=line.fiscal_operation_line_id, + cfop=line.cfop_id or None, + ncm=line.ncm_id, + nbs=line.nbs_id, + nbm=line.nbm_id, + cest=line.cest_id, + discount_value=line.discount_value, + insurance_value=line.insurance_value, + other_value=line.other_value, + ii_customhouse_charges=line.ii_customhouse_charges, + freight_value=line.freight_value, + fiscal_price=line.fiscal_price, + fiscal_quantity=line.fiscal_quantity, + uot_id=line.uot_id, + icmssn_range=line.icmssn_range_id, + icms_origin=line.icms_origin, + ind_final=line.ind_final, + ) + rate = ( + line.amount_currency / line.balance + if (line.balance and line.amount_currency) + else 1 + ) + line.compute_all_tax_dirty = True + line.compute_all_tax = { + frozendict( + { + "tax_repartition_line_id": tax["tax_repartition_line_id"], + "group_tax_id": tax["group"] and tax["group"].id or False, + "account_id": tax["account_id"] or line.account_id.id, + "currency_id": line.currency_id.id, + "analytic_distribution": ( + tax["analytic"] or not tax["use_in_tax_closing"] + ) + and line.analytic_distribution, + "tax_ids": [(6, 0, tax["tax_ids"])], + "tax_tag_ids": [(6, 0, tax["tag_ids"])], + "partner_id": line.move_id.partner_id.id or line.partner_id.id, + "move_id": line.move_id.id, + "display_type": line.display_type, + } + ): { + "name": tax["name"] + + (" " + _("(Discount)") if line.display_type == "epd" else ""), + "balance": tax["amount"] / rate, + "amount_currency": tax["amount"], + "tax_base_amount": tax["base"] + / rate + * (-1 if line.tax_tag_invert else 1), + } + for tax in compute_all_currency["taxes"] + if tax["amount"] + } + if not line.tax_repartition_line_id: + line.compute_all_tax[frozendict({"id": line.id})] = { + "tax_tag_ids": [(6, 0, compute_all_currency["base_tags"])], + } + + @api.onchange("fiscal_document_line_id") + def _onchange_fiscal_document_line_id(self): + if self.fiscal_document_line_id: + for field in self._shadowed_fields(): + value = getattr(self.fiscal_document_line_id, field) + if isinstance(value, tuple): # m2o + setattr(self, field, value[0]) + else: + setattr(self, field, value) + # override the default product uom (set by the onchange): + self.product_uom_id = self.fiscal_document_line_id.uom_id.id + + @api.onchange("fiscal_tax_ids") + def _onchange_fiscal_tax_ids(self): + """Ao alterar o campo fiscal_tax_ids que contém os impostos fiscais, + são atualizados os impostos contábeis relacionados""" + result = super()._onchange_fiscal_tax_ids() + + # Atualiza os impostos contábeis relacionados aos impostos fiscais + user_type = "sale" + if self.move_id.move_type in ("in_invoice", "in_refund"): + user_type = "purchase" + + self.tax_ids = self.fiscal_tax_ids.account_taxes( + user_type=user_type, fiscal_operation=self.fiscal_operation_id + ) + + return result + + # These fields are already inherited by _inherits, but there is some limitation of + # the ORM that the values of these fields are zeroed when called by onchange. This + # limitation directly affects the _get_amount_credit_debit method. + amount_untaxed = fields.Monetary(compute="_compute_amounts") + amount_total = fields.Monetary(compute="_compute_amounts") diff --git a/l10n_br_account/models/account_tax.py b/l10n_br_account/models/account_tax.py new file mode 100644 index 000000000000..ff93b498aa6c --- /dev/null +++ b/l10n_br_account/models/account_tax.py @@ -0,0 +1,307 @@ +# Copyright (C) 2009 - TODAY Renato Lima - Akretion +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import Command, api, fields, models + +from odoo.addons.l10n_br_fiscal.constants.fiscal import FINAL_CUSTOMER_NO + + +class AccountTax(models.Model): + _inherit = "account.tax" + + fiscal_tax_ids = fields.Many2many( + comodel_name="l10n_br_fiscal.tax", + relation="fiscal_account_tax_rel", + column1="account_tax_id", + column2="fiscal_tax_id", + string="Fiscal Taxes", + ) + + def compute_all( + self, + price_unit, + currency=None, + quantity=1.0, + product=None, + partner=None, + is_refund=False, + handle_price_include=True, + include_caba_tags=False, + fixed_multiplicator=1, + fiscal_taxes=None, + operation_line=False, + ncm=None, + nbs=None, + nbm=None, + cest=None, + cfop=None, + discount_value=None, + insurance_value=None, + other_value=None, + ii_customhouse_charges=None, + freight_value=None, + fiscal_price=None, + fiscal_quantity=None, + uot_id=None, + icmssn_range=None, + icms_origin=None, + ind_final=FINAL_CUSTOMER_NO, + ): + """Returns all information required to apply taxes + (in self + their children in case of a tax goup). + We consider the sequence of the parent for group of taxes. + Eg. considering letters as taxes and alphabetic order + as sequence : + [G, B([A, D, F]), E, C] will be computed as [A, D, F, C, E, G] + RETURN: { + 'total_excluded': 0.0, # Total without taxes + 'total_included': 0.0, # Total with taxes + 'taxes': [{ # One dict for each tax in self + # and their children + 'id': int, + 'name': str, + 'amount': float, + 'sequence': int, + 'account_id': int, + 'refund_account_id': int, + 'analytic': boolean, + }] + }""" + + taxes_results = super().compute_all( + price_unit, + currency, + quantity, + product, + partner, + is_refund, + handle_price_include, + include_caba_tags, + fixed_multiplicator, + ) + + if not fiscal_taxes: + fiscal_taxes = self.env["l10n_br_fiscal.tax"] + + product = product or self.env["product.product"] + + if len(self) == 0: + company = self.env.company + if self.env.context.get("default_company_id") or self.env.context.get( + "allowed_company_ids" + ): + company = self.env["res.company"].browse( + self.env.context.get("default_company_id") + or self.env.context.get("allowed_company_ids")[0] + ) + else: + company = self[0].company_id + + fiscal_taxes_results = fiscal_taxes.compute_taxes( + company=company, + partner=partner, + product=product, + price_unit=price_unit, + quantity=quantity, + uom_id=product.uom_id, + fiscal_price=fiscal_price or price_unit, + fiscal_quantity=fiscal_quantity or quantity, + uot_id=uot_id or product.uot_id, + ncm=ncm or product.ncm_id, + nbs=nbs or product.nbs_id, + nbm=nbm or product.nbm_id, + cest=cest or product.cest_id, + cfop=cfop, + discount_value=discount_value, + insurance_value=insurance_value, + other_value=other_value, + ii_customhouse_charges=ii_customhouse_charges, + freight_value=freight_value, + operation_line=operation_line, + icmssn_range=icmssn_range, + icms_origin=icms_origin or product.icms_origin, + ind_final=ind_final, + ) + + taxes_results["amount_tax_included"] = fiscal_taxes_results["amount_included"] + taxes_results["amount_tax_not_included"] = fiscal_taxes_results[ + "amount_not_included" + ] + taxes_results["amount_tax_withholding"] = fiscal_taxes_results[ + "amount_withholding" + ] + taxes_results["amount_estimate_tax"] = fiscal_taxes_results["estimate_tax"] + + account_taxes_by_domain = {} + + sign = -1 if fixed_multiplicator < 0 else 1 + + for tax in self: + tax_domain = tax.tax_group_id.fiscal_tax_group_id.tax_domain + account_taxes_by_domain.update({tax.id: tax_domain}) + + for account_tax in taxes_results["taxes"]: + tax = self.filtered(lambda t: t.id == account_tax.get("id")) + fiscal_tax = fiscal_taxes_results["taxes"].get( + account_taxes_by_domain.get(tax.id) + ) + + account_tax.update( + { + "tax_group_id": tax.tax_group_id.id, + "deductible": tax.deductible, + } + ) + + tax_repartition_lines = ( + is_refund + and tax.refund_repartition_line_ids + or tax.invoice_repartition_line_ids + ).filtered(lambda x: x.repartition_type == "tax") + + sum_repartition_factor = sum(tax_repartition_lines.mapped("factor")) + + if fiscal_tax: + if fiscal_tax.get("base") < 0: + sign = -1 + fiscal_tax["base"] = -fiscal_tax.get("base") + + if not fiscal_tax.get("tax_include") and not tax.deductible: + taxes_results["total_included"] += fiscal_tax.get("tax_value") + + fiscal_group = tax.tax_group_id.fiscal_tax_group_id + tax_amount = fiscal_tax.get("tax_value", 0.0) * sum_repartition_factor + tax_base = fiscal_tax.get("base") * sum_repartition_factor + if tax.deductible or fiscal_group.tax_withholding: + tax_amount = ( + fiscal_tax.get("tax_value", 0.0) * sum_repartition_factor + ) + + account_tax.update( + { + "id": account_tax.get("id"), + "name": fiscal_group.name, + "fiscal_name": fiscal_tax.get("name"), + "base": tax_base * sign, + "tax_include": fiscal_tax.get("tax_include"), + "amount": tax_amount * sign, + "fiscal_tax_id": fiscal_tax.get("fiscal_tax_id"), + "tax_withholding": fiscal_group.tax_withholding, + } + ) + + return taxes_results + + @api.model + def _compute_taxes_for_single_line( + self, + base_line, + handle_price_include=True, + include_caba_tags=False, + early_pay_discount_computation=None, + early_pay_discount_percentage=None, + ): + # TODO FIXME call super if possible + # TODO is this override really useful? + orig_price_unit_after_discount = base_line["price_unit"] * ( + 1 - (base_line["discount"] / 100.0) + ) + price_unit_after_discount = orig_price_unit_after_discount + taxes = base_line["taxes"]._origin + currency = base_line["currency"] or self.env.company.currency_id + rate = base_line["rate"] + + if early_pay_discount_computation in ("included", "excluded"): + remaining_part_to_consider = (100 - early_pay_discount_percentage) / 100.0 + price_unit_after_discount = ( + remaining_part_to_consider * price_unit_after_discount + ) + + if taxes: + line = base_line["record"] + taxes_res = taxes.with_context(**base_line["extra_context"]).compute_all( + price_unit_after_discount, + currency=currency, + quantity=base_line["quantity"], + product=base_line["product"], + partner=base_line["partner"], + is_refund=base_line["is_refund"], + handle_price_include=base_line["handle_price_include"], + include_caba_tags=include_caba_tags, + fiscal_taxes=line.fiscal_tax_ids, + operation_line=line.fiscal_operation_line_id, + cfop=line.cfop_id or None, + ncm=line.ncm_id, + nbs=line.nbs_id, + nbm=line.nbm_id, + cest=line.cest_id, + discount_value=line.discount_value, + insurance_value=line.insurance_value, + other_value=line.other_value, + ii_customhouse_charges=line.ii_customhouse_charges, + freight_value=line.freight_value, + fiscal_price=line.fiscal_price, + fiscal_quantity=line.fiscal_quantity, + uot_id=line.uot_id, + icmssn_range=line.icmssn_range_id, + icms_origin=line.icms_origin, + ind_final=line.ind_final, + ) + + to_update_vals = { + "tax_tag_ids": [Command.set(taxes_res["base_tags"])], + "price_subtotal": taxes_res["total_excluded"], + "price_total": taxes_res["total_included"], + } + + if early_pay_discount_computation == "excluded": + new_taxes_res = taxes.with_context( + **base_line["extra_context"] + ).compute_all( + orig_price_unit_after_discount, + currency=currency, + quantity=base_line["quantity"], + product=base_line["product"], + partner=base_line["partner"], + is_refund=base_line["is_refund"], + handle_price_include=base_line["handle_price_include"], + include_caba_tags=include_caba_tags, + ) + for tax_res, new_taxes_res in zip( + taxes_res["taxes"], new_taxes_res["taxes"] + ): + delta_tax = new_taxes_res["amount"] - tax_res["amount"] + tax_res["amount"] += delta_tax + to_update_vals["price_total"] += delta_tax + + tax_values_list = [] + for tax_res in taxes_res["taxes"]: + tax_amount = tax_res["amount"] / rate + if self.company_id.tax_calculation_rounding_method == "round_per_line": + tax_amount = currency.round(tax_amount) + tax_rep = self.env["account.tax.repartition.line"].browse( + tax_res["tax_repartition_line_id"] + ) + tax_values_list.append( + { + **tax_res, + "tax_repartition_line": tax_rep, + "base_amount_currency": tax_res["base"], + "base_amount": currency.round(tax_res["base"] / rate), + "tax_amount_currency": tax_res["amount"], + "tax_amount": tax_amount, + } + ) + + else: + price_subtotal = currency.round( + price_unit_after_discount * base_line["quantity"] + ) + to_update_vals = { + "tax_tag_ids": [Command.clear()], + "price_subtotal": price_subtotal, + "price_total": price_subtotal, + } + tax_values_list = [] + + return to_update_vals, tax_values_list diff --git a/l10n_br_account/models/account_tax_group.py b/l10n_br_account/models/account_tax_group.py new file mode 100644 index 000000000000..ebab76168225 --- /dev/null +++ b/l10n_br_account/models/account_tax_group.py @@ -0,0 +1,24 @@ +# Copyright (C) 2009 Renato Lima - Akretion +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import fields, models + + +class AccountTaxGroup(models.Model): + _inherit = "account.tax.group" + + fiscal_tax_group_id = fields.Many2one( + comodel_name="l10n_br_fiscal.tax.group", + string="Fiscal Tax Group", + ) + + def deductible_tax(self, type_tax_use="sale"): + return self.env["account.tax"].search( + [ + ("type_tax_use", "=", type_tax_use), + ("tax_group_id", "=", self.id), + ("deductible", "=", True), + ("company_id", "=", self.env.company.id), + ], + limit=1, + ) diff --git a/l10n_br_account/models/account_tax_template.py b/l10n_br_account/models/account_tax_template.py new file mode 100644 index 000000000000..090f352d1c41 --- /dev/null +++ b/l10n_br_account/models/account_tax_template.py @@ -0,0 +1,28 @@ +# Copyright (C) 2013 Renato Lima - Akretion +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import fields, models + + +class AccountTaxTemplate(models.Model): + _inherit = "account.tax.template" + + fiscal_tax_ids = fields.Many2many( + comodel_name="l10n_br_fiscal.tax", + relation="fiscal_account_template_tax_rel", + column1="account_tax_template_id", + column2="fiscal_tax_id", + string="Fiscal Taxes", + ) + + def _generate_tax(self, company): + mapping = super()._generate_tax(company) + taxes_template = mapping.get("tax_template_to_tax").keys() + + for tax_template in taxes_template: + tax_id = mapping.get("tax_template_to_tax").get(tax_template.id) + self.env["account.tax"].browse(tax_id).write( + {"fiscal_tax_ids": [(6, 0, tax_template.fiscal_tax_ids.ids)]} + ) + + return mapping diff --git a/l10n_br_account/models/fiscal_document.py b/l10n_br_account/models/fiscal_document.py new file mode 100644 index 000000000000..d4aa1728fc74 --- /dev/null +++ b/l10n_br_account/models/fiscal_document.py @@ -0,0 +1,198 @@ +# Copyright (C) 2009 - TODAY Renato Lima - Akretion +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from datetime import datetime, time + +from pytz import UTC, timezone + +from odoo import _, api, fields, models +from odoo.exceptions import UserError + +from odoo.addons.l10n_br_fiscal.constants.fiscal import ( + DOCUMENT_ISSUER_PARTNER, + SITUACAO_EDOC_EM_DIGITACAO, +) + + +class FiscalDocument(models.Model): + _inherit = "l10n_br_fiscal.document" + + move_ids = fields.One2many( + comodel_name="account.move", + inverse_name="fiscal_document_id", + string="Invoices", + ) + + # proxy fields to enable writing the related (shadowed) fields + # to the fiscal document from the account.move through the _inherits system + # despite they have the same names. + fiscal_partner_id = fields.Many2one( + string="Fiscal Partner", + related="partner_id", + readonly=False, + ) + fiscal_company_id = fields.Many2one( + string="Fiscal Company", + related="company_id", + readonly=False, + ) + fiscal_currency_id = fields.Many2one( + string="Fiscal Currency", + related="currency_id", + readonly=False, + ) + fiscal_user_id = fields.Many2one( + string="Fiscal User", + related="user_id", + readonly=False, + ) + + # commented out because of badly written TestInvoiceDiscount.test_date_in_out + # def write(self, vals): + # if self.document_type_id: + # return super().write(vals) + + fiscal_line_ids = fields.One2many( + copy=False, + ) + + # For some reason, perhaps limitation of _inhertis, + # the related directly in the account.move does not work correctly. + incoterm_id = fields.Many2one( + string="Fiscal Inconterm", + related="move_ids.invoice_incoterm_id", + ) + + document_date = fields.Datetime( + compute="_compute_document_date", inverse="_inverse_document_date", store=True + ) + + date_in_out = fields.Datetime( + compute="_compute_date_in_out", inverse="_inverse_date_in_out", store=True + ) + + document_type_id = fields.Many2one(inverse="_inverse_document_type_id") + + def _inverse_document_type_id(self): + pass # (meant to be overriden in account.move) + + @api.depends("move_ids", "move_ids.invoice_date") + def _compute_document_date(self): + for record in self: + if record.move_ids and record.issuer == DOCUMENT_ISSUER_PARTNER: + move_id = record.move_ids[0] + if move_id.invoice_date: + user_tz = timezone(self.env.user.tz or "UTC") + doc_date = datetime.combine(move_id.invoice_date, time.min) + record.document_date = ( + user_tz.localize(doc_date).astimezone(UTC).replace(tzinfo=None) + ) + + def _inverse_document_date(self): + for record in self: + if record.move_ids and record.issuer == DOCUMENT_ISSUER_PARTNER: + move_id = record.move_ids[0] + if record.document_date: + move_id.invoice_date = record.document_date.date() + + @api.depends("move_ids", "move_ids.date") + def _compute_date_in_out(self): + for record in self: + if record.move_ids and record.issuer == DOCUMENT_ISSUER_PARTNER: + move_id = record.move_ids[0] + if move_id.date: + user_tz = timezone(self.env.user.tz or "UTC") + doc_date = datetime.combine(move_id.date, time.min) + record.date_in_out = ( + user_tz.localize(doc_date).astimezone(UTC).replace(tzinfo=None) + ) + + def _inverse_date_in_out(self): + for record in self: + if record.move_ids and record.issuer == DOCUMENT_ISSUER_PARTNER: + move_id = record.move_ids[0] + if record.date_in_out: + move_id.date = record.date_in_out.date() + + def unlink(self): + non_draft_documents = self.filtered( + lambda d: d.state != SITUACAO_EDOC_EM_DIGITACAO + ) + + if non_draft_documents: + UserError( + _("You cannot delete a fiscal document " "which is not draft state.") + ) + return super().unlink() + + @api.model_create_multi + def create(self, vals_list): + """ + It's not allowed to create a fiscal document line without a document_type_id anyway. + But instead of letting Odoo crash in this case we simply avoid creating the + record. This makes it possible to create an account.move without + a fiscal_document_id despite the _inherits system: + Odoo will write NULL as the value in this case. + """ + if self._context.get("create_from_move"): + filtered_vals_list = [] + for values in vals_list: + if values.get("document_type_id") or values.get("document_serie_id"): + # we also disable the fiscal_line_ids creation here to + # let the ORM create them later from the account.move.line records + values.update({"fiscal_line_ids": False}) + filtered_vals_list.append(values) + return super().create(filtered_vals_list) + else: + return super().create(vals_list) + + def _update_cache(self, values, validate=True): + """ + Overriden to avoid raising error with ensure_one() in super() + when called from some account.move onchange + as we allow empty fiscal document in account.move. + """ + if len(self) == 0: + return + return super()._update_cache(values, validate) + + @api.returns("mail.message", lambda value: value.id) + def message_post(self, **kwargs): + """ + broadcast message_post to all related account.move so + messages in a fiscal document chatter are visible in the + related account moves. + """ + for doc in self: + for move in doc.move_ids: + move.message_post(**kwargs) + + def cancel_move_ids(self): + for record in self: + if record.move_ids: + self.move_ids.button_cancel() + + def _document_cancel(self, justificative): + result = super()._document_cancel(justificative) + msg = "Cancelamento: {}".format(justificative) + self.cancel_move_ids() + self.message_post(body=msg) + return result + + def _document_correction(self, justificative): + result = super()._document_correction(justificative) + msg = "Carta de correção: {}".format(justificative) + self.message_post(body=msg) + return result + + def action_document_confirm(self): + result = super().action_document_confirm() + move_ids = self.move_ids.filtered(lambda move: move.state == "draft") + move_ids._post() + return result + + def action_document_back2draft(self): + result = super().action_document_back2draft() + if self.move_ids: + self.move_ids.button_draft() + return result diff --git a/l10n_br_account/models/fiscal_document_line.py b/l10n_br_account/models/fiscal_document_line.py new file mode 100644 index 000000000000..afbf4d1b78c0 --- /dev/null +++ b/l10n_br_account/models/fiscal_document_line.py @@ -0,0 +1,76 @@ +# Copyright (C) 2021 - TODAY Gabriel Cardoso de Faria - Kmee +# Copyright (C) 2023 - TODAY Raphaël Valyi - Akretion +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import api, fields, models + + +class FiscalDocumentLine(models.Model): + _inherit = "l10n_br_fiscal.document.line" + + account_line_ids = fields.One2many( + comodel_name="account.move.line", + inverse_name="fiscal_document_line_id", + string="Invoice Lines", + ) + + # proxy fields to enable writing the related (shadowed) fields + # to the fiscal doc line from the aml through the _inherits system + # despite they have the same names. + fiscal_name = fields.Text( + string="Fiscal Name", + related="name", + readonly=False, + ) + fiscal_product_id = fields.Many2one( + string="Fiscal Product", + related="product_id", + readonly=False, + ) + fiscal_quantity = fields.Float( + string="Fiscal Quantity", + related="quantity", + readonly=False, + ) + fiscal_price_unit = fields.Float( + string="Fiscal Price Unit", + related="price_unit", + readonly=False, + ) + + @api.model_create_multi + def create(self, vals_list): + """ + Override the create method to ensure it filters out account.move.line records + that lack a valid document_id or fiscal_operation_line_id. Prevent the + creation of fiscal document lines without these mandatory fields to avoid + system crashes due to invalid records. If the conditions are not met, return an + empty list instead of creating any records. This supports the creation of + account.move.line records with NULL values for fiscal_document_line_id where + necessary. + """ + + if self._context.get("create_from_move_line"): + # Filter out the dictionaries that do not meet the conditions + filtered_vals_list = [ + vals + for vals in vals_list + if vals.get("document_id") and vals.get("fiscal_operation_line_id") + ] + # Stop execution and return empty if no dictionary meets the conditions + if not filtered_vals_list: + return [] + # Assign the filtered list back to the original list for further processing + vals_list = filtered_vals_list + + return super().create(vals_list) + + def _update_cache(self, values, validate=True): + """ + Overriden to avoid raising error with ensure_one() in super() + when called from some account.move.line onchange + as we allow empty fiscal document line in account.move.line. + """ + if len(self) == 0: + return + return super()._update_cache(values, validate) diff --git a/l10n_br_account/models/fiscal_operation.py b/l10n_br_account/models/fiscal_operation.py new file mode 100644 index 000000000000..722c472e15b0 --- /dev/null +++ b/l10n_br_account/models/fiscal_operation.py @@ -0,0 +1,85 @@ +# Copyright (C) 2009 - TODAY Renato Lima - Akretion +# Copyright (C) 2019 - TODAY Raphaël Valyi - Akretion +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import fields, models + +# from odoo.addons.account.models.account_invoice import TYPE2JOURNAL + +FISCAL_TYPE_INVOICE = { + "purchase": "in_invoice", + "purchase_refund": "in_refund", + "return_in": "in_refund", + "sale": "out_invoice", + "sale_refund": "out_refund", + "return_out": "out_refund", + "other": "out_invoice", +} + + +class Operation(models.Model): + _inherit = "l10n_br_fiscal.operation" + + journal_id = fields.Many2one( + comodel_name="account.journal", + string="Account Journal", + company_dependent=True, + domain="[('type', 'in', {'out': ['sale', 'general'], 'in': " + "['purchase', 'general'], 'all': ['sale', 'purchase', " + "'general']}.get(fiscal_operation_type, []))]", + ) + + fiscal_position_id = fields.Many2one( + comodel_name="account.fiscal.position", + string="Fiscal Position", + company_dependent=True, + ) + + deductible_taxes = fields.Boolean( + company_dependent=True, + ) + + def _change_action_view(self, action): + fiscal_op_type = action.get("context") + if fiscal_op_type == "out": + new_action = self.env.ref("l10n_br_account.fiscal_invoice_out_action") + elif fiscal_op_type == "in": + new_action = self.env.ref("l10n_br_account.fiscal_invoice_out_action") + else: + new_action = self.env.ref("l10n_br_account.fiscal_invoice_all_action") + invoice_type = FISCAL_TYPE_INVOICE[self.fiscal_type] + # TODO FIXME migrate! + journal_type = "TODO" # TYPE2JOURNAL[invoice_type] + new_action["context"] = { + "move_type": invoice_type, + "default_fiscal_operation_type": self.fiscal_operation_type, + "default_fiscal_operation_id": self.id, + "journal_type": journal_type, + } + new_action["domain"] = action.get("domain", {}) + return new_action.read()[0] + + def action_create_new(self): + action = super().action_create_new() + action["res_model"] = "account.move" + action["view_id"] = self.env.ref("l10n_br_account.fiscal_invoice_form").id + action["context"] = self._change_action_view(action)["context"] + return action + + def open_action(self): + action = super().open_action() + return self._change_action_view(action) + + def _fiscal_document_object(self): + return self.env["account.move"] + + def _line_domain(self, company, partner, product): + domain = super()._line_domain(company=company, partner=partner, product=product) + + domain += [ + "|", + ("fiscal_position_id", "=", partner.property_account_position_id.id), + ("fiscal_position_id", "=", False), + ] + + return domain diff --git a/l10n_br_account/models/fiscal_operation_line.py b/l10n_br_account/models/fiscal_operation_line.py new file mode 100644 index 000000000000..255adfff9d0a --- /dev/null +++ b/l10n_br_account/models/fiscal_operation_line.py @@ -0,0 +1,15 @@ +# Copyright (C) 2009 - TODAY Renato Lima - Akretion +# Copyright (C) 2019 - TODAY Raphaël Valyi - Akretion +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import fields, models + + +class OperationLine(models.Model): + _inherit = "l10n_br_fiscal.operation.line" + + fiscal_position_id = fields.Many2one( + comodel_name="account.fiscal.position", + string="Fiscal Position", + company_dependent=True, + ) diff --git a/l10n_br_account/models/fiscal_tax.py b/l10n_br_account/models/fiscal_tax.py new file mode 100644 index 000000000000..fa6e844624cb --- /dev/null +++ b/l10n_br_account/models/fiscal_tax.py @@ -0,0 +1,89 @@ +# Copyright (C) 2009 - TODAY Renato Lima - Akretion +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import api, fields, models + + +class FiscalTax(models.Model): + _inherit = "l10n_br_fiscal.tax" + + def account_taxes(self, user_type="sale", fiscal_operation=False): + account_taxes = self.env["account.tax"] + for fiscal_tax in self: + taxes = fiscal_tax._account_taxes() + # Atualiza os impostos contábeis relacionados aos impostos fiscais + account_taxes |= taxes.filtered( + lambda t: t.type_tax_use == user_type and t.active and not t.deductible + ) + # Caso a operação fiscal esteja definida para usar o impostos + # dedutíveis os impostos contáveis dedutíveis são adicionados na linha + # da movimentação/fatura + if fiscal_operation and fiscal_operation.deductible_taxes: + account_taxes |= taxes.filtered( + lambda t: t.type_tax_use == user_type and t.active and t.deductible + ) + + return account_taxes + + def _account_taxes(self): + self.ensure_one() + account_tax_group = self.tax_group_id.account_tax_group() + company = self.env.company + if self.env.context.get("default_company_id") or self.env.context.get( + "allowed_company_ids" + ): + company = self.env["res.company"].browse( + self.env.context.get("default_company_id") + or self.env.context.get("allowed_company_ids")[0] + ) + return self.env["account.tax"].search( + [ + ("tax_group_id", "=", account_tax_group.id), + ("active", "=", True), + ("company_id", "=", company.id), + ] + ) + + def _create_account_tax(self): + for fiscal_tax in self: + account_taxes = fiscal_tax._account_taxes() + if not account_taxes: + tax_users = {"sale": "out", "purchase": "in"} + + for tax_use in tax_users.keys(): + tax_values = { + "name": fiscal_tax.name + " " + tax_users.get(tax_use), + "type_tax_use": tax_use, + "fiscal_tax_ids": [(4, fiscal_tax.id)], + "tax_group_id": fiscal_tax.tax_group_id.account_tax_group().id, + "amount": 0.00, + } + + self.env["account.tax"].create(tax_values) + + else: + account_taxes.write({"fiscal_tax_ids": [(4, fiscal_tax.id)]}) + + @api.model_create_multi + def create(self, vals_list): + fiscal_taxes = super().create(vals_list) + fiscal_taxes._create_account_tax() + return fiscal_taxes + + def unlink(self): + for fiscal_tax in self: + account_taxes = fiscal_tax._account_taxes() + for account_tax in account_taxes: + account_tax.fiscal_tax_ids -= fiscal_tax + + if not account_tax.fiscal_tax_ids: + active_datetime = fields.Datetime.to_string(fields.Datetime.now()) + + account_tax.write( + { + "name": (account_tax.name + " Inative " + active_datetime), + "fiscal_tax_ids": False, + "active": False, + } + ) + return super().unlink() diff --git a/l10n_br_account/models/fiscal_tax_group.py b/l10n_br_account/models/fiscal_tax_group.py new file mode 100644 index 000000000000..b8b283034947 --- /dev/null +++ b/l10n_br_account/models/fiscal_tax_group.py @@ -0,0 +1,14 @@ +# Copyright (C) 2009 - TODAY Renato Lima - Akretion +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import models + + +class FiscalTaxGroup(models.Model): + _inherit = "l10n_br_fiscal.tax.group" + + def account_tax_group(self): + self.ensure_one() + return self.env["account.tax.group"].search( + [("fiscal_tax_group_id", "in", self.ids)], limit=1 + ) diff --git a/l10n_br_account/models/ir_model_data.py b/l10n_br_account/models/ir_model_data.py new file mode 100644 index 000000000000..373fc2e72858 --- /dev/null +++ b/l10n_br_account/models/ir_model_data.py @@ -0,0 +1,26 @@ +# Copyright (C) 2023 - TODAY Raphaël Valyi - Akretion +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import api, models + + +class IrModelData(models.Model): + _inherit = "ir.model.data" + + @api.model + def _update_xmlids(self, data_list, update=False): + """ + Because of the _inherits, Odoo naturally expects XML records + for account.move and account.move.line to have respectively a fiscal_document_id + and a fiscal_document_line_id. But in l10n_br_account we allow account moves + without fiscal documents. This override avoids crashing Odoo here. + """ + filtered_data_list = filter( + lambda data: ( + data["record"]._name + not in ("l10n_br_fiscal.document", "l10n_br_fiscal.document.line") + or data["record"].id + ), + data_list, + ) + return super()._update_xmlids(filtered_data_list, update) diff --git a/l10n_br_account/readme/CONFIGURE.rst b/l10n_br_account/readme/CONFIGURE.rst new file mode 100644 index 000000000000..0fbf6ba324f5 --- /dev/null +++ b/l10n_br_account/readme/CONFIGURE.rst @@ -0,0 +1,3 @@ +To configure this module, you need to: + +* go to ... diff --git a/l10n_br_account/readme/CONTRIBUTORS.rst b/l10n_br_account/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000000..2b39f5f93511 --- /dev/null +++ b/l10n_br_account/readme/CONTRIBUTORS.rst @@ -0,0 +1,17 @@ +* `Akretion `_: + + * Renato Lima + * Raphaël Valyi + +* `KMEE `_: + + * Luis Felipe Mileo + +* `Escodoo `_: + + * Marcel Savegnago + +* `Engenere `_: + + * Antônio S. Pereira Neto + * Felipe Motter Pereira diff --git a/l10n_br_account/readme/DESCRIPTION.rst b/l10n_br_account/readme/DESCRIPTION.rst new file mode 100644 index 000000000000..4e03c887a332 --- /dev/null +++ b/l10n_br_account/readme/DESCRIPTION.rst @@ -0,0 +1,2 @@ +This module was written to extend the functionality of ... to support ... +and allow you to ... diff --git a/l10n_br_account/readme/HISTORY.rst b/l10n_br_account/readme/HISTORY.rst new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/l10n_br_account/readme/INSTALL.rst b/l10n_br_account/readme/INSTALL.rst new file mode 100644 index 000000000000..61d088b3f425 --- /dev/null +++ b/l10n_br_account/readme/INSTALL.rst @@ -0,0 +1,3 @@ +To install this module, you need to: + +* do this ... diff --git a/l10n_br_account/readme/ROADMAP.rst b/l10n_br_account/readme/ROADMAP.rst new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/l10n_br_account/readme/USAGE.rst b/l10n_br_account/readme/USAGE.rst new file mode 100644 index 000000000000..c99b6d28f65e --- /dev/null +++ b/l10n_br_account/readme/USAGE.rst @@ -0,0 +1,3 @@ +To use this module, you need to: + +* go to ... diff --git a/l10n_br_account/report/__init__.py b/l10n_br_account/report/__init__.py new file mode 100644 index 000000000000..91e2c48d2d75 --- /dev/null +++ b/l10n_br_account/report/__init__.py @@ -0,0 +1 @@ +# from . import account_invoice_report diff --git a/l10n_br_account/report/account_invoice_report.py b/l10n_br_account/report/account_invoice_report.py new file mode 100644 index 000000000000..e9646f553ed5 --- /dev/null +++ b/l10n_br_account/report/account_invoice_report.py @@ -0,0 +1,155 @@ +# Copyright (C) 2016-Today - Akretion (). +# @author Magno Costa +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + +from odoo.addons.l10n_br_fiscal.constants.fiscal import ( + DOCUMENT_ISSUER, + PRODUCT_FISCAL_TYPE, +) + + +class AccountInvoiceReport(models.Model): + _inherit = "account.invoice.report" + + issuer = fields.Selection( + selection=DOCUMENT_ISSUER, + ) + + fiscal_operation_id = fields.Many2one( + comodel_name="l10n_br_fiscal.operation", + string="Operation", + ) + + fiscal_operation_line_id = fields.Many2one( + comodel_name="l10n_br_fiscal.operation.line", + string="Operation Line", + ) + + document_type_id = fields.Many2one( + comodel_name="l10n_br_fiscal.document.type", + ) + + document_serie_id = fields.Many2one( + comodel_name="l10n_br_fiscal.document.serie", + ) + + fiscal_type = fields.Selection(selection=PRODUCT_FISCAL_TYPE, string="Tipo Fiscal") + + cfop_id = fields.Many2one( + comodel_name="l10n_br_fiscal.cfop", + string="CFOP", + ) + + icms_value = fields.Float(string="Valor ICMS", digits="Account") + + icmsst_value = fields.Float(string="Valor ICMS ST", digits="Account") + + icms_origin_value = fields.Float(string="Valor Difal Origem", digits="Account") + + icms_destination_value = fields.Float( + string="Valor Difal Destino", + digits="Account", + ) + + icmsfcp_value = fields.Float(string="Valor Difal FCP", digits="Account") + + ipi_value = fields.Float(string="Valor IPI", digits="Account") + + pis_value = fields.Float(string="Valor PIS", digits="Account") + + cofins_value = fields.Float(string="Valor COFINS", digits="Account") + + ii_value = fields.Float(string="Valor II", digits="Account") + + total_with_taxes = fields.Float(string="Total com Impostos", digits="Account") + cest_id = fields.Many2one( + comodel_name="l10n_br_fiscal.cest", + string="CEST", + ) + + ncm_id = fields.Many2one(comodel_name="l10n_br_fiscal.ncm", string="NCM") + + def _select(self): + select_str = super()._select() + select_str += """ + , sub.issuer + , sub.document_type_id + , sub.document_serie_id + , sub.fiscal_operation_id + , sub.fiscal_operation_line_id + , sub.cfop_id + , sub.ncm_id + , sub.cest_id + , sub.fiscal_type + , sub.icms_value + , sub.icms_origin_value + , sub.icms_destination_value + , sub.icmsfcp_value + , sub.icmsst_value + , sub.ipi_value + , sub.pis_value + , sub.cofins_value + , sub.ii_value + , sub.total_with_taxes + """ + return select_str + + def _sub_select(self): + select_str = super()._sub_select() + + select_str += """ + , fd.issuer + , fd.document_type_id + , fd.document_serie_id + , fdl.fiscal_operation_id + , fdl.fiscal_operation_line_id + , fdl.cfop_id + , fdl.ncm_id + , fdl.cest_id + , fdl.fiscal_type + , SUM(fdl.icms_value) as icms_value + , SUM(fdl.icms_origin_value) as icms_origin_value + , SUM(fdl.icms_destination_value) as icms_destination_value + , SUM(fdl.icmsfcp_value) as icmsfcp_value + , SUM(fdl.icmsst_value) as icmsst_value + , SUM(fdl.ipi_value) as ipi_value + , SUM(fdl.pis_value) as pis_value + , SUM(fdl.cofins_value) as cofins_value + , SUM(fdl.ii_value) as ii_value + , SUM( + ail.price_subtotal + fdl.ipi_value + + fdl.icmsst_value + fdl.freight_value + + fdl.insurance_value + fdl.other_value) + as total_with_taxes + """ + return select_str + + def _from(self): + from_str = super()._from() + from_str += """ + LEFT JOIN l10n_br_fiscal_document fd ON + fd.id = ai.fiscal_document_id + LEFT JOIN l10n_br_fiscal_document_line fdl ON + fdl.id = ail.fiscal_document_line_id + LEFT JOIN product_product prd ON prd.id = ail.product_id + LEFT JOIN product_template prd_tmpl ON + prd_tmpl.id = prd.product_tmpl_id + """ + return from_str + + def _group_by(self): + group_by_str = super()._group_by() + group_by_str += """ + , fd.issuer + , fd.document_type_id + , fd.document_serie_id + , fdl.fiscal_operation_id + , fdl.fiscal_operation_line_id + , fdl.cfop_id + , fdl.ncm_id + , fdl.cest_id + , fdl.fiscal_type + """ + return group_by_str diff --git a/l10n_br_account/report/account_invoice_report_view.xml b/l10n_br_account/report/account_invoice_report_view.xml new file mode 100644 index 000000000000..44bd969286c1 --- /dev/null +++ b/l10n_br_account/report/account_invoice_report_view.xml @@ -0,0 +1,48 @@ + + + + + + l10n_br_account.invoice.report.search + account.invoice.report + + + + + + + + + + + + + diff --git a/l10n_br_account/security/ir.model.access.csv b/l10n_br_account/security/ir.model.access.csv new file mode 100644 index 000000000000..6df3fde5ef8f --- /dev/null +++ b/l10n_br_account/security/ir.model.access.csv @@ -0,0 +1,20 @@ +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" +"l10n_br_fixme_document_line_user","Fiscal Document Line for User","model_l10n_br_fiscal_document_line","base.group_user",1,1,1,1 +"l10n_br_account_document_serie_user","Fiscal Document Serie for Account User","l10n_br_fiscal.model_l10n_br_fiscal_document_serie","account.group_account_invoice",1,0,0,0 +"l10n_br_account_document_type_user","Fiscal Document Type for Account User","l10n_br_fiscal.model_l10n_br_fiscal_document_type","account.group_account_invoice",1,0,0,0 +"l10n_br_account_document_user","Fiscal Document for Account User","l10n_br_fiscal.model_l10n_br_fiscal_document","account.group_account_invoice",1,1,0,0 +"l10n_br_account_document_line_user","Fiscal Document Line for Account User","l10n_br_fiscal.model_l10n_br_fiscal_document_line","account.group_account_invoice",1,1,0,0 +"l10n_br_account_cfop_user","Fiscal CFOP for Account User","l10n_br_fiscal.model_l10n_br_fiscal_cfop","account.group_account_invoice",1,0,0,0 +"l10n_br_account_ncm_user","Fiscal NCM for Account User","l10n_br_fiscal.model_l10n_br_fiscal_ncm","account.group_account_invoice",1,1,0,0 +"l10n_br_account_nbs_user","Fiscal NBS for Account User","l10n_br_fiscal.model_l10n_br_fiscal_nbs","account.group_account_invoice",1,1,0,0 +"l10n_br_account_service_type_user","Fiscal Service Type for Account User","l10n_br_fiscal.model_l10n_br_fiscal_service_type","account.group_account_invoice",1,0,0,0 +"l10n_br_account_cest_user","Fiscal CEST for Account User","l10n_br_fiscal.model_l10n_br_fiscal_cest","account.group_account_invoice",1,0,0,0 +"l10n_br_account_product_genre_user","Fiscal Product Genre for Account User","l10n_br_fiscal.model_l10n_br_fiscal_product_genre","account.group_account_invoice",1,0,0,0 +"l10n_br_account_simplified_tax_user","Fiscal Simplified Tax for Account User","l10n_br_fiscal.model_l10n_br_fiscal_simplified_tax","account.group_account_invoice",1,0,0,0 +"l10n_br_account_simplified_tax_range_user","Fiscal Simplified Range Tax for Account User","l10n_br_fiscal.model_l10n_br_fiscal_simplified_tax_range","account.group_account_invoice",1,0,0,0 +"l10n_br_account_cst_user","Fiscal CST for Account User","l10n_br_fiscal.model_l10n_br_fiscal_cst","account.group_account_invoice",1,0,0,0 +"l10n_br_account_tax_group_user","Fiscal Tax Group for Account User","l10n_br_fiscal.model_l10n_br_fiscal_tax_group","account.group_account_invoice",1,0,0,0 +"l10n_br_account_tax_user","Fiscal Tax for Account User","l10n_br_fiscal.model_l10n_br_fiscal_tax","account.group_account_invoice",1,0,0,0 +"l10n_br_account_operation_user","Fiscal Operation for Account User","l10n_br_fiscal.model_l10n_br_fiscal_operation","account.group_account_invoice",1,0,0,0 +"l10n_br_account_operation_line_user","Fiscal Operation Line for Account User","l10n_br_fiscal.model_l10n_br_fiscal_operation_line","account.group_account_invoice",1,0,0,0 +"l10n_br_account_partner_profile_user","Fiscal Partner Profile for Account User","l10n_br_fiscal.model_l10n_br_fiscal_partner_profile","account.group_account_invoice",1,0,0,0 diff --git a/l10n_br_account/static/description/icon.png b/l10n_br_account/static/description/icon.png new file mode 100644 index 000000000000..d43df0f9de6e Binary files /dev/null and b/l10n_br_account/static/description/icon.png differ diff --git a/l10n_br_account/static/description/index.html b/l10n_br_account/static/description/index.html new file mode 100644 index 000000000000..f0779dbb0bb8 --- /dev/null +++ b/l10n_br_account/static/description/index.html @@ -0,0 +1,465 @@ + + + + + + +Brazilian Localization Account + + + +
+

Brazilian Localization Account

+ + +

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

+

This module was written to extend the functionality of … to support … +and allow you to …

+

Table of contents

+ +
+

Installation

+

To install this module, you need to:

+
    +
  • do this …
  • +
+
+
+

Configuration

+

To configure this module, you need to:

+
    +
  • go to …
  • +
+
+
+

Usage

+

To use this module, you need to:

+
    +
  • go to …
  • +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • Akretion
  • +
+
+
+

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:

+

renatonlima rvalyi

+

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_account/tests/__init__.py b/l10n_br_account/tests/__init__.py new file mode 100644 index 000000000000..25b0abd74a2c --- /dev/null +++ b/l10n_br_account/tests/__init__.py @@ -0,0 +1,13 @@ +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from . import test_account_move_sn +from . import test_account_move_lc +from . import test_account_taxes +from . import test_non_fiscal_move +from . import test_document_date +from . import test_invoice_refund +from . import test_move_discount + +# FIXME: a few "AssertionError: field tax_ids is not visible" +# migration errors to fix! +from . import test_multi_localizations_invoice diff --git a/l10n_br_account/tests/common.py b/l10n_br_account/tests/common.py new file mode 100644 index 000000000000..03ff42b19773 --- /dev/null +++ b/l10n_br_account/tests/common.py @@ -0,0 +1,374 @@ +# Copyright 2023 - TODAY Akretion - Raphael Valyi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields +from odoo.tests.common import Form + +from odoo.addons.account.tests.common import AccountTestInvoicingCommon + + +class AccountMoveBRCommon(AccountTestInvoicingCommon): + """ + This is the base test suite for l10n_br_account with proper Brazilian + Charts of Accounts and Brazilian data. + """ + + @classmethod + def setUpClass(cls, chart_template_ref=None): + super().setUpClass(chart_template_ref) + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + # super().setUpClass() would duplicate some random IPI tax + # we need to delete these duplicates to avoid errors: + cls.tax_sale_b.unlink() + cls.tax_purchase_b.unlink() + + cls.env.user.groups_id |= cls.env.ref("l10n_br_fiscal.group_manager") + cls.product_a.write( + { + "default_code": "prod_a", + "standard_price": 1000.0, + "ncm_id": cls.env.ref("l10n_br_fiscal.ncm_94033000").id, + "fiscal_genre_id": cls.env.ref("l10n_br_fiscal.product_genre_94").id, + "fiscal_type": "00", + "icms_origin": "5", + "taxes_id": False, + "tax_icms_or_issqn": "icms", + "uoe_id": cls.env.ref("uom.product_uom_kgm").id, + } + ) + cls.fiscal_type_product_product_a = cls.env["ir.property"].create( + { + "name": "fiscal_type", + "fields_id": cls.env["ir.model.fields"] + .search( + [("model", "=", "product.template"), ("name", "=", "fiscal_type")] + ) + .id, + "value": "04", + "type": "selection", + "res_id": cls.product_a.id, + } + ) + cls.fiscal_origin_product_product_a = cls.env["ir.property"].create( + { + "name": "fiscal_origin", + "fields_id": cls.env["ir.model.fields"] + .search( + [("model", "=", "product.template"), ("name", "=", "icms_origin")] + ) + .id, + "value": "5", + "type": "selection", + "res_id": cls.product_a.id, + } + ) + + cls.product_b.write( + { + "default_code": "prod_b", + "lst_price": 1000.0, + "ncm_id": cls.env.ref("l10n_br_fiscal.ncm_94013090").id, + "fiscal_genre_id": cls.env.ref("l10n_br_fiscal.product_genre_94").id, + "fiscal_type": "00", + "icms_origin": "0", + "taxes_id": False, + "tax_icms_or_issqn": "icms", + "uoe_id": cls.env.ref("uom.product_uom_kgm").id, + } + ) + cls.fiscal_type_product_product_b = cls.env["ir.property"].create( + { + "name": "fiscal_type", + "fields_id": cls.env["ir.model.fields"] + .search( + [("model", "=", "product.template"), ("name", "=", "fiscal_type")] + ) + .id, + "value": "00", + "type": "selection", + "res_id": cls.product_b.id, + } + ) + cls.fiscal_origin_product_product_b = cls.env["ir.property"].create( + { + "name": "fiscal_origin", + "fields_id": cls.env["ir.model.fields"] + .search( + [("model", "=", "product.template"), ("name", "=", "icms_origin")] + ) + .id, + "value": "0", + "type": "selection", + "res_id": cls.product_b.id, + } + ) + + cls.partner_a.write( + { + "cnpj_cpf": "49.190.159/0001-05", + "is_company": "1", + "ind_ie_dest": "1", + "tax_framework": "3", + "legal_name": "partner A", + "country_id": cls.env.ref("base.br").id, + "state_id": cls.env.ref("base.state_br_rj").id, + "district": "Centro", + "zip": "01311-915", + "fiscal_profile_id": cls.env.ref( + "l10n_br_fiscal.partner_fiscal_profile_snc" + ).id, + } + ) + cls.partner_b.write( + { + "cnpj_cpf": "42.591.651/0001-43", + "is_company": "0", + "legal_name": "partner B", + "country_id": cls.env.ref("base.br").id, + "state_id": cls.env.ref("base.state_br_ba").id, + "district": "Na PQP", + "zip": "01311-942", + "fiscal_profile_id": cls.env.ref( + "l10n_br_fiscal.partner_fiscal_profile_cnt" + ).id, + } + ) + + cls.partner_c = cls.partner_a.copy( + { + "name": "partner_c", + "cnpj_cpf": "67.405.936/0001-73", + "legal_name": "partner C", + "fiscal_profile_id": cls.env.ref( + "l10n_br_fiscal.partner_fiscal_profile_ncn" + ).id, + } + ) + + cls.fo_sale_with_icms_reduction = cls.env[ + "l10n_br_fiscal.operation.line" + ].create( + { + "name": "Venda com ICMS 12 e Redução de 26,57", + "ind_ie_dest": "1", + "cfop_internal_id": cls.env.ref("l10n_br_fiscal.cfop_5101").id, + "cfop_external_id": cls.env.ref("l10n_br_fiscal.cfop_6101").id, + "cfop_export_id": cls.env.ref("l10n_br_fiscal.cfop_7101").id, + "state": "approved", + "product_type": "04", + "fiscal_operation_id": cls.env.ref("l10n_br_fiscal.fo_venda").id, + } + ) + + @classmethod + def setup_company_data(cls, company_name, chart_template=None, **kwargs): + """ + You might want to override it to force a single chart_template. + The default behavior here is to load one for the SN and another for the LC. + """ + cnpj_cpf = kwargs.get("cnpj_cpf", "") + if company_name == "company_2_data": + company_name = "empresa 2 Simples Nacional" + chart_template = cls.env.ref( + "l10n_br_coa_simple.l10n_br_coa_simple_chart_template" + ) + cnpj_cpf = "30.360.463/0001-25" + elif company_name == "company_1_data": + company_name = "empresa 1 Lucro Presumido" + chart_template = cls.env.ref( + "l10n_br_coa_generic.l10n_br_coa_generic_template" + ) + cnpj_cpf = "18.751.708/0001-40" + + kwargs.update( + { + "country_id": cls.env.ref("base.br").id, + "currency_id": cls.env.ref("base.BRL").id, + "is_industry": True, + "cnpj_cpf": cnpj_cpf, + "state_id": cls.env.ref("base.state_br_sp").id, + } + ) + return super().setup_company_data(company_name, chart_template, **kwargs) + + @classmethod + def configure_normal_company_taxes(cls): + # Tax configuration for normal company + tax_def_model = cls.env["l10n_br_fiscal.tax.definition"] + doc_serie_model = cls.env["l10n_br_fiscal.document.serie"] + cls.pis_tax_definition_empresa_lc = tax_def_model.create( + { + "company_id": cls.company_data["company"].id, + "tax_group_id": cls.env.ref("l10n_br_fiscal.tax_group_pis").id, + "is_taxed": True, + "is_debit_credit": True, + "custom_tax": True, + "tax_id": cls.env.ref("l10n_br_fiscal.tax_pis_0_65").id, + "cst_id": cls.env.ref("l10n_br_fiscal.cst_pis_01").id, + "state": "approved", + } + ) + + cls.cofins_tax_definition_empresa_lc = tax_def_model.create( + { + "company_id": cls.company_data["company"].id, + "tax_group_id": cls.env.ref("l10n_br_fiscal.tax_group_cofins").id, + "is_taxed": True, + "is_debit_credit": True, + "custom_tax": True, + "tax_id": cls.env.ref("l10n_br_fiscal.tax_cofins_3").id, + "cst_id": cls.env.ref("l10n_br_fiscal.cst_cofins_01").id, + "state": "approved", + } + ) + + cls.icms_tax_definition_empresa_lc_icms_reduction = tax_def_model.create( + { + "company_id": cls.company_data["company"].id, + "tax_group_id": cls.env.ref("l10n_br_fiscal.tax_group_icms").id, + "is_taxed": True, + "is_debit_credit": True, + "custom_tax": True, + "tax_id": cls.env.ref("l10n_br_fiscal.tax_icms_12_red_26_57").id, + "cst_id": cls.env.ref("l10n_br_fiscal.cst_icms_20").id, + "state": "approved", + "fiscal_operation_line_id": cls.fo_sale_with_icms_reduction.id, + } + ) + + # Tax Definition for PIS and COFINS Withholding + cls.pis_wh_tax_definition_empresa_lc = tax_def_model.create( + { + "company_id": cls.company_data["company"].id, + "tax_group_id": cls.env.ref("l10n_br_fiscal.tax_group_pis_wh").id, + "is_taxed": True, + "is_debit_credit": True, + "custom_tax": True, + "tax_id": cls.env.ref("l10n_br_fiscal.tax_pis_wh_0_65").id, + "state": "expired", + } + ) + + cls.cofins_wh_tax_definition_empresa_lc = tax_def_model.create( + { + "company_id": cls.company_data["company"].id, + "tax_group_id": cls.env.ref("l10n_br_fiscal.tax_group_cofins_wh").id, + "is_taxed": True, + "is_debit_credit": True, + "custom_tax": True, + "tax_id": cls.env.ref("l10n_br_fiscal.tax_cofins_wh_3").id, + "state": "expired", + } + ) + + cls.empresa_lc_document_55_serie_1 = doc_serie_model.create( + { + "code": "1", + "name": "Série 1", + "document_type_id": cls.env.ref("l10n_br_fiscal.document_55").id, + "active": True, + } + ) + + @classmethod + def init_invoice( + cls, + move_type, + partner=None, + invoice_date=None, + post=False, + products=None, + amounts=None, + taxes=None, + currency=None, + document_type=None, + document_serie_id=None, + fiscal_operation=None, + fiscal_operation_lines=None, + document_serie=None, + document_number=None, + ): + """ + We could not override the super one because we need to inject extra BR fiscal fields. + """ + products = [] if products is None else products + amounts = [] if amounts is None else amounts + move_form = Form( + cls.env["account.move"].with_context( + default_move_type=move_type, + account_predictive_bills_disable_prediction=True, + ) + ) + move_form.invoice_date = invoice_date or fields.Date.from_string("2019-01-01") + # move_form.date = move_form.invoice_date + move_form.partner_id = partner or cls.partner_a + move_form.currency_id = currency if currency else cls.company_data["currency"] + + # extra BR fiscal params: + move_form.document_type_id = document_type + if document_serie_id is not None: + move_form.document_serie_id = document_serie_id + move_form.fiscal_operation_id = fiscal_operation + if document_number is not None: + move_form.document_number = document_number + if document_serie is not None: + move_form.document_serie = document_serie + + for index, product in enumerate(products): + with move_form.invoice_line_ids.new() as line_form: + line_form.product_id = product + + # extra BR fiscal params: + line_form.fiscal_operation_line_id = fiscal_operation_lines[index] + + if taxes: + line_form.tax_ids.clear() + line_form.tax_ids.add(taxes) + + for amount in amounts: + with move_form.invoice_line_ids.new() as line_form: + line_form.name = "test line" + # We use account_predictive_bills_disable_prediction context key so that + # this doesn't trigger prediction in case enterprise + # (hence account_predictive_bills) is installed + line_form.price_unit = amount + if taxes: + line_form.tax_ids.clear() + for tax in taxes: + line_form.tax_ids.add(tax) + + rslt = move_form.save() + + if post: + rslt.action_post() + + return rslt + + @classmethod + def sort_lines(cls, lines): + """ + Utility method to help debugging + """ + return lines.sorted( + lambda line: ( + line.exclude_from_invoice_tab, + not bool(line.tax_line_id), + line.name or "", + line.balance, + ) + ) + + @classmethod + def line_log(cls, lines, index): + """ + Utility method to help debugging + """ + lines = cls.sort_lines(lines.sorted()) + log = "LINE %s %s %s %s %s" % ( + index, + lines[index].name, + lines[index].debit, + lines[index].credit, + lines[index].account_id.name, + ) + return log diff --git a/l10n_br_account/tests/test_account_move_lc.py b/l10n_br_account/tests/test_account_move_lc.py new file mode 100644 index 000000000000..6670f060e4c7 --- /dev/null +++ b/l10n_br_account/tests/test_account_move_lc.py @@ -0,0 +1,1728 @@ +# Copyright 2023 - TODAY Akretion - Raphael Valyi +# Copyright 2024 - TODAY, Marcel Savegnago +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields +from odoo.tests.common import tagged + +from .common import AccountMoveBRCommon + + +# flake8: noqa: F841 +@tagged("post_install", "-at_install") +class AccountMoveLucroPresumido(AccountMoveBRCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.configure_normal_company_taxes() + + cls.move_out_venda = cls.init_invoice( + "out_invoice", + products=[cls.product_a], + document_type=cls.env.ref("l10n_br_fiscal.document_55"), + document_serie_id=cls.empresa_lc_document_55_serie_1, + fiscal_operation=cls.env.ref("l10n_br_fiscal.fo_venda"), + fiscal_operation_lines=[cls.env.ref("l10n_br_fiscal.fo_venda_venda")], + ) + + cls.move_out_venda_with_icms_reduction = cls.init_invoice( + "out_invoice", + products=[cls.product_a], + document_type=cls.env.ref("l10n_br_fiscal.document_55"), + document_serie_id=cls.empresa_lc_document_55_serie_1, + fiscal_operation=cls.env.ref("l10n_br_fiscal.fo_venda"), + fiscal_operation_lines=[cls.fo_sale_with_icms_reduction], + ) + + cls.move_out_simples_remessa = cls.init_invoice( + "out_invoice", + products=[cls.product_a], + document_type=cls.env.ref("l10n_br_fiscal.document_55"), + document_serie_id=cls.empresa_lc_document_55_serie_1, + fiscal_operation=cls.env.ref("l10n_br_fiscal.fo_simples_remessa"), + fiscal_operation_lines=[ + cls.env.ref("l10n_br_fiscal.fo_simples_remessa_simples_remessa") + ], + ) + + cls.env.ref("l10n_br_fiscal.fo_compras").deductible_taxes = True + cls.move_in_compra_para_revenda = cls.init_invoice( + "in_invoice", + products=[cls.product_a], + document_type=cls.env.ref("l10n_br_fiscal.document_55"), + fiscal_operation=cls.env.ref("l10n_br_fiscal.fo_compras"), + fiscal_operation_lines=[ + cls.env.ref("l10n_br_fiscal.fo_compras_compras_comercializacao") + ], + document_serie="1", + document_number="42", + ) + + # Account Moves With Tax Withholding + cls.pis_tax_definition_empresa_lc.state = "expired" + cls.cofins_tax_definition_empresa_lc.state = "expired" + cls.pis_wh_tax_definition_empresa_lc.action_approve() + cls.cofins_wh_tax_definition_empresa_lc.action_approve() + + cls.move_out_venda_tax_withholding = cls.init_invoice( + "out_invoice", + products=[cls.product_a], + document_type=cls.env.ref("l10n_br_fiscal.document_55"), + document_serie_id=cls.empresa_lc_document_55_serie_1, + fiscal_operation=cls.env.ref("l10n_br_fiscal.fo_venda"), + fiscal_operation_lines=[cls.env.ref("l10n_br_fiscal.fo_venda_venda")], + ) + + cls.move_out_simples_remessa_tax_withholding = cls.init_invoice( + "out_invoice", + products=[cls.product_a], + document_type=cls.env.ref("l10n_br_fiscal.document_55"), + document_serie_id=cls.empresa_lc_document_55_serie_1, + fiscal_operation=cls.env.ref("l10n_br_fiscal.fo_simples_remessa"), + fiscal_operation_lines=[ + cls.env.ref("l10n_br_fiscal.fo_simples_remessa_simples_remessa") + ], + ) + + cls.env.ref("l10n_br_fiscal.fo_compras").deductible_taxes = True + cls.move_in_compra_para_revenda_tax_withholding = cls.init_invoice( + "in_invoice", + products=[cls.product_a], + document_type=cls.env.ref("l10n_br_fiscal.document_55"), + fiscal_operation=cls.env.ref("l10n_br_fiscal.fo_compras"), + fiscal_operation_lines=[ + cls.env.ref("l10n_br_fiscal.fo_compras_compras_comercializacao") + ], + document_serie="1", + document_number="44", + ) + + # Set default values for the tax definitions + cls.pis_tax_definition_empresa_lc.action_approve() + cls.cofins_tax_definition_empresa_lc.action_approve() + cls.pis_wh_tax_definition_empresa_lc.state = "expired" + cls.cofins_wh_tax_definition_empresa_lc.state = "expired" + + @classmethod + def setup_company_data(cls, company_name, chart_template=None, **kwargs): + if company_name == "company_1_data": + company_name = "empresa 1 Lucro Presumido" + cnpj = "62.128.834/0001-34" + else: + company_name = "empresa 2 Lucro Presumido" + cnpj = "87.396.251/0001-15" + chart_template = cls.env.ref("l10n_br_coa_generic.l10n_br_coa_generic_template") + res = super().setup_company_data( + company_name, + chart_template, + tax_framework="3", + is_industry=True, + industry_type="00", + profit_calculation="presumed", + ripi=True, + piscofins_id=cls.env.ref("l10n_br_fiscal.tax_pis_cofins_columativo").id, + icms_regulation_id=cls.env.ref("l10n_br_fiscal.tax_icms_regulation").id, + cnae_main_id=cls.env.ref("l10n_br_fiscal.cnae_3101200").id, + document_type_id=cls.env.ref("l10n_br_fiscal.document_55").id, + **kwargs + ) + res["company"].partner_id.state_id = cls.env.ref("base.state_br_sp").id + res["company"].partner_id.cnpj_cpf = cnpj + chart_template.load_fiscal_taxes() + return res + + def test_venda_fiscal_lines(self): + self.assertEqual( + self.move_out_venda.invoice_line_ids[0].cfop_id.name, + "Venda de produção do estabelecimento", + ) + + def test_venda(self): + product_line_vals_1 = { + "name": self.product_a.display_name, + "product_id": self.product_a.id, + "account_id": self.product_a.property_account_income_id.id, + "partner_id": self.partner_a.id, + "product_uom_id": self.product_a.uom_id.id, + "quantity": 1.0, + "discount": 0.0, + "price_unit": 1000.0, + "price_subtotal": 1000.0, + "price_total": 1050.0, + "tax_line_id": False, + "currency_id": self.company_data["currency"].id, + "amount_currency": -843.5, + "debit": 0.0, + "credit": 843.5, + "date_maturity": False, + } + + tax_line_vals_cofins = { + "name": "COFINS", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "COFINS a Recolher")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "COFINS Saida")], order="id DESC", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -30.0, + "debit": 0.0, + "credit": 30.0, + "date_maturity": False, + } + + tax_line_vals_icms = { + "name": "ICMS", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "ICMS a Recolher")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "ICMS Saida")], order="id desc", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -120.0, + "debit": 0.0, + "credit": 120.0, + "date_maturity": False, + } + + tax_line_vals_ipi = { + "name": "IPI", + "product_id": False, + "account_id": self.env["account.account"] + .search( + [ + ("name", "=", "IPI a Recolher"), + ("company_id", "=", self.company_data["company"].id), + ], + order="id ASC", + limit=1, + ) + .id, # TODO find our why this complex domain is required for IPI + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "IPI Saída")], order="id desc", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -50.0, + "debit": 0.0, + "credit": 50.0, + "date_maturity": False, + } + + tax_line_vals_pis = { + "name": "PIS", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "PIS a Recolher")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "PIS Saida")], order="id desc", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -6.5, + "debit": 0.0, + "credit": 6.5, + "date_maturity": False, + } + + term_line_vals_1 = { + "name": "", + "product_id": False, + "account_id": self.company_data["default_account_receivable"].id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": False, + "currency_id": self.company_data["currency"].id, + "amount_currency": 1050.0, + "debit": 1050.0, + "credit": 0.0, + "date_maturity": fields.Date.from_string("2019-01-01"), + } + + move_vals = { + "partner_id": self.partner_a.id, + "currency_id": self.company_data["currency"].id, + "journal_id": self.company_data["default_journal_sale"].id, + "date": fields.Date.from_string("2019-01-01"), + "fiscal_position_id": False, + "payment_reference": "", + "invoice_payment_term_id": self.pay_terms_a.id, + "amount_untaxed": 1000.0, + "amount_tax": 50.0, + "amount_total": 1050.0, + } + + self.assertInvoiceValues( + self.move_out_venda, + [ + product_line_vals_1, + tax_line_vals_cofins, + tax_line_vals_icms, + tax_line_vals_ipi, + tax_line_vals_pis, + term_line_vals_1, + ], + move_vals, + ) + + def test_venda_with_icms_reduction(self): + product_line_vals_1 = { + "name": self.product_a.display_name, + "product_id": self.product_a.id, + "account_id": self.product_a.property_account_income_id.id, + "partner_id": self.partner_a.id, + "product_uom_id": self.product_a.uom_id.id, + "quantity": 1.0, + "discount": 0.0, + "price_unit": 1000.0, + "price_subtotal": 1000.0, + "price_total": 1050.0, + "tax_line_id": False, + "currency_id": self.company_data["currency"].id, + "amount_currency": -875.38, + "debit": 0.0, + "credit": 875.38, + "date_maturity": False, + } + + tax_line_vals_cofins = { + "name": "COFINS", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "COFINS a Recolher")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "COFINS Saida")], order="id DESC", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -30.0, + "debit": 0.0, + "credit": 30.0, + "date_maturity": False, + } + + tax_line_vals_icms = { + "name": "ICMS", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "ICMS a Recolher")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": 0.0, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "ICMS Saida")], order="id desc", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -88.12, + "debit": 0.0, + "credit": 88.12, + "date_maturity": False, + } + + tax_line_vals_ipi = { + "name": "IPI", + "product_id": False, + "account_id": self.env["account.account"] + .search( + [ + ("name", "=", "IPI a Recolher"), + ("company_id", "=", self.company_data["company"].id), + ], + order="id ASC", + limit=1, + ) + .id, # TODO find our why this complex domain is required for IPI + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": 0.0, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "IPI Saída")], order="id desc", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -50.0, + "debit": 0.0, + "credit": 50.0, + "date_maturity": False, + } + + tax_line_vals_pis = { + "name": "PIS", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "PIS a Recolher")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": 0.0, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "PIS Saida")], order="id desc", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -6.5, + "debit": 0.0, + "credit": 6.5, + "date_maturity": False, + } + + term_line_vals_1 = { + "name": "", + "product_id": False, + "account_id": self.company_data["default_account_receivable"].id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": False, + "currency_id": self.company_data["currency"].id, + "amount_currency": 1050.0, + "debit": 1050.0, + "credit": 0.0, + "date_maturity": fields.Date.from_string("2019-01-01"), + } + + move_vals = { + "partner_id": self.partner_a.id, + "currency_id": self.company_data["currency"].id, + "journal_id": self.company_data["default_journal_sale"].id, + "date": fields.Date.from_string("2019-01-01"), + "fiscal_position_id": False, + "payment_reference": "", + "invoice_payment_term_id": self.pay_terms_a.id, + "amount_untaxed": 1000.0, + "amount_tax": 50.0, + "amount_total": 1050.0, + } + + self.assertInvoiceValues( + self.move_out_venda_with_icms_reduction, + [ + product_line_vals_1, + tax_line_vals_cofins, + tax_line_vals_icms, + tax_line_vals_ipi, + tax_line_vals_pis, + term_line_vals_1, + ], + move_vals, + ) + + def test_venda_with_icms_reduction_with_relief(self): + # Testando com Alivio do ICMS + self.move_out_venda_with_icms_reduction.invoice_line_ids[0].icms_relief_id = 1 + self.move_out_venda_with_icms_reduction.invoice_line_ids._onchange_fiscal_taxes() + self.move_out_venda_with_icms_reduction.line_ids._compute_amounts() + + product_line_vals_1 = { + "name": self.product_a.display_name, + "product_id": self.product_a.id, + "account_id": self.product_a.property_account_income_id.id, + "partner_id": self.partner_a.id, + "product_uom_id": self.product_a.uom_id.id, + "quantity": 1.0, + "discount": 0.0, + "price_unit": 1000.0, + "price_subtotal": 1000.0, + "price_total": 1050.0, + "tax_line_id": False, + "currency_id": self.company_data["currency"].id, + "amount_currency": -839.15, + "debit": 0.0, + "credit": 839.15, + "date_maturity": False, + } + + tax_line_vals_cofins = { + "name": "COFINS", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "COFINS a Recolher")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "COFINS Saida")], order="id DESC", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -30.0, + "debit": 0.0, + "credit": 30.0, + "date_maturity": False, + } + + tax_line_vals_icms = { + "name": "ICMS", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "ICMS a Recolher")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": 0.0, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "ICMS Saida")], order="id desc", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -88.12, + "debit": 0.0, + "credit": 88.12, + "date_maturity": False, + } + + tax_line_vals_ipi = { + "name": "IPI", + "product_id": False, + "account_id": self.env["account.account"] + .search( + [ + ("name", "=", "IPI a Recolher"), + ("company_id", "=", self.company_data["company"].id), + ], + order="id ASC", + limit=1, + ) + .id, # TODO find our why this complex domain is required for IPI + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": 0.0, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "IPI Saída")], order="id desc", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -50.0, + "debit": 0.0, + "credit": 50.0, + "date_maturity": False, + } + + tax_line_vals_pis = { + "name": "PIS", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "PIS a Recolher")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": 0.0, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "PIS Saida")], order="id desc", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -6.5, + "debit": 0.0, + "credit": 6.5, + "date_maturity": False, + } + + term_line_vals_1 = { + "name": "", + "product_id": False, + "account_id": self.company_data["default_account_receivable"].id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": 0.0, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": False, + "currency_id": self.company_data["currency"].id, + "amount_currency": 1013.77, + "debit": 1013.77, + "credit": 0.0, + "date_maturity": fields.Date.from_string("2019-01-01"), + } + + move_vals = { + "partner_id": self.partner_a.id, + "currency_id": self.company_data["currency"].id, + "journal_id": self.company_data["default_journal_sale"].id, + "date": fields.Date.from_string("2019-01-01"), + "fiscal_position_id": False, + "payment_reference": "", + "invoice_payment_term_id": self.pay_terms_a.id, + "amount_untaxed": 1000.0, + "amount_tax": 50.0, + "amount_total": 1013.77, + } + + # TODO MIGRATE to v16: strangely this test works + # on my PC and not in the CI. We may fix it after merging... + # self.assertInvoiceValues( + # self.move_out_venda_with_icms_reduction, + # [ + # product_line_vals_1, + # tax_line_vals_cofins, + # tax_line_vals_icms, + # tax_line_vals_ipi, + # tax_line_vals_pis, + # term_line_vals_1, + # ], + # move_vals, + # ) + + def test_simples_remessa(self): + product_line_vals_1 = { + "name": self.product_a.display_name, + "product_id": self.product_a.id, + "account_id": self.product_a.property_account_income_id.id, + "partner_id": self.partner_a.id, + "product_uom_id": self.product_a.uom_id.id, + "quantity": 1.0, + "discount": 0.0, + "price_unit": 1000.0, + "price_subtotal": 1000.0, + "price_total": 1050.0, + "tax_line_id": False, + "currency_id": self.company_data["currency"].id, + "amount_currency": 0.0, + "debit": 0.0, + "credit": 0.0, + "date_maturity": False, + } + + tax_line_vals_cofins = { + "name": "COFINS", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "COFINS a Recolher")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "COFINS Saida")], order="id DESC", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -30.0, + "debit": 0.0, + "credit": 30.0, + "date_maturity": False, + } + + tax_line_vals_icms = { + "name": "ICMS", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "ICMS a Recolher")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "ICMS Saida")], order="id desc", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -120.0, + "debit": 0.0, + "credit": 120.0, + "date_maturity": False, + } + + tax_line_vals_ipi = { + "name": "IPI", + "product_id": False, + "account_id": self.env["account.account"] + .search( + [ + ("name", "=", "IPI a Recolher"), + ("company_id", "=", self.company_data["company"].id), + ], + order="id ASC", + limit=1, + ) + .id, # TODO find our why this complex domain is required for IPI + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "IPI Saída")], order="id desc", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -50.0, + "debit": 0.0, + "credit": 50.0, + "date_maturity": False, + } + + tax_line_vals_pis = { + "name": "PIS", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "PIS a Recolher")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "PIS Saida")], order="id desc", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -6.5, + "debit": 0.0, + "credit": 6.5, + "date_maturity": False, + } + + term_line_vals_1 = { + "name": "", + "product_id": False, + "account_id": self.company_data["default_account_receivable"].id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": False, + "currency_id": self.company_data["currency"].id, + "amount_currency": 206.5, + "debit": 206.5, + "credit": 0.0, + "date_maturity": fields.Date.from_string("2019-01-01"), + } + + move_vals = { + "partner_id": self.partner_a.id, + "currency_id": self.company_data["currency"].id, + "journal_id": self.company_data["default_journal_sale"].id, + "date": fields.Date.from_string("2019-01-01"), + "fiscal_position_id": False, + "payment_reference": "", + "invoice_payment_term_id": self.pay_terms_a.id, + "amount_untaxed": 1000, + "amount_tax": 50, + "amount_total": 206.5, + } + + self.assertInvoiceValues( + self.move_out_simples_remessa, + [ + product_line_vals_1, + tax_line_vals_cofins, + tax_line_vals_icms, + tax_line_vals_ipi, + tax_line_vals_pis, + term_line_vals_1, + ], + move_vals, + ) + + def test_compra_para_revenda(self): + """ + Test move with deductible taxes + """ + product_line_vals_1 = { + "name": self.product_a.display_name, + "product_id": self.product_a.id, + "account_id": self.product_a.property_account_expense_id.id, + "partner_id": self.partner_a.id, + "product_uom_id": self.product_a.uom_id.id, + "quantity": 1.0, + "discount": 0.0, + "price_unit": 1000.0, + "price_subtotal": 1000.0, + "price_total": 1050.0, + "tax_line_id": False, + "currency_id": self.company_data["currency"].id, + "amount_currency": 1050.0, + "debit": 1050.0, + "credit": 0.0, + "date_maturity": False, + } + + tax_line_vals_cofins = { + "name": "COFINS", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "COFINS a Compensar")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "COFINS Entrada")], order="id DESC", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": 30.0, + "debit": 30.0, + "credit": 0.0, + "date_maturity": False, + } + + tax_line_vals_cofins_comp = { + "name": "COFINS", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "COFINS s/ Vendas")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search( + [("name", "=", "COFINS Entrada Dedutível")], order="id desc", limit=1 + ) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -30.0, + "debit": 0.0, + "credit": 30.0, + "date_maturity": False, + } + + tax_line_vals_icms = { + "name": "ICMS", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "ICMS a Compensar")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "ICMS Entrada")], order="id DESC", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": 120.0, + "debit": 120.0, + "credit": 0.0, + "date_maturity": False, + } + + tax_line_vals_icms_comp = { + "name": "ICMS", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "ICMS s/ Vendas")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "ICMS Entrada Dedutível")], order="id desc", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -120.0, + "debit": 0.0, + "credit": 120.0, + "date_maturity": False, + } + + tax_line_vals_ipi = { + "name": "IPI", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "IPI a Compensar")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "IPI Entrada")], order="id desc", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": 50.0, + "debit": 50.0, + "credit": 0.0, + "date_maturity": False, + } + + tax_line_vals_ipi_comp = { + "name": "IPI", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "IPI s/ Vendas")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "IPI Entrada Dedutível")], order="id desc", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -50.0, + "debit": 0.0, + "credit": 50.0, + "date_maturity": False, + } + + tax_line_vals_pis = { + "name": "PIS", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "PIS a Compensar")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "PIS Entrada")], order="id desc", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": 6.5, + "debit": 6.5, + "credit": 0.0, + "date_maturity": False, + } + + tax_line_vals_pis_comp = { + "name": "PIS", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "PIS s/ Vendas")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "PIS Entrada Dedutível")], order="id desc", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -6.5, + "debit": 0.0, + "credit": 6.5, + "date_maturity": False, + } + + term_line_vals_1 = { + "name": "42/1-1", + "product_id": False, + "account_id": self.company_data["default_account_payable"].id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": False, + "currency_id": self.company_data["currency"].id, + "amount_currency": -1050.0, + "debit": 0.0, + "credit": 1050.0, + "date_maturity": fields.Date.from_string("2019-01-01"), + } + + move_vals = { + "partner_id": self.partner_a.id, + "currency_id": self.company_data["currency"].id, + "journal_id": self.company_data["default_journal_purchase"].id, + "date": fields.Date.from_string("2019-01-31"), + "fiscal_position_id": False, + "payment_reference": "", + "invoice_payment_term_id": self.pay_terms_a.id, + "amount_untaxed": 1000.0, + "amount_tax": 50.0, + "amount_total": 1050.0, + } + + self.assertInvoiceValues( + self.move_in_compra_para_revenda, + [ + product_line_vals_1, + tax_line_vals_cofins_comp, + tax_line_vals_cofins, + tax_line_vals_icms_comp, + tax_line_vals_icms, + tax_line_vals_ipi_comp, + tax_line_vals_ipi, + tax_line_vals_pis_comp, + tax_line_vals_pis, + term_line_vals_1, + ], + move_vals, + ) + + # TODO test effect of ind_final? + # ver aqui https://github.com/OCA/l10n-brazil/pull/2347#issuecomment-1548345563 + + # Tax Withholding Tests + def test_venda_tax_withholding(self): + product_line_vals_1 = { + "name": self.product_a.display_name, + "product_id": self.product_a.id, + "account_id": self.product_a.property_account_income_id.id, + "partner_id": self.partner_a.id, + "product_uom_id": self.product_a.uom_id.id, + "quantity": 1.0, + "discount": 0.0, + "price_unit": 1000.0, + "price_subtotal": 1000.0, + "price_total": 1050.0, + "tax_line_id": False, + "currency_id": self.company_data["currency"].id, + "amount_currency": -880.0, + "debit": 0.0, + "credit": 880.0, + "date_maturity": False, + } + + tax_line_vals_cofins = { + "name": "COFINS RET", + "product_id": False, + "account_id": self.product_a.property_account_income_id.id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "COFINS WH Saida")], order="id DESC", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": 30.0, + "debit": 30.0, + "credit": 0.0, + "date_maturity": False, + } + + tax_line_vals_icms = { + "name": "ICMS", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "ICMS a Recolher")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "ICMS Saida")], order="id desc", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -120.0, + "debit": 0.0, + "credit": 120.0, + "date_maturity": False, + } + + tax_line_vals_ipi = { + "name": "IPI", + "product_id": False, + "account_id": self.env["account.account"] + .search( + [ + ("name", "=", "IPI a Recolher"), + ("company_id", "=", self.company_data["company"].id), + ], + order="id ASC", + limit=1, + ) + .id, # TODO find our why this complex domain is required for IPI + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "IPI Saída")], order="id desc", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -50.0, + "debit": 0.0, + "credit": 50.0, + "date_maturity": False, + } + + tax_line_vals_pis = { + "name": "PIS RET", + "product_id": False, + "account_id": self.product_a.property_account_income_id.id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "PIS WH Saida")], order="id desc", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": 6.5, + "debit": 6.5, + "credit": 0.0, + "date_maturity": False, + } + + term_line_vals_1 = { + "name": "", + "product_id": False, + "account_id": self.company_data["default_account_receivable"].id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": False, + "currency_id": self.company_data["currency"].id, + "amount_currency": 1013.5, + "debit": 1013.5, + "credit": 0.0, + "date_maturity": fields.Date.from_string("2019-01-01"), + } + + move_vals = { + "partner_id": self.partner_a.id, + "currency_id": self.company_data["currency"].id, + "journal_id": self.company_data["default_journal_sale"].id, + "date": fields.Date.from_string("2019-01-01"), + "fiscal_position_id": False, + "payment_reference": "", + "invoice_payment_term_id": self.pay_terms_a.id, + "amount_untaxed": 1000.0, + "amount_tax": 50.0, + "amount_total": 1013.5, + } + + self.assertInvoiceValues( + self.move_out_venda_tax_withholding, + [ + product_line_vals_1, + tax_line_vals_cofins, + tax_line_vals_icms, + tax_line_vals_ipi, + tax_line_vals_pis, + term_line_vals_1, + ], + move_vals, + ) + + def test_simples_remessa_tax_withholding(self): + product_line_vals_1 = { + "name": self.product_a.display_name, + "product_id": self.product_a.id, + "account_id": self.product_a.property_account_income_id.id, + "partner_id": self.partner_a.id, + "product_uom_id": self.product_a.uom_id.id, + "quantity": 1.0, + "discount": 0.0, + "price_unit": 1000.0, + "price_subtotal": 1000.0, + "price_total": 1050.0, + "tax_line_id": False, + "currency_id": self.company_data["currency"].id, + "amount_currency": 0.0, + "debit": 0.0, + "credit": 0.0, + "date_maturity": False, + } + + tax_line_vals_cofins = { + "name": "COFINS RET", + "product_id": False, + "account_id": self.product_a.property_account_income_id.id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "COFINS WH Saida")], order="id DESC", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": 30.0, + "debit": 30.0, + "credit": 0.0, + "date_maturity": False, + } + + tax_line_vals_icms = { + "name": "ICMS", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "ICMS a Recolher")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "ICMS Saida")], order="id desc", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -120.0, + "debit": 0.0, + "credit": 120.0, + "date_maturity": False, + } + + tax_line_vals_ipi = { + "name": "IPI", + "product_id": False, + "account_id": self.env["account.account"] + .search( + [ + ("name", "=", "IPI a Recolher"), + ("company_id", "=", self.company_data["company"].id), + ], + order="id ASC", + limit=1, + ) + .id, # TODO find our why this complex domain is required for IPI + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "IPI Saída")], order="id desc", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -50.0, + "debit": 0.0, + "credit": 50.0, + "date_maturity": False, + } + + tax_line_vals_pis = { + "name": "PIS RET", + "product_id": False, + "account_id": self.product_a.property_account_income_id.id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "PIS WH Saida")], order="id desc", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": 6.5, + "debit": 6.5, + "credit": 0.0, + "date_maturity": False, + } + + term_line_vals_1 = { + "name": "", + "product_id": False, + "account_id": self.company_data["default_account_receivable"].id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": False, + "currency_id": self.company_data["currency"].id, + "amount_currency": 133.5, + "debit": 133.5, + "credit": 0.0, + "date_maturity": fields.Date.from_string("2019-01-01"), + } + + move_vals = { + "partner_id": self.partner_a.id, + "currency_id": self.company_data["currency"].id, + "journal_id": self.company_data["default_journal_sale"].id, + "date": fields.Date.from_string("2019-01-01"), + "fiscal_position_id": False, + "payment_reference": "", + "invoice_payment_term_id": self.pay_terms_a.id, + "amount_untaxed": 1000.0, # FIXME is this correct for a simples remessa?? + "amount_tax": 50.0, + "amount_total": 133.5, + } + + self.assertInvoiceValues( + self.move_out_simples_remessa_tax_withholding, + [ + product_line_vals_1, + tax_line_vals_cofins, + tax_line_vals_icms, + tax_line_vals_ipi, + tax_line_vals_pis, + term_line_vals_1, + ], + move_vals, + ) + + def test_compra_para_revenda_tax_withholding(self): + """ + Test move with deductible taxes and tax withholding + """ + product_line_vals_1 = { + "name": self.product_a.display_name, + "product_id": self.product_a.id, + "account_id": self.product_a.property_account_expense_id.id, + "partner_id": self.partner_a.id, + "product_uom_id": self.product_a.uom_id.id, + "quantity": 1.0, + "discount": 0.0, + "price_unit": 1000.0, + "price_subtotal": 1000.0, + "price_total": 1050.0, + "tax_line_id": False, + "currency_id": self.company_data["currency"].id, + "amount_currency": 1050.0, + "debit": 1050.0, + "credit": 0.0, + "date_maturity": False, + } + + tax_line_vals_cofins = { + "name": "COFINS RET", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "COFINS a Recolher")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "COFINS WH Entrada")], order="id DESC", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -30.0, + "debit": 0.0, + "credit": 30.0, + "date_maturity": False, + } + + tax_line_vals_icms = { + "name": "ICMS", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "ICMS a Compensar")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "ICMS Entrada")], order="id DESC", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": 120.0, + "debit": 120.0, + "credit": 0.0, + "date_maturity": False, + } + + tax_line_vals_icms_comp = { + "name": "ICMS", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "ICMS s/ Vendas")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "ICMS Entrada Dedutível")], order="id desc", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -120.0, + "debit": 0.0, + "credit": 120.0, + "date_maturity": False, + } + + tax_line_vals_ipi = { + "name": "IPI", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "IPI a Compensar")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "IPI Entrada")], order="id desc", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": 50.0, + "debit": 50.0, + "credit": 0.0, + "date_maturity": False, + } + + tax_line_vals_ipi_comp = { + "name": "IPI", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "IPI s/ Vendas")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "IPI Entrada Dedutível")], order="id desc", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -50.0, + "debit": 0.0, + "credit": 50.0, + "date_maturity": False, + } + + tax_line_vals_pis = { + "name": "PIS RET", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "PIS a Recolher")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "PIS WH Entrada")], order="id desc", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -6.5, + "debit": 0.0, + "credit": 6.5, + "date_maturity": False, + } + + term_line_vals_1 = { + "name": "44/1-1", + "product_id": False, + "account_id": self.company_data["default_account_payable"].id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": False, + "currency_id": self.company_data["currency"].id, + "amount_currency": -1013.5, + "debit": 0.0, + "credit": 1013.5, + "date_maturity": fields.Date.from_string("2019-01-01"), + } + + move_vals = { + "partner_id": self.partner_a.id, + "currency_id": self.company_data["currency"].id, + "journal_id": self.company_data["default_journal_purchase"].id, + "date": fields.Date.from_string("2019-01-31"), + "fiscal_position_id": False, + "payment_reference": "", + "invoice_payment_term_id": self.pay_terms_a.id, + "amount_untaxed": 1000.0, + "amount_tax": 50.0, + "amount_total": 1013.5, + } + + self.assertInvoiceValues( + self.move_in_compra_para_revenda_tax_withholding, + [ + product_line_vals_1, + tax_line_vals_cofins, + tax_line_vals_icms_comp, + tax_line_vals_icms, + tax_line_vals_ipi_comp, + tax_line_vals_ipi, + tax_line_vals_pis, + term_line_vals_1, + ], + move_vals, + ) + + def test_composite_move(self): + # first we make a few assertions about an existing vendor bill: + self.assertEqual(len(self.move_in_compra_para_revenda.invoice_line_ids), 1) + self.assertEqual(len(self.move_in_compra_para_revenda.line_ids), 10) + self.assertEqual(self.move_in_compra_para_revenda.amount_total, 1050) + + self.assertEqual(len(self.move_in_compra_para_revenda.fiscal_document_ids), 1) + self.assertEqual( + self.move_in_compra_para_revenda.open_fiscal_document()["res_id"], + self.move_in_compra_para_revenda.fiscal_document_id.id, + ) + + # now we create a dumb fiscal document we will import in our vendor bill: + fiscal_doc_to_import = self.env["l10n_br_fiscal.document"].create( + { + "fiscal_operation_id": self.env.ref("l10n_br_fiscal.fo_compras").id, + "document_type_id": self.env.ref("l10n_br_fiscal.document_55").id, + "document_serie": 1, + "document_number": 123, + "issuer": "partner", + "partner_id": self.partner_a.id, + "fiscal_operation_type": "in", + } + ) + + fiscal_doc_line_to_import = self.env["l10n_br_fiscal.document.line"].create( + { + "document_id": fiscal_doc_to_import.id, + "name": "Purchase Test", + "product_id": self.product_a.id, + "fiscal_operation_type": "in", + "fiscal_operation_id": self.env.ref("l10n_br_fiscal.fo_compras").id, + "fiscal_operation_line_id": self.env.ref( + "l10n_br_fiscal.fo_compras_compras" + ).id, + } + ) + fiscal_doc_line_to_import._onchange_product_id_fiscal() + + # let's import it: + self.move_in_compra_para_revenda.fiscal_document_id = fiscal_doc_to_import + self.move_in_compra_para_revenda.button_import_fiscal_document() + + # now a few assertions to check if it has been properly imported: + self.assertEqual(len(self.move_in_compra_para_revenda.invoice_line_ids), 2) + self.assertEqual( + self.move_in_compra_para_revenda.invoice_line_ids[ + 1 + ].fiscal_document_line_id.product_id, + self.product_a, + ) + + self.assertEqual(len(self.move_in_compra_para_revenda.fiscal_document_ids), 2) + self.assertIn( + str(fiscal_doc_to_import.id), + str(self.move_in_compra_para_revenda.open_fiscal_document()["domain"]), + ) + self.assertIn( + str(self.move_in_compra_para_revenda.fiscal_document_id.id), + str(self.move_in_compra_para_revenda.open_fiscal_document()["domain"]), + ) + + invoice_lines = sorted( + self.move_in_compra_para_revenda.invoice_line_ids, key=lambda item: item.id + ) + self.assertEqual( + fiscal_doc_to_import.id, + invoice_lines[1].fiscal_document_line_id.document_id.id, + ) + self.assertEqual(len(self.move_in_compra_para_revenda.line_ids), 11) + self.assertEqual(self.move_in_compra_para_revenda.amount_total, 2100) + + def test_change_states(self): + # first we make a few assertions about an existing vendor bill: + document_id = self.move_out_venda.fiscal_document_id + self.assertEqual(self.move_out_venda.state, "draft") + self.assertEqual(document_id.state, "em_digitacao") + self.move_out_venda.action_post() + self.assertEqual(self.move_out_venda.state, "posted") + self.assertEqual(document_id.state, "a_enviar") + self.move_out_venda.button_draft() + self.assertEqual(self.move_out_venda.state, "draft") + self.assertEqual(document_id.state, "em_digitacao") + document_id.action_document_confirm() + self.assertEqual(self.move_out_venda.state, "posted") + self.assertEqual(document_id.state, "a_enviar") + document_id.action_document_back2draft() + self.assertEqual(self.move_out_venda.state, "draft") + self.assertEqual(document_id.state, "em_digitacao") diff --git a/l10n_br_account/tests/test_account_move_sn.py b/l10n_br_account/tests/test_account_move_sn.py new file mode 100644 index 000000000000..ce0819fa0abf --- /dev/null +++ b/l10n_br_account/tests/test_account_move_sn.py @@ -0,0 +1,188 @@ +# Copyright 2023 - TODAY Akretion - Raphael Valyi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields +from odoo.tests.common import tagged + +from .common import AccountMoveBRCommon + + +@tagged("post_install", "-at_install") +class AccountMoveSimpleNacional(AccountMoveBRCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.icms_tax_definition_empresa_simples_nacional = cls.env[ + "l10n_br_fiscal.tax.definition" + ].create( + { + "company_id": cls.company_data["company"].id, + "tax_group_id": cls.env.ref("l10n_br_fiscal.tax_group_icmssn").id, + "is_taxed": True, + "is_debit_credit": True, + "custom_tax": True, + "tax_id": cls.env.ref("l10n_br_fiscal.tax_icms_sn_com_credito").id, + "cst_id": cls.env.ref("l10n_br_fiscal.cst_icmssn_101").id, + "state": "approved", + } + ) + + cls.empresa_sn_document_55_serie_1 = cls.env[ + "l10n_br_fiscal.document.serie" + ].create( + { + "code": "1", + "name": "Série 1", + "document_type_id": cls.env.ref("l10n_br_fiscal.document_55").id, + "active": True, + } + ) + cls.move_out_revenda = cls.init_invoice( + "out_invoice", + products=[cls.product_a], + document_type=cls.env.ref("l10n_br_fiscal.document_55"), + document_serie_id=cls.empresa_sn_document_55_serie_1, + fiscal_operation=cls.env.ref("l10n_br_fiscal.fo_venda"), + fiscal_operation_lines=[cls.env.ref("l10n_br_fiscal.fo_venda_revenda")], + ) + + @classmethod + def setup_company_data(cls, company_name, chart_template=None, **kwargs): + if company_name == "company_1_data": + company_name = "empresa 1 Simples Nacional" + else: + company_name = "empresa 2 Simples Nacional" + chart_template = cls.env.ref( + "l10n_br_coa_simple.l10n_br_coa_simple_chart_template" + ) + res = super().setup_company_data( + company_name, + chart_template, + tax_framework="1", + is_industry=True, + coefficient_r=False, + ripi=True, + piscofins_id=cls.env.ref( + "l10n_br_fiscal.tax_pis_cofins_simples_nacional" + ).id, + tax_ipi_id=cls.env.ref("l10n_br_fiscal.tax_ipi_outros").id, + tax_icms_id=cls.env.ref("l10n_br_fiscal.tax_icms_sn_com_credito").id, + cnae_main_id=cls.env.ref("l10n_br_fiscal.cnae_3101200").id, + document_type_id=cls.env.ref("l10n_br_fiscal.document_55").id, + annual_revenue=815000.0, + **kwargs + ) + chart_template.load_fiscal_taxes() + return res + + def test_company_sn_config(self): + self.assertEqual( + self.company_data["company"].simplified_tax_id.name, "Anexo 2 - Indústria" + ) + self.assertEqual(self.company_data["company"].simplified_tax_percent, 8.44) + self.assertEqual( + self.company_data["company"].simplified_tax_range_id.name, "Faixa 4" + ) + + def test_revenda_fiscal_lines(self): + self.assertEqual( + self.move_out_revenda.invoice_line_ids[0].cfop_id.name, + "Venda de mercadoria adquirida ou recebida de terceiros", + ) + self.assertEqual( + self.move_out_revenda.invoice_line_ids[0].icmssn_tax_id, + self.env.ref("l10n_br_fiscal.tax_icms_sn_com_credito"), + ) + self.assertEqual( + self.move_out_revenda.invoice_line_ids[0].icms_cst_id, + self.env.ref("l10n_br_fiscal.cst_icmssn_101"), + ) + self.assertEqual(self.move_out_revenda.invoice_line_ids[0].icmssn_percent, 2.70) + + def test_revenda(self): + product_line_vals_1 = { + "name": self.product_a.display_name, + "product_id": self.product_a.id, + "account_id": self.product_a.property_account_income_id.id, + "partner_id": self.partner_a.id, + "product_uom_id": self.product_a.uom_id.id, + "quantity": 1.0, + "discount": 0.0, + "price_unit": 1000.0, + "price_subtotal": 1000.0, + "price_total": 1000.0, + "tax_line_id": False, + "currency_id": self.company_data["currency"].id, + "amount_currency": -973.0, + "debit": 0.0, + "credit": 973.0, + "date_maturity": False, + } + + tax_line_vals_icms = { + "name": "ICMS - Simples Nacional", + "product_id": False, + "account_id": self.env["account.account"] + .search([("name", "=", "ICMS a Recolher")], order="id DESC", limit=1) + .id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": self.env["account.tax"] + .search([("name", "=", "ICMS SN Saida")], order="id DESC", limit=1) + .id, + "currency_id": self.company_data["currency"].id, + "amount_currency": -27.0, + "debit": 0.0, + "credit": 27.0, + "date_maturity": False, + } + + term_line_vals_1 = { + "name": "", + "product_id": False, + "account_id": self.company_data["default_account_receivable"].id, + "partner_id": self.partner_a.id, + "product_uom_id": False, + "quantity": False, + "discount": 0.0, + "price_unit": 0.0, + "price_subtotal": 0.0, + "price_total": 0.0, + "tax_ids": [], + "tax_line_id": False, + "currency_id": self.company_data["currency"].id, + "amount_currency": 1000.0, + "debit": 1000.0, + "credit": 0.0, + "date_maturity": fields.Date.from_string("2019-01-01"), + } + + move_vals = { + "partner_id": self.partner_a.id, + "currency_id": self.company_data["currency"].id, + "journal_id": self.company_data["default_journal_sale"].id, + "date": fields.Date.from_string("2019-01-01"), + "fiscal_position_id": False, + "payment_reference": "", + "invoice_payment_term_id": self.pay_terms_a.id, + "amount_untaxed": 1000.0, + "amount_tax": 0.0, + "amount_total": 1000.0, + } + + self.assertInvoiceValues( + self.move_out_revenda, + [ + product_line_vals_1, + tax_line_vals_icms, + term_line_vals_1, + ], + move_vals, + ) diff --git a/l10n_br_account/tests/test_account_taxes.py b/l10n_br_account/tests/test_account_taxes.py new file mode 100644 index 000000000000..51a4244a3865 --- /dev/null +++ b/l10n_br_account/tests/test_account_taxes.py @@ -0,0 +1,43 @@ +# Copyright (C) 2020 Renato Lima - Akretion +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo.tests import TransactionCase + + +class TestAccountTaxes(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + cls.l10n_br_company = cls.env["res.company"].create( + {"name": "Empresa Teste do Plano de Contas Simplificado"} + ) + cls.env.user.company_ids += cls.l10n_br_company + cls.env.company = cls.l10n_br_company + + def test_account_taxes(self): + """Test if account taxes are related with fiscal taxes""" + l10n_br_coa_charts = ( + self.env["account.chart.template"] + .search([]) + .filtered( + lambda chart: chart.get_external_id() + .get(chart.id) + .split(".")[0] + .startswith("l10n_br_coa_") + if chart.get_external_id().get(chart.id) + else False + ) + ) + for l10n_br_coa_chart in l10n_br_coa_charts: + l10n_br_coa_chart.try_loading() + account_taxes = self.env["account.tax"].search( + [("company_id", "=", self.l10n_br_company.id)] + ) + + is_fiscal_taxes = False + for tax in account_taxes: + if tax.tax_group_id.fiscal_tax_group_id and tax.fiscal_tax_ids: + is_fiscal_taxes = True + + assert is_fiscal_taxes, "There are not fiscal taxes related" diff --git a/l10n_br_account/tests/test_document_date.py b/l10n_br_account/tests/test_document_date.py new file mode 100644 index 000000000000..f19e17f51338 --- /dev/null +++ b/l10n_br_account/tests/test_document_date.py @@ -0,0 +1,149 @@ +# Copyright (C) 2023-Today - Engenere (). +# @author Felipe Motter Pereira + +from datetime import datetime, time, timedelta + +from pytz import UTC, timezone + +from odoo.tests import TransactionCase + +from odoo.addons.l10n_br_fiscal.constants.fiscal import DOCUMENT_ISSUER_PARTNER + + +class TestInvoiceDiscount(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + cls.company = cls.env.ref("l10n_br_base.empresa_lucro_presumido") + + # set default user company + companies = cls.env["res.company"].search([]) + cls.env.user.company_ids = [(6, 0, companies.ids)] + cls.env.user.company_id = cls.company + + cls.invoice_account_id = cls.env["account.account"].create( + { + "company_id": cls.company.id, + "account_type": "asset_receivable", + "code": "RECTEST", + "name": "Test receivable account", + "reconcile": True, + } + ) + + cls.invoice_journal = cls.env["account.journal"].create( + { + "company_id": cls.company.id, + "name": "Invoice Journal - (test)", + "code": "INVTEST", + "type": "sale", + } + ) + + cls.invoice_line_account_id = cls.env["account.account"].create( + { + "company_id": cls.company.id, + "account_type": "income", + "code": "705070", + "name": "Product revenue account (test)", + } + ) + + cls.fiscal_operation_id = cls.env.ref("l10n_br_fiscal.fo_venda") + cls.fiscal_operation_id.deductible_taxes = True + + product_id = cls.env.ref("product.product_product_7") + + invoice_line_vals = [ + ( + 0, + 0, + { + "account_id": cls.invoice_line_account_id.id, + "product_id": product_id.id, + "quantity": 1, + "price_unit": 1000.0, + "discount_value": 100.0, + }, + ) + ] + + cls.move_id = ( + cls.env["account.move"] + .with_context(check_move_validity=False) + .create( + { + "company_id": cls.company.id, + "document_serie_id": cls.env.ref( + "l10n_br_fiscal.empresa_lc_document_55_serie_1" + ).id, + "journal_id": cls.invoice_journal.id, + "invoice_user_id": cls.env.user.id, + "fiscal_operation_id": cls.fiscal_operation_id.id, + "move_type": "out_invoice", + "currency_id": cls.company.currency_id.id, + "invoice_line_ids": invoice_line_vals, + } + ) + ) + + def test_document_date(self): + self.move_id.issuer = DOCUMENT_ISSUER_PARTNER + user_tz = timezone(self.env.user.tz or "UTC") + original_date = datetime.combine(datetime.now().date(), time.min) + # Convert the original_date to the user's timezone and remove the time for comparison + original_date_in_user_tz = ( + user_tz.localize(original_date).astimezone(UTC).replace(tzinfo=None) + ) + original_date_without_time = original_date_in_user_tz.date() + + self.move_id.invoice_date = original_date.date() + self.move_id.fiscal_document_id._compute_document_date() + + self.assertEqual( + self.move_id.fiscal_document_id.document_date.date(), + original_date_without_time, + "Computed document date is incorrect", + ) + + def test_inverse_document_date(self): + self.move_id.issuer = DOCUMENT_ISSUER_PARTNER + new_date = datetime.now() - timedelta(days=2) + self.move_id.fiscal_document_id.document_date = new_date + self.move_id.fiscal_document_id._inverse_document_date() + + self.assertEqual( + self.move_id.invoice_date, + new_date.date(), + "Inverse computed invoice date is incorrect", + ) + + def test_date_in_out(self): + self.move_id.issuer = DOCUMENT_ISSUER_PARTNER + user_tz = timezone(self.env.user.tz or "UTC") + original_date = datetime.combine(datetime.now().date(), time.min) + # Convert the original_date to the user's timezone and remove the time for comparison + original_date_in_user_tz = ( + user_tz.localize(original_date).astimezone(UTC).replace(tzinfo=None) + ) + original_date_without_time = original_date_in_user_tz.date() + self.move_id.date = original_date.date() + self.move_id.fiscal_document_id._compute_date_in_out() + + self.assertEqual( + self.move_id.fiscal_document_id.date_in_out.date(), + original_date_without_time, + "Computed date in out is incorrect", + ) + + def test_inverse_date_in_out(self): + self.move_id.issuer = DOCUMENT_ISSUER_PARTNER + new_date = datetime.now() - timedelta(days=2) + self.move_id.fiscal_document_id.date_in_out = new_date + self.move_id.fiscal_document_id._inverse_date_in_out() + self.assertEqual( + self.move_id.date, + new_date.date(), + "Inverse computed account date is incorrect", + ) diff --git a/l10n_br_account/tests/test_invoice_refund.py b/l10n_br_account/tests/test_invoice_refund.py new file mode 100644 index 000000000000..896681baf6b1 --- /dev/null +++ b/l10n_br_account/tests/test_invoice_refund.py @@ -0,0 +1,179 @@ +# Copyright (C) 2021 Ygor Carvalho - KMEE +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import fields +from odoo.exceptions import UserError +from odoo.tests import TransactionCase + + +class TestInvoiceRefund(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + + cls.sale_account = cls.env["account.account"].create( + dict( + code="X1020", + name="Product Refund Sales - (test)", + account_type="income", + ) + ) + + cls.refund_journal = cls.env["account.journal"].create( + dict( + name="Refund Journal - (test)", + code="TREJ", + type="sale", + refund_sequence=True, + default_account_id=cls.sale_account.id, + ) + ) + + cls.reverse_vals = { + "date": fields.Date.from_string("2019-02-01"), + "reason": "no reason", + "refund_method": "refund", + "journal_id": cls.refund_journal.id, + } + + cls.invoice = cls.env["account.move"].create( + dict( + name="Test Refund Invoice", + move_type="out_invoice", + invoice_payment_term_id=cls.env.ref( + "account.account_payment_term_advance" + ).id, + partner_id=cls.env.ref("l10n_br_base.res_partner_cliente1_sp").id, + journal_id=cls.refund_journal.id, + document_type_id=cls.env.ref("l10n_br_fiscal.document_55").id, + document_serie_id=cls.env.ref( + "l10n_br_fiscal.empresa_lc_document_55_serie_1" + ).id, + invoice_line_ids=[ + ( + 0, + 0, + { + "product_id": cls.env.ref("product.product_product_6").id, + "quantity": 1.0, + "price_unit": 100.0, + "account_id": cls.env["account.account"] + .search( + [ + ( + "account_type", + "=", + "income", + ), + ( + "company_id", + "=", + cls.env.company.id, + ), + ], + limit=1, + ) + .id, + "name": "Refund Test", + "uom_id": cls.env.ref("uom.product_uom_unit").id, + }, + ) + ], + ) + ) + + def test_refund(self): + reverse_vals = self.reverse_vals + + invoice = self.invoice + self.assertEqual( + invoice.state, + "draft", + "Invoice should be in state Draft", + ) + + invoice.action_post() + self.assertEqual( + invoice.state, + "posted", + "Invoice should be in state Posted", + ) + + move_reversal = ( + self.env["account.move.reversal"] + .with_context(active_model="account.move", active_ids=invoice.ids) + .create(reverse_vals) + ) + + with self.assertRaises(UserError): + move_reversal.reverse_moves() + + invoice["fiscal_operation_id"] = (self.env.ref("l10n_br_fiscal.fo_venda").id,) + + with self.assertRaises(UserError): + move_reversal.reverse_moves() + + for line_id in invoice.invoice_line_ids: + line_id["fiscal_operation_id"] = ( + self.env.ref("l10n_br_fiscal.fo_venda").id, + ) + line_id["fiscal_operation_line_id"] = self.env.ref( + "l10n_br_fiscal.fo_venda_venda" + ).id + + reversal = move_reversal.reverse_moves() + reverse_move = self.env["account.move"].browse(reversal["res_id"]) + + self.assertTrue(reverse_move) + + self.assertEqual( + reverse_move.operation_name, + "Devolução de Venda", + "The refund process was unsuccessful.", + ) + + def test_refund_force_fiscal_operation(self): + reverse_vals = self.reverse_vals + invoice = self.invoice + + invoice["fiscal_operation_id"] = (self.env.ref("l10n_br_fiscal.fo_venda").id,) + + for line_id in invoice.invoice_line_ids: + line_id["fiscal_operation_id"] = ( + self.env.ref("l10n_br_fiscal.fo_venda").id, + ) + line_id["fiscal_operation_line_id"] = self.env.ref( + "l10n_br_fiscal.fo_venda_venda" + ).id + + invoice.action_post() + self.assertEqual( + invoice.state, + "posted", + "Invoice should be in state Posted", + ) + + reverse_vals.update( + { + "force_fiscal_operation_id": self.env.ref( + "l10n_br_fiscal.fo_simples_remessa" + ).id + } + ) + move_reversal = ( + self.env["account.move.reversal"] + .with_context(active_model="account.move", active_ids=invoice.ids) + .create(reverse_vals) + ) + + reversal = move_reversal.reverse_moves() + reverse_move = self.env["account.move"].browse(reversal["res_id"]) + + self.assertTrue(reverse_move) + + self.assertEqual( + reverse_move.operation_name, + "Simples Remessa", + "The force fiscal operation process was unsuccessful.", + ) diff --git a/l10n_br_account/tests/test_move_discount.py b/l10n_br_account/tests/test_move_discount.py new file mode 100644 index 000000000000..3dba1f765ffb --- /dev/null +++ b/l10n_br_account/tests/test_move_discount.py @@ -0,0 +1,89 @@ +# Copyright (C) 2023-Today - Engenere (). +# @author Felipe Motter Pereira + +from odoo.tests import TransactionCase + + +class TestInvoiceDiscount(TransactionCase): + def setUp(self): + super().setUp() + + self.company = self.env.ref("l10n_br_base.empresa_lucro_presumido") + + # set default user company + companies = self.env["res.company"].search([]) + self.env.user.company_ids = [(6, 0, companies.ids)] + self.env.user.company_id = self.company + + self.invoice_account_id = self.env["account.account"].create( + { + "company_id": self.company.id, + "account_type": "asset_receivable", + "code": "RECTEST", + "name": "Test receivable account", + "reconcile": True, + } + ) + + self.invoice_journal = self.env["account.journal"].create( + { + "company_id": self.company.id, + "name": "Invoice Journal - (test)", + "code": "INVTEST", + "type": "sale", + } + ) + + self.invoice_line_account_id = self.env["account.account"].create( + { + "company_id": self.company.id, + "account_type": "income", + "code": "705070", + "name": "Product revenue account (test)", + } + ) + + self.fiscal_operation_id = self.env.ref("l10n_br_fiscal.fo_venda") + self.fiscal_operation_id.deductible_taxes = True + + product_id = self.env.ref("product.product_product_7") + + invoice_line_vals = [ + ( + 0, + 0, + { + "account_id": self.invoice_line_account_id.id, + "product_id": product_id.id, + "quantity": 1, + "price_unit": 1000.0, + "discount_value": 100.0, + }, + ) + ] + + self.move_id = ( + self.env["account.move"] + .with_context(check_move_validity=False) + .create( + { + "company_id": self.company.id, + "document_serie_id": self.env.ref( + "l10n_br_fiscal.empresa_lc_document_55_serie_1" + ).id, + "journal_id": self.invoice_journal.id, + "invoice_user_id": self.env.user.id, + "fiscal_operation_id": self.fiscal_operation_id.id, + "move_type": "out_invoice", + "currency_id": self.company.currency_id.id, + "invoice_line_ids": invoice_line_vals, + } + ) + ) + + def test_discount(self): + self.assertEqual(self.move_id.invoice_line_ids.price_unit, 1000) + self.assertEqual(self.move_id.invoice_line_ids.quantity, 1) + self.assertEqual(self.move_id.invoice_line_ids.discount_value, 100) + self.assertEqual(self.move_id.invoice_line_ids.discount, 10) + self.assertEqual(self.move_id.invoice_line_ids.price_subtotal, 900) diff --git a/l10n_br_account/tests/test_multi_localizations_invoice.py b/l10n_br_account/tests/test_multi_localizations_invoice.py new file mode 100644 index 000000000000..5871f4624c28 --- /dev/null +++ b/l10n_br_account/tests/test_multi_localizations_invoice.py @@ -0,0 +1,198 @@ +# Copyright (C) 2023 - TODAY Raphaël Valyi - Akretion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import logging + +from odoo.tests.common import tagged +from odoo.tests.suite import OdooSuite + +_logger = logging.getLogger(__name__) + + +# flake8: noqa: B950 - line too long +def addTest(self, test): + """ + This monkey patch is required to avoid triggering all the tests from + TestAccountMoveOutInvoiceOnchanges when it is imported. + see https://stackoverflow.com/questions/69091760/how-can-i-import-a-testclass-properly-to-inherit-from-without-it-being-run-as-a + """ + if type(test).__name__ == "MultiLocalizationsInvoice": + if test._testMethodName.startswith("test_force_"): + # in our MultiLocalizationInvoice class tests should start + # with test_force_ to be enabled in the test suite. + return OdooSuite.addTest._original_method(self, test) + + elif type(test).__name__ != "TestAccountMoveOutInvoiceOnchanges": + return OdooSuite.addTest._original_method(self, test) + + +addTest._original_method = OdooSuite.addTest +OdooSuite.addTest = addTest + + +# flake8: noqa: E402 - module level import not at top of file +from odoo.addons.account.tests.test_account_move_out_invoice import ( + TestAccountMoveOutInvoiceOnchanges, +) + + +@tagged("post_install", "-at_install") +class MultiLocalizationsInvoice(TestAccountMoveOutInvoiceOnchanges): + """ + This is a simple test for ensuring l10n_br_account doesn't break the basic + account module behavior with customer invoices. + """ + + @classmethod + def setup_company_data(cls, company_name, chart_template=None, **kwargs): + usa = cls.env.ref("base.us").id # would be Brazil by default otherwise + return super().setup_company_data( + company_name, chart_template, country_id=usa, **kwargs + ) + + @classmethod + def setUpClass(cls, chart_template_ref=None): + res = super().setUpClass(chart_template_ref) + # FIXME the following line should not be required but as for + # now if we don't add this group, creating a refund will result + # in an attempt to create a l10n_br_fiscal.subsequent.document record. + cls.env.user.groups_id |= cls.env.ref("l10n_br_fiscal.group_manager") + return res + + # The following tests list is taken with + # cat addons/account/tests/test_account_move_out_invoice.py | grep "def test_" + # then the following script will format the lines: + # for line in lines.splitlines(): + # print(line.replace("def test_", "def test_force_")) + # print(line.replace("def ", " super().").replace("(self):", "()") + "\n") + # + # ideally they should made to pass for a True multi-localizations compatibility + + def test_force_out_invoice_onchange_invoice_date(self): + return super().test_out_invoice_onchange_invoice_date() + + def test_force_out_invoice_line_onchange_product_1(self): + return super().test_out_invoice_line_onchange_product_1() + + def test_force_out_invoice_line_onchange_product_2_with_fiscal_pos_1(self): + return super().test_out_invoice_line_onchange_product_2_with_fiscal_pos_1() + + def test_force_out_invoice_line_onchange_product_2_with_fiscal_pos_2(self): + return super().test_out_invoice_line_onchange_product_2_with_fiscal_pos_2() + + # def test_force_out_invoice_line_onchange_business_fields_1(self): + # FIXME + # return super().test_out_invoice_line_onchange_business_fields_1() + + # def test_force_out_invoice_line_onchange_accounting_fields_1(self): + # FIXME this test works with most of the l10n-brazil modules + # but fails because of _order = "date desc, date_maturity ASC, id desc" + # inside l10n_br_account_payment_order/models/account_move_line.py + # return super().test_out_invoice_line_onchange_accounting_fields_1() + + def test_force_out_invoice_line_onchange_partner_1(self): + return super().test_out_invoice_line_onchange_partner_1() + + # def test_force_out_invoice_line_onchange_taxes_1(self): + # return super().test_out_invoice_line_onchange_taxes_1() + + def test_force_out_invoice_line_onchange_rounding_price_subtotal_1(self): + return super().test_out_invoice_line_onchange_rounding_price_subtotal_1() + + # def test_force_out_invoice_line_onchange_rounding_price_subtotal_2(self): + # return super().test_out_invoice_line_onchange_rounding_price_subtotal_2() + + # def test_force_out_invoice_line_onchange_taxes_2_price_unit_tax_included(self): + # return super().test_out_invoice_line_onchange_taxes_2_price_unit_tax_included() + + def test_force_out_invoice_line_onchange_analytic(self): + return super().test_out_invoice_line_onchange_analytic() + + def test_force_out_invoice_line_onchange_analytic_2(self): + return super().test_out_invoice_line_onchange_analytic_2() + + def test_force_out_invoice_line_onchange_cash_rounding_1(self): + return super().test_out_invoice_line_onchange_cash_rounding_1() + + def test_force_out_invoice_line_onchange_currency_1(self): + return super().test_out_invoice_line_onchange_currency_1() + + # def test_force_out_invoice_line_tax_fixed_price_include_free_product(self): + # FIXME + # return super().test_out_invoice_line_tax_fixed_price_include_free_product() + + # def test_force_out_invoice_line_taxes_fixed_price_include_free_product(self): + # FIXME + # return super().test_out_invoice_line_taxes_fixed_price_include_free_product() + + def test_force_out_invoice_create_refund(self): + return super().test_out_invoice_create_refund() + + def test_force_out_invoice_create_refund_multi_currency(self): + return super().test_out_invoice_create_refund_multi_currency() + + def test_force_out_invoice_create_refund_auto_post(self): + return super().test_out_invoice_create_refund_auto_post() + + def test_force_out_invoice_create_1(self): + return super().test_out_invoice_create_1() + + def test_force_out_invoice_create_child_partner(self): + return super().test_out_invoice_create_child_partner() + + def test_force_out_invoice_write_1(self): + return super().test_out_invoice_write_1() + + def test_force_out_invoice_write_2(self): + return super().test_out_invoice_write_2() + + def test_force_out_invoice_post_1(self): + return super().test_out_invoice_post_1() + + def test_force_out_invoice_post_2(self): + return super().test_out_invoice_post_2() + + def test_force_out_invoice_switch_out_refund_1(self): + return super().test_out_invoice_switch_out_refund_1() + + def test_force_out_invoice_switch_out_refund_2(self): + return super().test_out_invoice_switch_out_refund_2() + + def test_force_out_invoice_reverse_move_tags(self): + return super().test_out_invoice_reverse_move_tags() + + def test_force_out_invoice_change_period_accrual_1(self): + return super().test_out_invoice_change_period_accrual_1() + + def test_force_out_invoice_multi_date_change_period_accrual(self): + return super().test_out_invoice_multi_date_change_period_accrual() + + def test_force_out_invoice_filter_zero_balance_lines(self): + return super().test_out_invoice_filter_zero_balance_lines() + + def test_force_out_invoice_recomputation_receivable_lines(self): + return super().test_out_invoice_recomputation_receivable_lines() + + # def test_force_out_invoice_rounding_recomputation_receivable_lines(self): + # return super().test_out_invoice_rounding_recomputation_receivable_lines() + + # def test_force_out_invoice_multi_company(self): + # return super().test_out_invoice_multi_company() + + def test_force_out_invoice_multiple_switch_payment_terms(self): + return super().test_out_invoice_multiple_switch_payment_terms() + + def test_force_out_invoice_copy_custom_date(self): + return super().test_out_invoice_copy_custom_date() + + def test_force_out_invoice_note_and_tax_partner_is_set(self): + return super().test_out_invoice_note_and_tax_partner_is_set() + + # def test_force_out_invoice_reverse_caba(self): + # return super().test_out_invoice_reverse_caba() + + # def test_force_out_invoice_duplicate_currency_rate(self): + # return super().test_out_invoice_duplicate_currency_rate() + + def test_force_out_invoice_depreciated_account(self): + return super().test_out_invoice_depreciated_account() diff --git a/l10n_br_account/tests/test_non_fiscal_move.py b/l10n_br_account/tests/test_non_fiscal_move.py new file mode 100644 index 000000000000..0a330d567923 --- /dev/null +++ b/l10n_br_account/tests/test_non_fiscal_move.py @@ -0,0 +1,129 @@ +# Copyright (C) 2018 - Magno Costa - Akretion +# Copyright (C) 2023 - TODAY Raphaël Valyi - Akretion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo.tests import TransactionCase + + +class TestCustomerInvoice(TransactionCase): + """ + This is a simple test for ensuring non fiscal account.move + doesn't create fiscal document(.line). + """ + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + + cls.sale_account = cls.env["account.account"].create( + dict( + code="X1020", + name="Product Sales - (test)", + account_type="income", + reconcile=True, + ) + ) + + cls.sale_journal = cls.env["account.journal"].create( + dict( + name="Sales Journal - (test)", + code="TSAJ", + type="sale", + refund_sequence=True, + default_account_id=cls.sale_account.id, + ) + ) + + cls.init_number_of_fiscal_docs = len( + cls.env["l10n_br_fiscal.document"].search([]) + ) # NOTE search_count would increment with disabled dummy docs create + cls.init_number_of_fiscal_doc_lines = len( + cls.env["l10n_br_fiscal.document.line"].search([]) + ) + cls.invoice_1 = cls.env["account.move"].create( + dict( + name="Test Customer Invoice 1", + move_type="out_invoice", + partner_id=cls.env.ref("base.res_partner_3").id, + journal_id=cls.sale_journal.id, + invoice_line_ids=[ + ( + 0, + 0, + { + "product_id": cls.env.ref("product.product_product_5").id, + "quantity": 10.0, + "price_unit": 450.0, + "account_id": cls.env["account.account"] + .search( + [ + ( + "account_type", + "=", + "income", + ), + ( + "company_id", + "=", + cls.env.company.id, + ), + ], + limit=1, + ) + .id, + "name": "product test 5", + "uom_id": cls.env.ref("uom.product_uom_unit").id, + }, + ) + ], + ) + ) + + def test_dummy_doc_usage(self): + self.assertEqual( + self.init_number_of_fiscal_docs, + len(self.env["l10n_br_fiscal.document"].search([])), + "Non fiscal invoices should not create fiscal documents" + "They should use the company dummy document instead.", + ) + + def test_dummy_doc_line_usage(self): + self.assertEqual( + self.init_number_of_fiscal_doc_lines, + len(self.env["l10n_br_fiscal.document.line"].search([])), + "Non fiscal invoices should not create fiscal document lines" + "They should use the company dummy document line instead.", + ) + + def test_invoice_copy_with_dummy(self): + """ + Test the functionality of copying an invoice while using a fiscal dummy. + Verify that the new invoice isn't recognized as a fiscal document, + the same fiscal dummy is used, and that no new entries were created. + """ + + # Retrieve initial count of fiscal document lines + init_number_of_fiscal_doc_lines = self.env[ + "l10n_br_fiscal.document.line" + ].search_count([]) + + invoice_copy = self.invoice_1.copy() + + # Confirm that the copied invoice uses the fiscal dummy + self.assertFalse(self.invoice_1.document_type_id.exists()) + self.assertFalse(invoice_copy.document_type_id.exists()) + + # Check that no new fiscal document lines were created after copying the invoice + final_number_of_fiscal_doc_lines = self.env[ + "l10n_br_fiscal.document.line" + ].search_count([]) + self.assertEqual( + init_number_of_fiscal_doc_lines, final_number_of_fiscal_doc_lines + ) + + # Check that all account move lines are associated with the fiscal dummy + for line in invoice_copy.line_ids: + self.assertEqual(line.fiscal_document_line_id.id, False) + + self.assertEqual(len(invoice_copy), 1) diff --git a/l10n_br_account/views/account_invoice_view.xml b/l10n_br_account/views/account_invoice_view.xml new file mode 100644 index 000000000000..614bb0b94ef7 --- /dev/null +++ b/l10n_br_account/views/account_invoice_view.xml @@ -0,0 +1,527 @@ + + + + + + l10n_br_account.invoice.search + account.move + + + + + + + + + ['|','|','|', '|', ('document_number','ilike',self), ('name','ilike',self), ('invoice_origin','ilike',self), ('ref', 'ilike', self), ('partner_id', 'child_of', self)] + Account Number + + + + + + l10n_br_account.invoice.tree + account.move + + + + + + + + + {'invisible': [('document_type_id', '!=', False)]} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {'default_move_type': context.get('default_move_type'), 'journal_id': journal_id, 'default_partner_id': commercial_partner_id, 'default_currency_id': currency_id or company_currency_id, 'default_fiscal_operation_id': fiscal_operation_id, 'default_document_type_id': document_type_id} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {'default_move_type': context.get('default_move_type'), 'line_ids': line_ids, 'journal_id': journal_id, 'default_partner_id': commercial_partner_id, 'default_currency_id': currency_id or company_currency_id, 'default_fiscal_operation_id': fiscal_operation_id, 'default_document_type_id': document_type_id, 'default_exclude_from_invoice_tab': 1} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [('type_tax_use', '=?', parent.invoice_filter_type_domain), ('company_id', '=', parent.company_id)] + {'append_type_to_tax_name': not parent.invoice_filter_type_domain} + {'no_create': True} + 1 + + + + + + + + context.get('default_move_type') in ('out_invoice', 'out_refund', 'out_receipt') and [('sale_ok', '=', True), '|', ('company_id', '=', False), ('company_id', '=', parent.company_id)] or [('purchase_ok', '=', True), '|', ('company_id', '=', False), ('company_id', '=', parent.company_id)] + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +