diff --git a/l10n_br_sale_stock/README.rst b/l10n_br_sale_stock/README.rst new file mode 100644 index 000000000000..5d110d623bbd --- /dev/null +++ b/l10n_br_sale_stock/README.rst @@ -0,0 +1,131 @@ +========================================== +Brazilian Localization Sales and Warehouse +========================================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:6caf227edb60b685df07f1b80681e366eae20f0674437e09fb2000c812a0ca84 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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_sale_stock + :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_sale_stock + :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| + +Este módulo estende o módulo padrão de vendas e estoque do Odoo para atender às necessidades específicas do Brasil, especialmente no contexto de vendas de produtos com NF-e (Nota Fiscal Eletrônica). + +Ele automatiza a propagação da operação fiscal, comentários fiscais e incoterms para as faturas geradas diretamente a partir de ordens de venda ou movimentações de estoque, garantindo que essas informações sejam corretamente transferidas e utilizadas no processo de faturamento. + +**Table of contents** + +.. contents:: + :local: + +Installation +============ + +This module depends on: + +* sale_stock +* l10n_br_sale +* l10n_br_stock_account + +Configuration +============= + +No configuration required. + +Changelog +========= + +15.0.1.0.0 (2024-09-17) +~~~~~~~~~~~~~~~~~~~~~~~ + +* [MIG] Migration to version 15.0 + +12.0.1.0.0 (2020-05-29) +~~~~~~~~~~~~~~~~~~~~~~~ + +* [MIG] Migration to version 12.0 + +10.0.1.0.0 (2019-09-13) +~~~~~~~~~~~~~~~~~~~~~~~ + +* [MIG] Migration to version 10.0 + +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 +~~~~~~~~~~~~ + +* Renato Lima +* Raphaël Valyi +* Magno Costa +* Gabriel Cardoso de Faria + +Other credits +~~~~~~~~~~~~~ + +The development of this module has been financially supported by: + +* Aketion LTDA + +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-mbcosta| image:: https://github.com/mbcosta.png?size=40px + :target: https://github.com/mbcosta + :alt: mbcosta + +Current `maintainers `__: + +|maintainer-renatonlima| |maintainer-mbcosta| + +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_sale_stock/__init__.py b/l10n_br_sale_stock/__init__.py new file mode 100644 index 000000000000..371b71bcf51a --- /dev/null +++ b/l10n_br_sale_stock/__init__.py @@ -0,0 +1,6 @@ +# Copyright (C) 2013 Raphaël Valyi - Akretion +# Copyright (C) 2013 Renato Lima - Akretion +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from . import models +from . import wizards diff --git a/l10n_br_sale_stock/__manifest__.py b/l10n_br_sale_stock/__manifest__.py new file mode 100644 index 000000000000..df0e8ec6ce0e --- /dev/null +++ b/l10n_br_sale_stock/__manifest__.py @@ -0,0 +1,30 @@ +# Copyright (C) 2013 Raphaël Valyi - Akretion +# Copyright (C) 2013 Renato Lima - Akretion +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +{ + "name": "Brazilian Localization Sales and Warehouse", + "category": "Localization", + "license": "AGPL-3", + "author": "Akretion, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/l10n-brazil", + "version": "15.0.1.0.0", + "development_status": "Beta", + "maintainers": ["renatonlima", "mbcosta"], + "depends": [ + "sale_stock", + "l10n_br_sale", + "l10n_br_stock_account", + ], + "data": [ + "views/res_company_view.xml", + "views/res_config_settings_view.xml", + "views/sale_order_view.xml", + ], + "demo": [ + "demo/l10n_br_sale_stock_demo.xml", + "demo/sale_order_demo.xml", + ], + "installable": True, + "auto_install": True, +} diff --git a/l10n_br_sale_stock/demo/l10n_br_sale_stock_demo.xml b/l10n_br_sale_stock/demo/l10n_br_sale_stock_demo.xml new file mode 100644 index 000000000000..8e1f6e8042dc --- /dev/null +++ b/l10n_br_sale_stock/demo/l10n_br_sale_stock_demo.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n_br_sale_stock/demo/sale_order_demo.xml b/l10n_br_sale_stock/demo/sale_order_demo.xml new file mode 100644 index 000000000000..cce86702439a --- /dev/null +++ b/l10n_br_sale_stock/demo/sale_order_demo.xml @@ -0,0 +1,367 @@ + + + + + + + + True + + + + + + + + + + + + + + l10n_br_sale_stock - Endereço de Entrega e Faturamento diferentes + + + + + + + + draft + + + True + TESTE - TERMOS E CONDIÇÕES + TESTE - CUSTOMER ADDITIONAL DATA + TESTE - FISCAL ADDITIONAL DATA + + + + + Gaveta Preta + + 2 + + 500 + out + + + + + + + + + + + Cadeira de Escritório Preta + + 2 + + 500 + out + + + + + + + + + + + Cadeira de Escritório Preta + + 1 + + 50 + out + + + + + + + + + + + l10n_br_sale_stock - Produto e Serviço + + + + + + + + draft + + + True + TESTE de criação de duas Notas de Serviço e Produto + TESTE - CUSTOMER ADDITIONAL DATA + TESTE - FISCAL ADDITIONAL DATA + + + + + Gaveta Preta + + 2 + + 500 + out + + + + + + + + + + + Customized Odoo Development + + 10 + + 100 + out + + + + + + + + + + + l10n_br_sale_stock - Agrupamento dos Pickings + + + + + + + + draft + + + True + TESTE - TERMOS E CONDIÇÕES + TESTE - CUSTOMER ADDITIONAL DATA + TESTE - FISCAL ADDITIONAL DATA + + + + + [FURN_8900] Gaveta Preta + + 2 + + 500 + out + + + + + + + + + + + Cadeira de Escritório Preta + + 2 + + 500 + out + + + + + + + + + + + Cadeira de Escritório Preta + + 1 + + 50 + out + + + + + + + + + + + l10n_br_sale_stock - Agrupamento dos Pickings + + + + + + + + draft + + + True + TESTE - TERMOS E CONDIÇÕES + TESTE - CUSTOMER ADDITIONAL DATA + TESTE - FISCAL ADDITIONAL DATA + + + + + Gaveta Preta + + 2 + + 500 + out + + + + + + + + + + + Cadeira de Escritório Preta + + 2 + + 500 + out + + + + + + + + + + + Cadeira de Escritório Preta + + 1 + + 50 + out + + + + + + + + + + + + LC l10n_br_sale - Produtos + + + + + + + draft + + TESTE + + + + + + Cadeira de Escritório Preta + + 2 + + 500 + out + + + + + + + + + + + Cadeira de Escritório Preta + + 2 + + 500 + out + + + + + + + + + diff --git a/l10n_br_sale_stock/i18n/l10n_br_sale_stock.pot b/l10n_br_sale_stock/i18n/l10n_br_sale_stock.pot new file mode 100644 index 000000000000..905ae6faa999 --- /dev/null +++ b/l10n_br_sale_stock/i18n/l10n_br_sale_stock.pot @@ -0,0 +1,125 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_br_sale_stock +# +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_sale_stock +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_sale_order__button_create_invoice_invisible +msgid "Button Create Invoice Invisible" +msgstr "" + +#. module: l10n_br_sale_stock +#: model:ir.model,name:l10n_br_sale_stock.model_res_company +msgid "Companies" +msgstr "" + +#. module: l10n_br_sale_stock +#: model:ir.model,name:l10n_br_sale_stock.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: l10n_br_sale_stock +#: model_terms:ir.ui.view,arch_db:l10n_br_sale_stock.l10n_br_sale_stock_res_config_settings_form +msgid "Define if Invoice should be create from Sale Order or Stock Picking" +msgstr "" + +#. module: l10n_br_sale_stock +#: model:ir.model.fields,help:l10n_br_sale_stock.field_res_company__sale_create_invoice_policy +#: model:ir.model.fields,help:l10n_br_sale_stock.field_res_config_settings__sale_create_invoice_policy +msgid "" +"Define, when Product Type are not service, if Invoice should be create from " +"Sale Order or Stock Picking." +msgstr "" + +#. module: l10n_br_sale_stock +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_res_company__display_name +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_res_config_settings__display_name +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_sale_order__display_name +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_sale_order_line__display_name +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_stock_invoice_onshipping__display_name +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_stock_move__display_name +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_stock_picking__display_name +msgid "Display Name" +msgstr "" + +#. module: l10n_br_sale_stock +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_res_company__id +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_res_config_settings__id +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_sale_order__id +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_sale_order_line__id +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_stock_invoice_onshipping__id +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_stock_move__id +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_stock_picking__id +msgid "ID" +msgstr "" + +#. module: l10n_br_sale_stock +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_res_company____last_update +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_res_config_settings____last_update +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_sale_order____last_update +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_sale_order_line____last_update +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_stock_invoice_onshipping____last_update +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_stock_move____last_update +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_stock_picking____last_update +msgid "Last Modified on" +msgstr "" + +#. module: l10n_br_sale_stock +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_res_company__sale_create_invoice_policy +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_res_config_settings__sale_create_invoice_policy +msgid "Sale Create Invoice Policy" +msgstr "" + +#. module: l10n_br_sale_stock +#: code:addons/l10n_br_sale_stock/models/res_company.py:0 +#: model:ir.model.fields.selection,name:l10n_br_sale_stock.selection__res_company__sale_create_invoice_policy__sale_order +#, python-format +msgid "Sale Order" +msgstr "" + +#. module: l10n_br_sale_stock +#: model:ir.model,name:l10n_br_sale_stock.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: l10n_br_sale_stock +#: model:ir.model,name:l10n_br_sale_stock.model_sale_order_line +msgid "Sales Order Line" +msgstr "" + +#. module: l10n_br_sale_stock +#: model:ir.model,name:l10n_br_sale_stock.model_stock_invoice_onshipping +msgid "Stock Invoice Onshipping" +msgstr "" + +#. module: l10n_br_sale_stock +#: model:ir.model,name:l10n_br_sale_stock.model_stock_move +msgid "Stock Move" +msgstr "" + +#. module: l10n_br_sale_stock +#: code:addons/l10n_br_sale_stock/models/res_company.py:0 +#: model:ir.model.fields.selection,name:l10n_br_sale_stock.selection__res_company__sale_create_invoice_policy__stock_picking +#, python-format +msgid "Stock Picking" +msgstr "" + +#. module: l10n_br_sale_stock +#: model_terms:ir.ui.view,arch_db:l10n_br_sale_stock.l10n_br_sale_stock_res_config_settings_form +msgid "This default value is applied to creation of Invoice." +msgstr "" + +#. module: l10n_br_sale_stock +#: model:ir.model,name:l10n_br_sale_stock.model_stock_picking +msgid "Transfer" +msgstr "" diff --git a/l10n_br_sale_stock/i18n/pt_BR.po b/l10n_br_sale_stock/i18n/pt_BR.po new file mode 100644 index 000000000000..7302af351a9b --- /dev/null +++ b/l10n_br_sale_stock/i18n/pt_BR.po @@ -0,0 +1,163 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_br_sale_stock +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-11-27 22:00+0000\n" +"PO-Revision-Date: 2024-07-29 17:58+0000\n" +"Last-Translator: Marcel Savegnago \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 5.6.2\n" + +#. module: l10n_br_sale_stock +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_sale_order__button_create_invoice_invisible +msgid "Button Create Invoice Invisible" +msgstr "Botão Criar fatura invisível" + +#. module: l10n_br_sale_stock +#: model:ir.model,name:l10n_br_sale_stock.model_res_company +msgid "Companies" +msgstr "Empresas" + +#. module: l10n_br_sale_stock +#: model:ir.model,name:l10n_br_sale_stock.model_res_config_settings +msgid "Config Settings" +msgstr "Ajustes de Configurações" + +#. module: l10n_br_sale_stock +#: model_terms:ir.ui.view,arch_db:l10n_br_sale_stock.l10n_br_sale_stock_res_config_settings_form +msgid "Define if Invoice should be create from Sale Order or Stock Picking" +msgstr "" +"Define se a fatura deve ser criada a partir do Pedido de Venda ou da " +"Expedição de Estoque" + +#. module: l10n_br_sale_stock +#: model:ir.model.fields,help:l10n_br_sale_stock.field_res_company__sale_create_invoice_policy +#: model:ir.model.fields,help:l10n_br_sale_stock.field_res_config_settings__sale_create_invoice_policy +msgid "" +"Define, when Product Type are not service, if Invoice should be create from " +"Sale Order or Stock Picking." +msgstr "" +"Define, quando o tipo de produto não for serviço, se a fatura deve ser " +"criada a partir do Pedido de Venda ou da Expedição de Estoque." + +#. module: l10n_br_sale_stock +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_res_company__display_name +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_res_config_settings__display_name +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_sale_order__display_name +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_sale_order_line__display_name +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_stock_invoice_onshipping__display_name +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_stock_move__display_name +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_stock_picking__display_name +msgid "Display Name" +msgstr "Nome Exibido" + +#. module: l10n_br_sale_stock +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_res_company__id +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_res_config_settings__id +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_sale_order__id +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_sale_order_line__id +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_stock_invoice_onshipping__id +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_stock_move__id +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_stock_picking__id +msgid "ID" +msgstr "ID" + +#. module: l10n_br_sale_stock +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_res_company____last_update +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_res_config_settings____last_update +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_sale_order____last_update +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_sale_order_line____last_update +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_stock_invoice_onshipping____last_update +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_stock_move____last_update +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_stock_picking____last_update +msgid "Last Modified on" +msgstr "Última Modificação em" + +#. module: l10n_br_sale_stock +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_res_company__sale_create_invoice_policy +#: model:ir.model.fields,field_description:l10n_br_sale_stock.field_res_config_settings__sale_create_invoice_policy +msgid "Sale Create Invoice Policy" +msgstr "Política de Criação de Faturas de Venda" + +#. module: l10n_br_sale_stock +#: code:addons/l10n_br_sale_stock/models/res_company.py:0 +#: model:ir.model.fields.selection,name:l10n_br_sale_stock.selection__res_company__sale_create_invoice_policy__sale_order +#, python-format +msgid "Sale Order" +msgstr "Pedido de Venda" + +#. module: l10n_br_sale_stock +#: model:ir.model,name:l10n_br_sale_stock.model_sale_order +msgid "Sales Order" +msgstr "Pedido de Vendas" + +#. module: l10n_br_sale_stock +#: model:ir.model,name:l10n_br_sale_stock.model_sale_order_line +msgid "Sales Order Line" +msgstr "Linha do Pedido de Vendas" + +#. module: l10n_br_sale_stock +#: model:ir.model,name:l10n_br_sale_stock.model_stock_invoice_onshipping +msgid "Stock Invoice Onshipping" +msgstr "Faturamento ao Enviar" + +#. module: l10n_br_sale_stock +#: model:ir.model,name:l10n_br_sale_stock.model_stock_move +msgid "Stock Move" +msgstr "Movimento de Estoque" + +#. module: l10n_br_sale_stock +#: code:addons/l10n_br_sale_stock/models/res_company.py:0 +#: model:ir.model.fields.selection,name:l10n_br_sale_stock.selection__res_company__sale_create_invoice_policy__stock_picking +#, python-format +msgid "Stock Picking" +msgstr "Separação de Estoque" + +#. module: l10n_br_sale_stock +#: model_terms:ir.ui.view,arch_db:l10n_br_sale_stock.l10n_br_sale_stock_res_config_settings_form +msgid "This default value is applied to creation of Invoice." +msgstr "Este valor padrão é aplicado à criação de faturas." + +#. module: l10n_br_sale_stock +#: model:ir.model,name:l10n_br_sale_stock.model_stock_picking +msgid "Transfer" +msgstr "Transferir" + +#~ msgid "Invoice Status" +#~ msgstr "Situação do faturamento" + +#~ msgid "Invoiced" +#~ msgstr "Faturado" + +#~ msgid "" +#~ "Invoiced: an invoice already exists\n" +#~ "To Be Invoiced: need to be invoiced\n" +#~ "Not Applicable: no invoice to create" +#~ msgstr "" +#~ "Faturado: já existe uma fatura\n" +#~ "A ser faturado: precisa ser faturado\n" +#~ "Não Aplicável: não precisa ser faturado" + +#~ msgid "Not Applicable" +#~ msgstr "Não Aplicável" + +#~ msgid "Stock Rule" +#~ msgstr "Regra de estoque" + +#~ msgid "To Be Invoiced" +#~ msgstr "Para faturar" + +#~ msgid "Brazilian Localization Sales and Warehouse" +#~ msgstr "Localização Brasileira - Módulo de Vendas e Estoque" + +#~ msgid "Brazilian Localization for sale_stock_module" +#~ msgstr "Localização Brasileira - Integra os módulos de venda e estoque" diff --git a/l10n_br_sale_stock/models/__init__.py b/l10n_br_sale_stock/models/__init__.py new file mode 100644 index 000000000000..1e48fcb36da5 --- /dev/null +++ b/l10n_br_sale_stock/models/__init__.py @@ -0,0 +1,9 @@ +# Copyright (C) 2015 Renato Lima - Akretion +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from . import sale_order_line +from . import stock_move +from . import sale_order +from . import stock_picking +from . import res_company +from . import res_config_settings diff --git a/l10n_br_sale_stock/models/res_company.py b/l10n_br_sale_stock/models/res_company.py new file mode 100644 index 000000000000..173de610ef21 --- /dev/null +++ b/l10n_br_sale_stock/models/res_company.py @@ -0,0 +1,19 @@ +# Copyright (C) 2021 Akretion +# @author Magno Costa +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + + sale_create_invoice_policy = fields.Selection( + selection=[ + ("sale_order", _("Sale Order")), + ("stock_picking", _("Stock Picking")), + ], + help="Define, when Product Type are not service, if Invoice" + " should be create from Sale Order or Stock Picking.", + default="stock_picking", + ) diff --git a/l10n_br_sale_stock/models/res_config_settings.py b/l10n_br_sale_stock/models/res_config_settings.py new file mode 100644 index 000000000000..096b630f6854 --- /dev/null +++ b/l10n_br_sale_stock/models/res_config_settings.py @@ -0,0 +1,13 @@ +# Copyright (C) 2021 Akretion +# @author Magno Costa +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + sale_create_invoice_policy = fields.Selection( + related="company_id.sale_create_invoice_policy", readonly=False + ) diff --git a/l10n_br_sale_stock/models/sale_order.py b/l10n_br_sale_stock/models/sale_order.py new file mode 100644 index 000000000000..f20e8de1bff6 --- /dev/null +++ b/l10n_br_sale_stock/models/sale_order.py @@ -0,0 +1,64 @@ +# Copyright (C) 2020 Magno Costa - Akretion +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import api, fields, models + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + # Make Invisible Invoice Button + button_create_invoice_invisible = fields.Boolean( + compute="_compute_get_button_create_invoice_invisible" + ) + + @api.depends("state", "order_line.invoice_status") + def _compute_get_button_create_invoice_invisible(self): + button_create_invoice_invisible = False + + lines = self.order_line.filtered( + lambda line: line.invoice_status == "to invoice" + ) + + # Somente depois do Pedido confirmado o botão pode aparecer + if self.state != "sale": + button_create_invoice_invisible = True + else: + if self.company_id.sale_create_invoice_policy == "stock_picking": + # A criação de Fatura de Serviços deve ser possível via Pedido + if not any(line.product_id.type == "service" for line in lines): + button_create_invoice_invisible = True + else: + # No caso da Politica de criação baseada no Pedido de Venda + # qdo acionado o Botão irá criar as Faturas automaticamente + # mesmo no caso de ter Produtos e Serviços + if not lines: + button_create_invoice_invisible = True + + self.button_create_invoice_invisible = button_create_invoice_invisible + + @api.onchange("partner_shipping_id") + def _onchange_partner_shipping_id(self): + """ + Caso ocorra a alteração do campo Endereço de Entrega/partner_shipping_id + depois do Pedido confirmado os stock.picking relacionados ficam com o + partner_id anterior, o que é errado, o metodo original apenas mostra uma + mensagem de alerta na tela orientando o usuário a corrigir manualmente, + mas como na localização com esse modulo pode se definir a criação da + Invoice a partir do stock.picking é melhor garantir a alteração do campo + partner_id da stock.picking afim de evitar erros na criação da Invoice. + :return: super() + """ + # TODO: Verificar essa questão na migração a partir da v14 + + pickings = self.picking_ids.filtered( + lambda p: p.state not in ["done", "cancel"] + and p.partner_id != self.partner_shipping_id + ) + # Atribuição da forma abaixo por algum motivo + # não funciona apenas o write + # for picking in pickings: + # picking.partner_id = self.partner_shipping_id + pickings.write({"partner_id": self.partner_shipping_id.id}) + + return super()._onchange_partner_shipping_id() diff --git a/l10n_br_sale_stock/models/sale_order_line.py b/l10n_br_sale_stock/models/sale_order_line.py new file mode 100644 index 000000000000..793e8cedc190 --- /dev/null +++ b/l10n_br_sale_stock/models/sale_order_line.py @@ -0,0 +1,58 @@ +# Copyright (C) 2013 Raphaël Valyi - Akretion +# Copyright (C) 2014 Renato Lima - Akretion +# Copyright (C) 2021 Magno Costa - Akretion +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import api, models + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + def _prepare_procurement_values(self, group_id=False): + values = {} + if self.order_id.fiscal_operation_id: + values = self._prepare_br_fiscal_dict() + values.update(super()._prepare_procurement_values(group_id)) + # Incluir o invoice_state + if self.order_id.company_id.sale_create_invoice_policy == "stock_picking": + values["invoice_state"] = "2binvoiced" + + return values + + # no trigger product_id.invoice_policy to avoid retroactively changing SO + @api.depends("qty_invoiced", "qty_delivered", "product_uom_qty", "order_id.state") + def _get_to_invoice_qty(self): + """ + Compute the quantity to invoice. If the invoice policy is order, + the quantity to invoice is calculated from the ordered quantity. + Otherwise, the quantity delivered is used. + """ + result = super()._get_to_invoice_qty() + + for line in self: + if line.order_id.state in ["sale", "done"]: + if line.product_id.invoice_policy == "order": + if ( + line.order_id.company_id.sale_create_invoice_policy + == "stock_picking" + and line.product_id.type == "product" + ): + # O correto seria que ao selecionar + # sale_create_invoice_policy 'stock_picking' os + # produtos tenham o campo invoice_policy definidos para + # 'delivery', porém para evitar que seja criada uma + # Fatura a partir do Pedido de Venda estamos + # alterando isso mesmo para os produtos definidos com + # 'order', já que a Politica de Criação da Fatura no + # caso do Tipo Produto está definida para ser a + # partir do stock.picking . + # TODO: Essa seria a melhor opção ? Por enquanto pelo + # que vi para ter o mesmo resultado, que é no caso + # sale_create_invoice_policy 'stock_picking' só ser + # possível criar a partir do sale.order Faturas das + # linhas que sejam type service sim, a outra opção + # seria sobre escrever o metodo action_invoice_create + # sem ser possível chamar o super. + line.qty_to_invoice = 0 + return result diff --git a/l10n_br_sale_stock/models/stock_move.py b/l10n_br_sale_stock/models/stock_move.py new file mode 100644 index 000000000000..f3b03160f8d1 --- /dev/null +++ b/l10n_br_sale_stock/models/stock_move.py @@ -0,0 +1,51 @@ +# Copyright (C) 2020 Gabriel Cardoso de Faria +# Copyright (C) 2021 Magno Costa - Akretion +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import models + + +class StockMove(models.Model): + _inherit = "stock.move" + + def _get_price_unit_invoice(self, inv_type, partner, qty=1): + result = super()._get_price_unit_invoice(inv_type, partner, qty) + # Caso tenha Sale Line já vem desagrupado aqui devido ao KEY + if len(self) == 1: + # Caso venha apenas uma linha porem sem + # sale_line_id é preciso ignora-la + if self.sale_line_id and self.sale_line_id.price_unit != result: + result = self.sale_line_id.price_unit + + return result + + def _get_new_picking_values(self): + # IMPORTANTE: a sequencia de update dos dicionarios quando o + # partner_shipping_id é diferente, o metodo do fiscal está + # sobre escrevendo o partner_id e acaba criando um picking + # sem o partner_id caso esse dict atualize o do super + values = {} + fiscal_operation = False + if self.sale_line_id: + if self.sale_line_id.order_id.fiscal_operation_id: + fiscal_operation = self.sale_line_id.order_id.fiscal_operation_id + values = self.sale_line_id.order_id._prepare_br_fiscal_dict() + + values.update(super()._get_new_picking_values()) + # self is a recordset, possibly with different fiscal operations + # so we use the fiscal_opration from the SO for the picking: + if fiscal_operation: + values.update({"fiscal_operation_id": fiscal_operation.id}) + + return values + + def _get_fiscal_partner(self): + """ + Use partner_invoice_id if different from partner + """ + self.ensure_one() + partner = super()._get_fiscal_partner() + if self.sale_line_id: + if partner != self.sale_line_id.order_id.partner_invoice_id: + partner = self.sale_line_id.order_id.partner_invoice_id + return partner diff --git a/l10n_br_sale_stock/models/stock_picking.py b/l10n_br_sale_stock/models/stock_picking.py new file mode 100644 index 000000000000..30641d141ee1 --- /dev/null +++ b/l10n_br_sale_stock/models/stock_picking.py @@ -0,0 +1,43 @@ +# Copyright (C) 2021 Magno Costa - Akretion +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import models + + +class StockPicking(models.Model): + _inherit = "stock.picking" + + def _get_partner_to_invoice(self): + """ + If the partner has some invoicing contact defined + partner_invoice_id is auto filled, but it can also be changed. + partner_invoice_id is used if different from partner_id + """ + self.ensure_one() + partner_id = super()._get_partner_to_invoice() + if self.sale_id: + if partner_id != self.sale_id.partner_invoice_id.id: + partner_id = self.sale_id.partner_invoice_id.id + return partner_id + + def _get_fiscal_partner(self): + self.ensure_one() + partner = super()._get_fiscal_partner() + partner_to_invoice = self._get_partner_to_invoice() + if partner.id != partner_to_invoice: + partner = self.env["res.partner"].browse(partner_to_invoice) + return partner + + def _get_default_fiscal_operation(self): + fiscal_operation = super()._get_default_fiscal_operation() + if self.sale_id: + if self.sale_id.fiscal_operation_id: + # Evita a inconsistência de ter o Pedido de Vendas com uma + # OP Fiscal e a Ordem de Seleção outra, quando o campo + # invoice_state é alterado, o usuário pode alterar o campo + # mas dessa forma forçamos a decisão de não usar a mesma + # do Pedido. + if fiscal_operation != self.sale_id.fiscal_operation_id: + fiscal_operation = self.sale_id.fiscal_operation_id + + return fiscal_operation diff --git a/l10n_br_sale_stock/readme/CONFIGURE.rst b/l10n_br_sale_stock/readme/CONFIGURE.rst new file mode 100644 index 000000000000..e7dc235973ab --- /dev/null +++ b/l10n_br_sale_stock/readme/CONFIGURE.rst @@ -0,0 +1 @@ +No configuration required. diff --git a/l10n_br_sale_stock/readme/CONTRIBUTORS.rst b/l10n_br_sale_stock/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000000..991d50b77a8c --- /dev/null +++ b/l10n_br_sale_stock/readme/CONTRIBUTORS.rst @@ -0,0 +1,4 @@ +* Renato Lima +* Raphaël Valyi +* Magno Costa +* Gabriel Cardoso de Faria diff --git a/l10n_br_sale_stock/readme/CREDITS.rst b/l10n_br_sale_stock/readme/CREDITS.rst new file mode 100644 index 000000000000..d91cdc201998 --- /dev/null +++ b/l10n_br_sale_stock/readme/CREDITS.rst @@ -0,0 +1,3 @@ +The development of this module has been financially supported by: + +* Aketion LTDA diff --git a/l10n_br_sale_stock/readme/DESCRIPTION.rst b/l10n_br_sale_stock/readme/DESCRIPTION.rst new file mode 100644 index 000000000000..895e76e3104a --- /dev/null +++ b/l10n_br_sale_stock/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +Este módulo estende o módulo padrão de vendas e estoque do Odoo para atender às necessidades específicas do Brasil, especialmente no contexto de vendas de produtos com NF-e (Nota Fiscal Eletrônica). + +Ele automatiza a propagação da operação fiscal, comentários fiscais e incoterms para as faturas geradas diretamente a partir de ordens de venda ou movimentações de estoque, garantindo que essas informações sejam corretamente transferidas e utilizadas no processo de faturamento. diff --git a/l10n_br_sale_stock/readme/HISTORY.rst b/l10n_br_sale_stock/readme/HISTORY.rst new file mode 100644 index 000000000000..b7a4ae3ab149 --- /dev/null +++ b/l10n_br_sale_stock/readme/HISTORY.rst @@ -0,0 +1,14 @@ +15.0.1.0.0 (2024-09-17) +~~~~~~~~~~~~~~~~~~~~~~~ + +* [MIG] Migration to version 15.0 + +12.0.1.0.0 (2020-05-29) +~~~~~~~~~~~~~~~~~~~~~~~ + +* [MIG] Migration to version 12.0 + +10.0.1.0.0 (2019-09-13) +~~~~~~~~~~~~~~~~~~~~~~~ + +* [MIG] Migration to version 10.0 diff --git a/l10n_br_sale_stock/readme/INSTALL.rst b/l10n_br_sale_stock/readme/INSTALL.rst new file mode 100644 index 000000000000..0ce3db863a91 --- /dev/null +++ b/l10n_br_sale_stock/readme/INSTALL.rst @@ -0,0 +1,5 @@ +This module depends on: + +* sale_stock +* l10n_br_sale +* l10n_br_stock_account diff --git a/l10n_br_sale_stock/readme/ROADMAP.rst b/l10n_br_sale_stock/readme/ROADMAP.rst new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/l10n_br_sale_stock/readme/USAGE.rst b/l10n_br_sale_stock/readme/USAGE.rst new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/l10n_br_sale_stock/static/description/icon.png b/l10n_br_sale_stock/static/description/icon.png new file mode 100644 index 000000000000..3a0328b516c4 Binary files /dev/null and b/l10n_br_sale_stock/static/description/icon.png differ diff --git a/l10n_br_sale_stock/static/description/index.html b/l10n_br_sale_stock/static/description/index.html new file mode 100644 index 000000000000..134c6522913e --- /dev/null +++ b/l10n_br_sale_stock/static/description/index.html @@ -0,0 +1,476 @@ + + + + + +Brazilian Localization Sales and Warehouse + + + +
+

Brazilian Localization Sales and Warehouse

+ + +

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

+

Este módulo estende o módulo padrão de vendas e estoque do Odoo para atender às necessidades específicas do Brasil, especialmente no contexto de vendas de produtos com NF-e (Nota Fiscal Eletrônica).

+

Ele automatiza a propagação da operação fiscal, comentários fiscais e incoterms para as faturas geradas diretamente a partir de ordens de venda ou movimentações de estoque, garantindo que essas informações sejam corretamente transferidas e utilizadas no processo de faturamento.

+

Table of contents

+ +
+

Installation

+

This module depends on:

+
    +
  • sale_stock
  • +
  • l10n_br_sale
  • +
  • l10n_br_stock_account
  • +
+
+
+

Configuration

+

No configuration required.

+
+
+

Changelog

+
+

15.0.1.0.0 (2024-09-17)

+
    +
  • [MIG] Migration to version 15.0
  • +
+
+
+

12.0.1.0.0 (2020-05-29)

+
    +
  • [MIG] Migration to version 12.0
  • +
+
+
+

10.0.1.0.0 (2019-09-13)

+
    +
  • [MIG] Migration to version 10.0
  • +
+
+
+
+

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

+ +
+
+

Other credits

+

The development of this module has been financially supported by:

+
    +
  • Aketion LTDA
  • +
+
+
+

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 mbcosta

+

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_sale_stock/tests/__init__.py b/l10n_br_sale_stock/tests/__init__.py new file mode 100644 index 000000000000..a64b0d449d2f --- /dev/null +++ b/l10n_br_sale_stock/tests/__init__.py @@ -0,0 +1 @@ +from . import test_sale_stock diff --git a/l10n_br_sale_stock/tests/test_sale_stock.py b/l10n_br_sale_stock/tests/test_sale_stock.py new file mode 100644 index 000000000000..e413f7d84785 --- /dev/null +++ b/l10n_br_sale_stock/tests/test_sale_stock.py @@ -0,0 +1,514 @@ +# Copyright 2020 KMEE +# Copyright (C) 2021 Magno Costa - Akretion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +# TODO: In v16 check the possiblity to use the commom.py +# from stock_picking_invoicing +# https://github.com/OCA/account-invoicing/blob/16.0/ +# stock_picking_invoicing/tests/common.py +from odoo.tests import Form, tagged + +from odoo.addons.l10n_br_stock_account.tests.common import TestBrPickingInvoicingCommon + + +@tagged("post_install", "-at_install") +class TestSaleStock(TestBrPickingInvoicingCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + + def test_02_sale_stock_return(self): + """ + Test a SO with a product invoiced on delivery. Deliver and invoice + the SO, then do a return + of the picking. Check that a refund invoice is well generated. + """ + # intial so + self.partner = self.env.ref("l10n_br_base.res_partner_address_ak2") + self.product = self.env.ref("product.product_delivery_01") + so_vals = { + "partner_id": self.partner.id, + "partner_invoice_id": self.partner.id, + "partner_shipping_id": self.partner.id, + "order_line": [ + ( + 0, + 0, + { + "name": self.product.name, + "product_id": self.product.id, + "product_uom_qty": 3.0, + "product_uom": self.product.uom_id.id, + "price_unit": self.product.list_price, + }, + ) + ], + "pricelist_id": self.env.ref("product.list0").id, + } + self.so = self.env["sale.order"].create(so_vals) + + for line in self.so.order_line: + line._onchange_product_id_fiscal() + + # confirm our standard so, check the picking + self.so.action_confirm() + self.assertTrue( + self.so.picking_ids, + 'Sale Stock: no picking created for "invoice on ' + 'delivery" storable products', + ) + + # set stock.picking to be invoiced + self.assertTrue( + len(self.so.picking_ids) == 1, + "More than one stock " "picking for sale.order", + ) + self.so.picking_ids.set_to_be_invoiced() + + # validate stock.picking + stock_picking = self.so.picking_ids + + # compare sale.order.line with stock.move + stock_move = stock_picking.move_lines + sale_order_line = self.so.order_line + + sm_fields = [key for key in self.env["stock.move"]._fields.keys()] + sol_fields = [key for key in self.env["sale.order.line"]._fields.keys()] + + skipped_fields = [ + "id", + "display_name", + "state", + ] + common_fields = list(set(sm_fields) & set(sol_fields) - set(skipped_fields)) + + for field in common_fields: + self.assertEqual( + stock_move[field], + sale_order_line[field], + "Field %s failed to transfer from " + "sale.order.line to stock.move" % field, + ) + + self.env["stock.immediate.transfer"].create( + {"pick_ids": [(4, stock_picking.id)]} + ).process() + + # O valor do price_unit da stock.move é alterado ao Confirmar o + # stock.picking de acordo com a forma de valorização de estoque + # definida( ex.: metodo _run_fifo é chamado e altera o valor do + # price_unit https://github.com/odoo/odoo/blob/12.0/addons + # /stock_account/models/stock.py#L255 ), por isso os campos + # relacionados a esse valor não são iguais. + # O teste está sendo feito novamente para essa questão ficar clara + # em alterações e migrações. + skipped_fields_after_confirm = [ + "price_gross", + "amount_taxed", + "financial_total", + "financial_total_gross", + "fiscal_price", + "amount_fiscal", + "price_unit", + "amount_untaxed", + "amount_total", + ] + skipped_fields[len(skipped_fields) :] = skipped_fields_after_confirm + + common_fields = list(set(sm_fields) & set(sol_fields) - set(skipped_fields)) + + for field in common_fields: + self.assertEqual( + stock_move[field], + sale_order_line[field], + "Field %s failed to transfer from " + "sale.order.line to stock.move" % field, + ) + + def test_picking_sale_order_product_and_service(self): + """ + Test Sale Order with product and service + """ + + sale_order_2 = self.env.ref("l10n_br_sale_stock.main_so_l10n_br_sale_stock_2") + sale_order_2.action_confirm() + + # Forma encontrada para chamar o metodo + # _compute_get_button_create_invoice_invisible + sale_order_form = Form(sale_order_2) + sale_order = sale_order_form.save() + # Metodo de criação da fatura a partir do sale.order + # deve gerar apenas a linha de serviço + sale_order._create_invoices(final=True) + # Deve existir apenas a Fatura/Documento Fiscal de Serviço + self.assertEqual(1, sale_order.invoice_count) + for invoice in sale_order.invoice_ids: + for line in invoice.invoice_line_ids: + self.assertEqual(line.product_id.type, "service") + # Confirmando a Fatura de Serviço + invoice.action_post() + self.assertEqual( + invoice.state, "posted", "Invoice should be in state Posted." + ) + + picking = sale_order.picking_ids + # Check product availability + picking.action_assign() + # Apenas o Produto criado + self.assertEqual(len(picking.move_ids_without_package), 1) + self.assertEqual(picking.invoice_state, "2binvoiced") + # Force product availability + for move in picking.move_ids_without_package: + move.quantity_done = move.product_uom_qty + # Usado para validar a transferencia dos campos da linha + # do Pedido de Venda para a linha da Fatura/Invoice + sale_order_line = move.sale_line_id + self.assertEqual(sale_order_line.product_uom, move.product_uom) + + self.picking_move_state(picking) + self.assertEqual(picking.state, "done") + invoice = self.create_invoice_wizard(picking) + self.assertEqual(picking.invoice_state, "invoiced") + self.assertIn(invoice, picking.invoice_ids) + self.assertIn(picking, invoice.picking_ids) + # Picking criado com o Partner Shipping da Sale Order + self.assertEqual(picking.partner_id, sale_order_2.partner_shipping_id) + # Fatura criada com o Partner Invoice da Sale Order + self.assertEqual(invoice.partner_id, sale_order_2.partner_invoice_id) + # Fatura criada com o Partner Shipping usado no Picking + self.assertEqual(invoice.partner_shipping_id, picking.partner_id) + # Quando informado usar o Termo de Pagto definido no Pedido de Venda + # e não o padrão do cliente + self.assertEqual(invoice.invoice_payment_term_id, sale_order_2.payment_term_id) + + # Apenas a Fatura com a linha do produto foi criada + self.assertEqual(len(invoice.invoice_line_ids), 1) + + # No Pedido de Venda devem existir duas Faturas/Documentos Fiscais + # de Serviço e Produto + self.assertEqual(2, sale_order_2.invoice_count) + + # Confirmando a Fatura + invoice.action_post() + self.assertEqual(invoice.state, "posted", "Invoice should be in state Posted.") + + # Validar Atualização da Quantidade Faturada + for line in sale_order_2.order_line: + # Apenas a linha de Produto tem a qtd faturada dobrada + if line.product_id.type == "product": + # A quantidade Faturada deve ser igual + # a Quantidade do Produto + self.assertEqual(line.product_uom_qty, line.qty_invoiced) + + # Checar se os campos das linhas do Pedido de Vendas + # estão iguais as linhas da Fatura/Invoice. + sol_fields = [key for key in self.env["sale.order.line"]._fields.keys()] + + acl_fields = [key for key in self.env["account.move.line"]._fields.keys()] + + skipped_fields = [ + "agent_ids", + "id", + "display_name", + "state", + "create_date", + # O campo da Unidade de Medida possui um nome diferente na + # account.move.line product_uom_id, por isso é removido porém + # a copia entre os objetos é testada tanto no stock.move acima + # quanto na account.move.line abaixo + "uom_id", + # Ao chamar o _onchange_product_id_fiscal no stock.move o + # partner_id usado no mapeamento é o do objeto, nesse teste + # 'Akretion Aluminio - SP' por ser o Endereço de Entrega + # partner_shipping_id, porém esse não é o partner_invoice_id + # 'Akretion Sao Paulo' essa diferença ocasiona diferentes + # 'Linhas de Operações Fiscal'/fiscal_operation_line_id entre: + # Objeto | Linha de Operações Fiscal + # _______________________________|____________________________ + # sale.order.line | 'Revenda não Contribuinte' + # stock.move e account.move.line | 'Revenda' + # TODO: O mapeamento da 'Linha de Operações Fiscal' precisa + # considerar os casos onde o partner_id do objeto não é o + # partner_invoice_id. Por enquanto o campo não está sendo validado + # para evitar erros aqui já que isso precisa ser resolvido em outro + # modulo ou talvez aqui porém seria apenas uma correção temporaria. + "fiscal_operation_line_id", + ] + + common_fields = list(set(acl_fields) & set(sol_fields) - set(skipped_fields)) + invoice_lines = picking.invoice_ids.invoice_line_ids + + for field in common_fields: + self.assertEqual( + sale_order_line[field], + invoice_lines[field], + "Field %s failed to transfer from " + "sale.order.line to account.move.line" % field, + ) + + for inv_line in invoice_lines: + if inv_line.product_id == sale_order_line.product_id: + self.assertEqual(sale_order_line.product_uom, inv_line.product_uom_id) + + # Teste de Retorno + picking_devolution = self.return_picking_wizard(picking) + + self.assertEqual(picking_devolution.invoice_state, "2binvoiced") + self.assertTrue( + picking_devolution.fiscal_operation_id, "Missing Fiscal Operation." + ) + for line in picking_devolution.move_lines: + self.assertEqual(line.invoice_state, "2binvoiced") + # Valida presença dos campos principais para o mapeamento Fiscal + self.assertTrue(line.fiscal_operation_id, "Missing Fiscal Operation.") + self.assertTrue( + line.fiscal_operation_line_id, "Missing Fiscal Operation Line." + ) + + self.picking_move_state(picking_devolution) + self.assertEqual(picking_devolution.state, "done", "Change state fail.") + invoice_devolution = self.create_invoice_wizard(picking_devolution) + + # Confirmando a Fatura + invoice_devolution.action_post() + self.assertEqual( + invoice_devolution.state, "posted", "Invoice should be in state Posted." + ) + # Validar Atualização da Quantidade Faturada + for line in sale_order_2.order_line: + # Apenas a linha de Produto tem a qtd faturada dobrada + if line.product_id.type == "product": + # A quantidade Faturada deve ser zero + # devido a Devolução + self.assertEqual(0.0, line.qty_invoiced) + + def test_picking_invoicing_partner_shipping_invoiced(self): + """ + Test the invoice generation grouped by partner/product with 2 + picking and 3 moves per picking, but Partner to Shipping is + different from Partner to Invoice. + """ + sale_order_1 = self.env.ref("l10n_br_sale_stock.main_so_l10n_br_sale_stock_1") + sale_order_1.action_confirm() + picking = sale_order_1.picking_ids + self.picking_move_state(picking) + + sale_order_2 = self.env.ref("l10n_br_sale_stock.main_so_l10n_br_sale_stock_2") + sale_order_2.action_confirm() + picking2 = sale_order_2.picking_ids + + self.picking_move_state(picking2) + self.assertEqual(picking.state, "done") + self.assertEqual(picking2.state, "done") + pickings = picking | picking2 + invoice = self.create_invoice_wizard(pickings) + + # Fatura Agrupada + self.assertEqual(len(invoice), 1) + self.assertEqual(picking.invoice_state, "invoiced") + self.assertEqual(picking2.invoice_state, "invoiced") + # Fatura deverá ser criada com o partner_invoice_id + self.assertEqual(invoice.partner_id, sale_order_1.partner_invoice_id) + # Fatura com o partner shipping + self.assertEqual(invoice.partner_shipping_id, picking.partner_id) + self.assertIn(invoice, picking.invoice_ids) + self.assertIn(picking, invoice.picking_ids) + self.assertIn(invoice, picking2.invoice_ids) + self.assertIn(picking2, invoice.picking_ids) + + # Not grouping products with different sale line, + # 3 products from sale_order_1 and 1 product from sale_order_2 + self.assertEqual(len(invoice.invoice_line_ids), 4) + for inv_line in invoice.invoice_line_ids: + # TODO: No travis falha o browse aqui + # l10n_br_stock_account/models/stock_invoice_onshipping.py:105 + # isso não acontece no caso da empresa de Lucro Presumido + # ou quando é feito o teste apenas instalando os modulos + # l10n_br_account e em seguida o l10n_br_stock_account + # self.assertTrue( + # inv_line.tax_ids, "Error to map Sale Tax in invoice.line." + # ) + # Valida presença dos campos principais para o mapeamento Fiscal + self.assertTrue(inv_line.fiscal_operation_id, "Missing Fiscal Operation.") + self.assertTrue( + inv_line.fiscal_operation_line_id, "Missing Fiscal Operation Line." + ) + + def test_ungrouping_pickings_partner_shipping_different(self): + """ + Test the invoice generation grouped by partner/product with 3 + picking and 3 moves per picking, the 3 has the same Partner to + Invoice but one has Partner to Shipping so shouldn't be grouping. + """ + + sale_order_1 = self.env.ref("l10n_br_sale_stock.main_so_l10n_br_sale_stock_1") + sale_order_1.action_confirm() + picking = sale_order_1.picking_ids + self.picking_move_state(picking) + + sale_order_3 = self.env.ref("l10n_br_sale_stock.main_so_l10n_br_sale_stock_3") + sale_order_3.action_confirm() + picking3 = sale_order_3.picking_ids + self.picking_move_state(picking3) + self.assertEqual(picking.state, "done") + self.assertEqual(picking3.state, "done") + + sale_order_4 = self.env.ref("l10n_br_sale_stock.main_so_l10n_br_sale_stock_4") + sale_order_4.action_confirm() + picking4 = sale_order_4.picking_ids + self.picking_move_state(picking4) + self.assertEqual(picking.state, "done") + self.assertEqual(picking3.state, "done") + + pickings = picking | picking3 | picking4 + invoices = self.create_invoice_wizard(pickings) + + # Mesmo tendo o mesmo Partner Invoice se não tiver o + # mesmo Partner Shipping não deve ser Agrupado + self.assertEqual(len(invoices), 2) + self.assertEqual(picking.invoice_state, "invoiced") + self.assertEqual(picking3.invoice_state, "invoiced") + self.assertEqual(picking4.invoice_state, "invoiced") + + # Fatura que tem um Partner shipping + # diferente não foi agrupada + invoice_pick_1 = invoices.filtered( + lambda t: t.partner_shipping_id == picking.partner_id + ) + # Fatura deverá ser criada com o partner_invoice_id + self.assertEqual(invoice_pick_1.partner_id, sale_order_1.partner_invoice_id) + # Fatura criada com o Partner Shipping usado no Picking + self.assertEqual(invoice_pick_1.partner_shipping_id, picking.partner_id) + + # TODO: O processo de criação a partir de um Pedido de Venda vem + # preenchido o campo partner_shipping_id, isso deve ser mantido por + # ser considerado o padrão ou é melhor remover o partner_shipping_id + # quando o valor é igual ao partner_id? + + # Fatura Agrupada, não deve ter o partner_shipping_id preenchido + # invoice_pick_3_4 = invoices.filtered(lambda t: not t.partner_shipping_id) + + invoice_pick_3_4 = invoices.filtered( + lambda t: t.partner_shipping_id == t.partner_id + ) + self.assertIn(invoice_pick_3_4, picking3.invoice_ids) + self.assertIn(invoice_pick_3_4, picking4.invoice_ids) + + def test_synchronize_sale_partner_shipping_in_stock_picking(self): + """ + Test the synchronize Sale Partner Shipping in Stock Picking + """ + sale_order_1 = self.env.ref("l10n_br_sale_stock.main_so_l10n_br_sale_stock_1") + sale_order_1.action_confirm() + picking = sale_order_1.picking_ids + sale_order_1.partner_shipping_id = self.env.ref( + "l10n_br_base.res_partner_address_ak2" + ).id + sale_order_1._onchange_partner_shipping_id() + self.assertEqual(sale_order_1.partner_shipping_id, picking.partner_id) + + def test_lucro_presumido_company(self): + """ + Test Lucro Presumido Company + """ + self._change_user_company(self.env.ref("l10n_br_base.empresa_lucro_presumido")) + sale_order_1 = self.env.ref( + "l10n_br_sale_stock.l10n_br_sale_stock_lucro_presumido" + ) + sale_order_form = Form(sale_order_1) + sale_order = sale_order_form.save() + sale_order.incoterm = self.env.ref("account.incoterm_FOB") + + sale_order.action_confirm() + picking = sale_order_1.picking_ids + self.picking_move_state(picking) + invoice = self.create_invoice_wizard(picking) + self.assertEqual(len(invoice), 1) + for inv_line in invoice.invoice_line_ids: + # TODO: No Travis quando a empresa main_company falha esse browse aqui + # l10n_br_stock_account/models/stock_invoice_onshipping.py:105 + # isso não acontece no caso da empresa de Lucro Presumido ou quando é + # feito o teste apenas instalando os modulos l10n_br_account e em + # seguida o l10n_br_stock_account. + self.assertTrue(inv_line.tax_ids, "Error to map Sale Tax in invoice.line.") + + def test_button_create_bill_in_view(self): + """ + Test Field to make Button Create Bill invisible. + """ + sale_order_form = Form(self.env.ref("l10n_br_sale.main_so_only_products")) + sale_products = sale_order_form.save() + # Caso do Pedido de Vendas em Rascunho + self.assertTrue( + sale_products.button_create_invoice_invisible, + "Field to make invisible the Button Create Bill should be" + " invisible when Sale Order is not in state Sale or Done.", + ) + sale_products.action_confirm() + self.assertTrue( + sale_products.button_create_invoice_invisible, + "Field to make invisible the button Create Bill should be" + " invisible when Sale Order has only products.", + ) + + # Caso somente Serviços + sale_order_form = Form(self.env.ref("l10n_br_sale.main_so_only_services")) + sale_only_service = sale_order_form.save() + sale_only_service.action_confirm() + self.assertFalse( + sale_only_service.button_create_invoice_invisible, + "Field to make invisible the Button Create Bill should be" + " False when the Sale Order has only Services.", + ) + + # Caso Produto e Serviço + sale_order_form = Form(self.env.ref("l10n_br_sale.main_so_product_service")) + sale_service_product = sale_order_form.save() + sale_service_product.action_confirm() + self.assertFalse( + sale_only_service.button_create_invoice_invisible, + "Field to make invisible the Button Create Bill should be" + " False when the Sale Order has Service and Product.", + ) + + def test_compatible_with_international_case(self): + """ + Test compatibility with international cases or + without Fiscal Operation. + """ + so_international = self.env.ref("sale.sale_order_3") + so_international.fiscal_operation_id = False + so_international.action_confirm() + picking = so_international.picking_ids + self.picking_move_state(picking) + invoice = self.create_invoice_wizard(picking) + invoice.action_post() + # Caso Internacional não deve ter Documento Fiscal associado + self.assertFalse( + invoice.fiscal_document_id, + "International case should not has Fiscal Document.", + ) + # Teste Retorno + picking_devolution = self.return_picking_wizard(picking) + invoice_devolution = self.create_invoice_wizard(picking_devolution) + self.assertFalse( + invoice_devolution.fiscal_document_id, + "International case should not has Fiscal Document.", + ) + + def test_form_stock_picking(self): + """Test Stock Picking with Form""" + + sale_order = self.env.ref("l10n_br_sale_stock.main_so_l10n_br_sale_stock_1") + sale_order.action_confirm() + picking = sale_order.picking_ids + self.picking_move_state(picking) + picking_form = Form(picking) + + # Apesar do metodo onchange retornar uma OP Fiscal padrão, + # quando existe um Pedido de Venda associado deve usar retornar + # a mesma OP Fiscal do Pedido. + picking_form.invoice_state = "none" + picking_form.invoice_state = "2binvoiced" + self.assertEqual(sale_order.fiscal_operation_id, picking.fiscal_operation_id) + picking_form.save() diff --git a/l10n_br_sale_stock/views/res_company_view.xml b/l10n_br_sale_stock/views/res_company_view.xml new file mode 100644 index 000000000000..745c0dc121bf --- /dev/null +++ b/l10n_br_sale_stock/views/res_company_view.xml @@ -0,0 +1,15 @@ + + + + + l10n_br_fiscal.res.company.form + res.company + + + + + + + + + diff --git a/l10n_br_sale_stock/views/res_config_settings_view.xml b/l10n_br_sale_stock/views/res_config_settings_view.xml new file mode 100644 index 000000000000..c6f4be48273d --- /dev/null +++ b/l10n_br_sale_stock/views/res_config_settings_view.xml @@ -0,0 +1,38 @@ + + + + + l10n_br_sale_stock.res.config.settings.form + res.config.settings + + + +
+
+
+
+
+
+
+ +
diff --git a/l10n_br_sale_stock/views/sale_order_view.xml b/l10n_br_sale_stock/views/sale_order_view.xml new file mode 100644 index 000000000000..33829f3bac45 --- /dev/null +++ b/l10n_br_sale_stock/views/sale_order_view.xml @@ -0,0 +1,38 @@ + + + + + l10n_br_sale_stock.order.form + sale.order + + 99 + + + + + + {'invisible': [('button_create_invoice_invisible', '=', True)]} + + + + {'invisible': ['|', ('button_create_invoice_invisible', '=', True), ('state', '=', 'sale')]} + + + + + diff --git a/l10n_br_sale_stock/wizards/__init__.py b/l10n_br_sale_stock/wizards/__init__.py new file mode 100644 index 000000000000..87b9317d657c --- /dev/null +++ b/l10n_br_sale_stock/wizards/__init__.py @@ -0,0 +1 @@ +from . import stock_invoice_onshipping diff --git a/l10n_br_sale_stock/wizards/stock_invoice_onshipping.py b/l10n_br_sale_stock/wizards/stock_invoice_onshipping.py new file mode 100644 index 000000000000..fd28edd2e5db --- /dev/null +++ b/l10n_br_sale_stock/wizards/stock_invoice_onshipping.py @@ -0,0 +1,102 @@ +# Copyright 2020 KMEE +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class StockInvoiceOnshipping(models.TransientModel): + _inherit = "stock.invoice.onshipping" + + def _build_invoice_values_from_pickings(self, pickings): + """ + Build dict to create a new invoice from given pickings + :param pickings: stock.picking recordset + :return: dict + """ + invoice, values = super()._build_invoice_values_from_pickings(pickings) + + pick = fields.first(pickings) + if pick.sale_id: + values.update( + { + "partner_id": pick.sale_id.partner_invoice_id.id, + } + ) + + if pick.sale_id.payment_term_id.id != values.get("invoice_payment_term_id"): + values.update( + {"invoice_payment_term_id": pick.sale_id.payment_term_id.id} + ) + + # O campo payment_mode_id é implementado com a instalação do + # l10n_br_account_nfe mas o l10n_br_sale_stock não tem + # dependencia direta desse modulo, para evitar a necessidade + # de um 'glue' modulo para resolver isso é feita a verificação + # se o campo existe antes de preenche-lo + if hasattr(pick.sale_id, "payment_mode_id"): + if pick.sale_id.payment_mode_id.id != values.get("payment_mode_id"): + values.update({"payment_mode_id": pick.sale_id.payment_mode_id.id}) + if pick.sale_id.incoterm.id != values.get("invoice_incoterm_id"): + values.update({"invoice_incoterm_id": pick.sale_id.incoterm.id}) + + if pick.sale_id.copy_note and pick.sale_id.note: + # Evita enviar False quando não tem nada + additional_data = "" + if pick.sale_id.manual_customer_additional_data: + additional_data = "{}".format( + pick.sale_id.manual_customer_additional_data + ) + + values.update( + { + "manual_customer_additional_data": additional_data + + " TERMOS E CONDIÇÕES: {}".format(pick.sale_id.note), + } + ) + + return invoice, values + + def _get_move_key(self, move): + """ + Get the key based on the given move + :param move: stock.move recordset + :return: key + """ + key = super()._get_move_key(move) + if move.sale_line_id: + # Apesar da linha da Fatura permitir ter mais de uma linha de + # pedido de venda associada(campo sale_line_ids na invoice line) + # existe um erro a ser resolvido + # Issue https://github.com/odoo/odoo/issues/77028 + # PR https://github.com/odoo/odoo/pull/77195 + # Além disso é preciso verificar outras questões + # por exemplo datas de entrega diferentes, informações + # comerciais que são discriminadas por itens e etc. + key = key + (move.sale_line_id,) + + return key + + def _get_invoice_line_values(self, moves, invoice_values, invoice): + """ + Create invoice line values from given moves + :param moves: stock.move + :param invoice: account.invoice + :return: dict + """ + + values = super()._get_invoice_line_values(moves, invoice_values, invoice) + # Devido ao KEY com sale_line_id aqui + # vem somente um registro + if len(moves) == 1: + # Caso venha apenas uma linha porem sem + # sale_line_id é preciso ignora-la + if moves.sale_line_id: + values["sale_line_ids"] = [(6, 0, moves.sale_line_id.ids)] + values[ + "analytic_account_id" + ] = moves.sale_line_id.order_id.analytic_account_id.id + values["analytic_tag_ids"] = [ + (6, 0, moves.sale_line_id.analytic_tag_ids.ids) + ] + + return values diff --git a/setup/l10n_br_sale_stock/odoo/addons/l10n_br_sale_stock b/setup/l10n_br_sale_stock/odoo/addons/l10n_br_sale_stock new file mode 120000 index 000000000000..aab9190b696c --- /dev/null +++ b/setup/l10n_br_sale_stock/odoo/addons/l10n_br_sale_stock @@ -0,0 +1 @@ +../../../../l10n_br_sale_stock \ No newline at end of file diff --git a/setup/l10n_br_sale_stock/setup.py b/setup/l10n_br_sale_stock/setup.py new file mode 100644 index 000000000000..28c57bb64031 --- /dev/null +++ b/setup/l10n_br_sale_stock/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)