diff --git a/l10n_pt_account_moloni/README.rst b/l10n_pt_account_moloni/README.rst new file mode 100644 index 00000000..fbe2ca57 --- /dev/null +++ b/l10n_pt_account_moloni/README.rst @@ -0,0 +1,79 @@ +================ +Moloni Invoicing +================ + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:fa9e714a8ad78c478d7702d2dba13e062374e1fef9369858ac86646485b1f74c + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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--portugal-lightgray.png?logo=github + :target: https://github.com/OCA/l10n-portugal/tree/14.0/l10n_pt_account_moloni + :alt: OCA/l10n-portugal +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/l10n-portugal-14-0/l10n-portugal-14-0-l10n_pt_account_moloni + :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-portugal&target_branch=14.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +O melhor software certificado de gestão e facturação online! +Gerir a sua empresa nunca foi tão simples, barato e eficiente. +Emita faturas em segundos. + +**Table of contents** + +.. contents:: + :local: + +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 +~~~~~~~ + +* ERp Gap + +Contributors +~~~~~~~~~~~~ + +* ERP Gap +* Daniel Reis , `Open Source Integrators `_ + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/l10n-portugal `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/l10n_pt_account_moloni/__init__.py b/l10n_pt_account_moloni/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/l10n_pt_account_moloni/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/l10n_pt_account_moloni/__manifest__.py b/l10n_pt_account_moloni/__manifest__.py new file mode 100644 index 00000000..ce5947c8 --- /dev/null +++ b/l10n_pt_account_moloni/__manifest__.py @@ -0,0 +1,20 @@ +{ + "name": "Moloni Invoicing", + "version": "14.0.1.0.0", + "summary": "Moloni software de facturação online", + "author": "ERp Gap, Odoo Community Association (OCA)", + "license": "AGPL-3", + "website": "https://github.com/OCA/l10n-portugal", + "category": "Invoicing", + "depends": [ + "account", + ], + "data": [ + "data/ir_config_parameter_data.xml", + "views/account_views.xml", + "views/res_config_settings_views.xml", + ], + "installable": True, + "application": True, + "auto_install": False, +} diff --git a/l10n_pt_account_moloni/data/ir_config_parameter_data.xml b/l10n_pt_account_moloni/data/ir_config_parameter_data.xml new file mode 100644 index 00000000..5200ed5b --- /dev/null +++ b/l10n_pt_account_moloni/data/ir_config_parameter_data.xml @@ -0,0 +1,26 @@ + + + + + + moloni_invoice_status + 1 + + + + moloni_product_reference + Odoo Product + + + + moloni_product_category_name + Odoo Product Category + + + + moloni_product_unit_name + Odoo Product Unit + + + + diff --git a/l10n_pt_account_moloni/i18n/l10n_pt_account_moloni.pot b/l10n_pt_account_moloni/i18n/l10n_pt_account_moloni.pot new file mode 100644 index 00000000..88b9fbb2 --- /dev/null +++ b/l10n_pt_account_moloni/i18n/l10n_pt_account_moloni.pot @@ -0,0 +1,532 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_moloni +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-01-20 22:12+0000\n" +"PO-Revision-Date: 2020-01-20 22:12+0000\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: account_moloni +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Authentication Credentials" +msgstr "" + +#. module: account_moloni +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Invoice Configurations" +msgstr "" + +#. module: account_moloni +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Product Configurations" +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Authentication failed:\n" +"\n" +"%s." +msgstr "" + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_res_config_settings__moloni_client_secret_code +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Client Secret Code" +msgstr "" + +#. module: account_moloni +#: model:ir.model.fields.selection,name:account_moloni.selection__res_config_settings__moloni_invoice_status__1 +msgid "Closed" +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/res_config_settings.py:0 +#, python-format +msgid "" +"Companies:\n" +"\n" +"%s." +msgstr "" + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_res_config_settings__moloni_company_id +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Company ID" +msgstr "" + +#. module: account_moloni +#: model:ir.model,name:account_moloni.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Create customer failed with status code %d." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Create customer failed:\n" +"\n" +"%s." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Create invoice failed with status code %d." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Create invoice failed:\n" +"\n" +"%s." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Create product category failed with status code %d." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Create product category failed:\n" +"\n" +"%s." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Create product failed with status code %d." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Create product failed:\n" +"\n" +"%s." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Create product unit failed with status code %d." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Create product unit failed:\n" +"\n" +"%s." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Create tax failed with status code %d." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Create tax failed:\n" +"\n" +"%s." +msgstr "" + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_res_config_settings__moloni_developer_id +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Developer ID" +msgstr "" + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_abstract_moloni__display_name +msgid "Display Name" +msgstr "" + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_res_config_settings__moloni_document_set_id +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Document Set ID" +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/res_config_settings.py:0 +#, python-format +msgid "" +"Document Sets:\n" +"\n" +"%s." +msgstr "" + +#. module: account_moloni +#: model:ir.model.fields.selection,name:account_moloni.selection__res_config_settings__moloni_invoice_status__0 +msgid "Draft" +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Get all companies failed with status code %d." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Get all companies failed:\n" +"\n" +"%s." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Get all document sets failed with status code %d." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Get all document sets failed:\n" +"\n" +"%s." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Get all product categories failed with status code %d." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Get all product categories failed:\n" +"\n" +"%s." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Get all product units failed with status code %d." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Get all product units failed:\n" +"\n" +"%s." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Get all taxes failed with status code %d." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Get all taxes failed:\n" +"\n" +"%s." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Get customer failed with status code %d." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Get customer failed:\n" +"\n" +"%s." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Get next customer number failed with status code %d." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Get next customer number failed:\n" +"\n" +"%s." +msgstr "" + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_abstract_moloni__id +msgid "ID" +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/res_config_settings.py:0 +#: code:addons/account_moloni/models/res_config_settings.py:0 +#: code:addons/account_moloni/models/res_config_settings.py:0 +#, python-format +msgid "ID: %s\n" +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Incorrect Moloni authentication credentials." +msgstr "" + +#. module: account_moloni +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Invoice Status" +msgstr "" + +#. module: account_moloni +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "It's recommended that you change your password frequently." +msgstr "" + +#. module: account_moloni +#: model:ir.model,name:account_moloni.model_account_move +msgid "Journal Entries" +msgstr "" + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_abstract_moloni____last_update +msgid "Last Modified on" +msgstr "" + +#. module: account_moloni +#: model:ir.model,name:account_moloni.model_abstract_moloni +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Moloni API" +msgstr "" + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_abstract_moloni__moloni_id +#: model:ir.model.fields,field_description:account_moloni.field_account_move__moloni_id +#: model:ir.model.fields,field_description:account_moloni.field_account_tax__moloni_id +msgid "Moloni ID" +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/res_config_settings.py:0 +#: code:addons/account_moloni/models/res_config_settings.py:0 +#: code:addons/account_moloni/models/res_config_settings.py:0 +#, python-format +msgid "" +"Name: %s\n" +"\n" +msgstr "" + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_res_config_settings__moloni_password +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Password" +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Please configure Moloni authentication credentials in invoicing settings." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Please configure Moloni company id in invoicing settings." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Please configure Moloni document set id in invoicing settings." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Please configure Moloni product category name in invoicing settings." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Please configure Moloni product reference in invoicing settings." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Please configure Moloni product unit name in invoicing settings." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Please configure Moloni tax id in invoicing settings." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Please configure VAT for customer %s." +msgstr "" + +#. module: account_moloni +#: model_terms:ir.ui.view,arch_db:account_moloni.view_move_form +msgid "Post to Moloni" +msgstr "" + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_res_config_settings__moloni_product_category_name +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Product Category Name" +msgstr "" + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_res_config_settings__moloni_product_reference +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Product Reference" +msgstr "" + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_res_config_settings__moloni_product_unit_name +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Product Unit Name" +msgstr "" + +#. module: account_moloni +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Required to create the default product." +msgstr "" + +#. module: account_moloni +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Required to create the invoice." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Search for customer failed with status code %d." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Search for product failed with status code %d." +msgstr "" + +#. module: account_moloni +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "See Companies" +msgstr "" + +#. module: account_moloni +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "See Document Sets" +msgstr "" + +#. module: account_moloni +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "See Taxes" +msgstr "" + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_res_config_settings__moloni_invoice_status +msgid "Status" +msgstr "" + +#. module: account_moloni +#: model:ir.model,name:account_moloni.model_account_tax +msgid "Tax" +msgstr "" + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_res_config_settings__moloni_tax_id +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Tax ID" +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Tax with ID %s not found." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/res_config_settings.py:0 +#, python-format +msgid "" +"Taxes:\n" +"\n" +"%s." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Update customer failed with status code %d." +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Update customer failed:\n" +"\n" +"%s." +msgstr "" + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_res_config_settings__moloni_username +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Username" +msgstr "" + +#. module: account_moloni +#: code:addons/account_moloni/models/res_config_settings.py:0 +#, python-format +msgid "Value: %s\n" +msgstr "" diff --git a/l10n_pt_account_moloni/i18n/pt.po b/l10n_pt_account_moloni/i18n/pt.po new file mode 100644 index 00000000..924174f1 --- /dev/null +++ b/l10n_pt_account_moloni/i18n/pt.po @@ -0,0 +1,589 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_moloni +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-01-20 20:47+0000\n" +"PO-Revision-Date: 2020-01-20 20:47+0000\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: account_moloni +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Authentication Credentials" +msgstr "Credenciais de Autenticação" + +#. module: account_moloni +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Invoice Configurations" +msgstr "Configurações da Fatura" + +#. module: account_moloni +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Product Configurations" +msgstr "Configurações do Produto" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Authentication failed:\n" +"\n" +"%s." +msgstr "" +"Falha na autenticação:\n" +"\n" +"%s." + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_res_config_settings__moloni_client_secret_code +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Client Secret Code" +msgstr "Client Secret Code" + +#. module: account_moloni +#: model:ir.model.fields.selection,name:account_moloni.selection__res_config_settings__moloni_invoice_status__1 +msgid "Closed" +msgstr "Fechado" + +#. module: account_moloni +#: code:addons/account_moloni/models/res_config_settings.py:0 +#, python-format +msgid "" +"Companies:\n" +"\n" +"%s." +msgstr "" +"Empresas:\n" +"\n" +"%s." + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_res_config_settings__moloni_company_id +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Company ID" +msgstr "ID da Empresa" + +#. module: account_moloni +#: model:ir.model,name:account_moloni.model_res_config_settings +msgid "Config Settings" +msgstr "Configurações" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Create customer failed with status code %d." +msgstr "Falha ao criar cliente com o código %d." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Create customer failed:\n" +"\n" +"%s." +msgstr "" +"Falha ao criar cliente:\n" +"\n" +"%s." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Create invoice failed with status code %d." +msgstr "Falha na criação da fatura com o código %d." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Create invoice failed:\n" +"\n" +"%s." +msgstr "" +"Falha na criação da fatura:\n" +"\n" +"%s." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Create product category failed with status code %d." +msgstr "Falha na criação da categoria do produto com o código %d." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Create product category failed:\n" +"\n" +"%s." +msgstr "" +"Falha na criação da categoria do produto:\n" +"\n" +"%s." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Create product failed with status code %d." +msgstr "Falha ao criar o produto com o código %d." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Create product failed:\n" +"\n" +"%s." +msgstr "" +"Falha ao criar o produto:\n" +"\n" +"%s." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Create product unit failed with status code %d." +msgstr "Falha na criação da unidade do produto com o código %d." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Create product unit failed:\n" +"\n" +"%s." +msgstr "" +"Falha na criação da unidade do produto:\n" +"\n" +"%s." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Create tax failed with status code %d." +msgstr "Falha na criação do imposto com o código %d." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Create tax failed:\n" +"\n" +"%s." +msgstr "" +"Falha ao criar imposto:\n" +"\n" +"%s." + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_res_config_settings__moloni_developer_id +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Developer ID" +msgstr "Developer ID" + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_abstract_moloni__display_name +msgid "Display Name" +msgstr "Nome a Exibir" + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_res_config_settings__moloni_document_set_id +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Document Set ID" +msgstr "ID da Série de Documento" + +#. module: account_moloni +#: code:addons/account_moloni/models/res_config_settings.py:0 +#, python-format +msgid "" +"Document Sets:\n" +"\n" +"%s." +msgstr "" +"Séries de Documentos:\n" +"\n" +"%s." + +#. module: account_moloni +#: model:ir.model.fields.selection,name:account_moloni.selection__res_config_settings__moloni_invoice_status__0 +msgid "Draft" +msgstr "Rascunho" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Get all companies failed with status code %d." +msgstr "Falha ao obter todas as empresas com o código %d." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Get all companies failed:\n" +"\n" +"%s." +msgstr "" +"Falha ao obter todas as empresas:\n" +"\n" +"%s." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Get all document sets failed with status code %d." +msgstr "Falha ao obter todas as séries de documentos com o código %d." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Get all document sets failed:\n" +"\n" +"%s." +msgstr "" +"Falha ao obter todas as séries de documentos:\n" +"\n" +"%s." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Get all product categories failed with status code %d." +msgstr "Falha ao obter todas as categorias de produtos com o código %d." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Get all product categories failed:\n" +"\n" +"%s." +msgstr "" +"Falha ao obter todas as categorias de produtos:\n" +"\n" +"%s." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Get all product units failed with status code %d." +msgstr "Falha ao obter todas as unidades de produtos com o código %d." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Get all product units failed:\n" +"\n" +"%s." +msgstr "" +"Falha ao obter todas as unidades de produtos:\n" +"\n" +"%s." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Get all taxes failed with status code %d." +msgstr "Falha ao obter todos os impostos com o código %d." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Get all taxes failed:\n" +"\n" +"%s." +msgstr "" +"Falha ao obter todos os impostos:\n" +"\n" +"%s." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Get customer failed with status code %d." +msgstr "Falha ao obter o cliente com o código %d." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Get customer failed:\n" +"\n" +"%s." +msgstr "" +"Falha ao obter o cliente:\n" +"\n" +"%s." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Get next customer number failed with status code %d." +msgstr "Falha ao obter o próximo número de cliente com o código %d." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Get next customer number failed:\n" +"\n" +"%s." +msgstr "" +"Falha ao obter o próximo número de cliente:\n" +"\n" +"%s." + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_abstract_moloni__id +msgid "ID" +msgstr "ID" + +#. module: account_moloni +#: code:addons/account_moloni/models/res_config_settings.py:0 +#: code:addons/account_moloni/models/res_config_settings.py:0 +#: code:addons/account_moloni/models/res_config_settings.py:0 +#, python-format +msgid "ID: %s\n" +msgstr "ID: %s\n" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Incorrect Moloni authentication credentials." +msgstr "Credenciais de autenticação do Moloni incorretas." + +#. module: account_moloni +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Invoice Status" +msgstr "Status da Fatura" + +#. module: account_moloni +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "It's recommended that you change your password frequently." +msgstr "É recomendável que você altere sua password com frequência." + +#. module: account_moloni +#: model:ir.model,name:account_moloni.model_account_move +msgid "Journal Entries" +msgstr "Lançamentos de Diário" + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_abstract_moloni____last_update +msgid "Last Modified on" +msgstr "Última Modificação em" + +#. module: account_moloni +#: model:ir.model,name:account_moloni.model_abstract_moloni +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Moloni API" +msgstr "API do Moloni" + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_abstract_moloni__moloni_id +#: model:ir.model.fields,field_description:account_moloni.field_account_move__moloni_id +#: model:ir.model.fields,field_description:account_moloni.field_account_tax__moloni_id +msgid "Moloni ID" +msgstr "ID do Moloni" + +#. module: account_moloni +#: code:addons/account_moloni/models/res_config_settings.py:0 +#: code:addons/account_moloni/models/res_config_settings.py:0 +#: code:addons/account_moloni/models/res_config_settings.py:0 +#, python-format +msgid "" +"Name: %s\n" +"\n" +msgstr "" +"Nome: %s\n" +"\n" + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_res_config_settings__moloni_password +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Password" +msgstr "Password" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Please configure Moloni authentication credentials in invoicing settings." +msgstr "" +"Configure as credenciais de autenticação do Moloni nas configurações de faturação." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Please configure Moloni company id in invoicing settings." +msgstr "Configure o id da empresa do Moloni nas configurações de faturação." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Please configure Moloni document set id in invoicing settings." +msgstr "Configure o id da série de documento do Moloni nas configurações de faturação." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Please configure Moloni product category name in invoicing settings." +msgstr "Configure o nome da categoria do produto do Moloni nas configurações de faturação." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Please configure Moloni product reference in invoicing settings." +msgstr "Configure a referência do produto do Moloni nas configurações de faturação." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Please configure Moloni product unit name in invoicing settings." +msgstr "Configure o nome da unidade do produto do Moloni nas configurações de faturação." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Please configure Moloni tax id in invoicing settings." +msgstr "Configure o ID do imposto do Moloni nas configurações de faturação." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Please configure VAT for customer %s." +msgstr "Configure o NIF para o cliente %s." + +#. module: account_moloni +#: model_terms:ir.ui.view,arch_db:account_moloni.view_move_form +msgid "Post to Moloni" +msgstr "Faturar no Moloni" + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_res_config_settings__moloni_product_category_name +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Product Category Name" +msgstr "Nome da Categoria do Produto" + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_res_config_settings__moloni_product_reference +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Product Reference" +msgstr "Referência do Produto" + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_res_config_settings__moloni_product_unit_name +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Product Unit Name" +msgstr "Nome da Unidade do Produto" + +#. module: account_moloni +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Required to create the default product." +msgstr "Necessário para criar o produto padrão." + +#. module: account_moloni +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Required to create the invoice." +msgstr "Necessário para criar a fatura." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Search for customer failed with status code %d." +msgstr "A pesquisa do cliente falhou com o código %d." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Search for product failed with status code %d." +msgstr "A pesquisa do produto falhou com o código %d." + +#. module: account_moloni +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "See Companies" +msgstr "Ver Empresas" + +#. module: account_moloni +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "See Document Sets" +msgstr "Ver Séries de Documentos" + +#. module: account_moloni +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "See Taxes" +msgstr "Ver Impostos" + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_res_config_settings__moloni_invoice_status +msgid "Status" +msgstr "Status" + +#. module: account_moloni +#: model:ir.model,name:account_moloni.model_account_tax +msgid "Tax" +msgstr "Imposto" + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_res_config_settings__moloni_tax_id +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Tax ID" +msgstr "ID do Imposto" + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Tax with ID %s not found." +msgstr "Imposto com ID %s não encontrado." + +#. module: account_moloni +#: code:addons/account_moloni/models/res_config_settings.py:0 +#, python-format +msgid "" +"Taxes:\n" +"\n" +"%s." +msgstr "" +"Impostos:\n" +"\n" +"%s." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "Update customer failed with status code %d." +msgstr "Falha na atualização do cliente com o código %d." + +#. module: account_moloni +#: code:addons/account_moloni/models/account.py:0 +#, python-format +msgid "" +"Update customer failed:\n" +"\n" +"%s." +msgstr "" +"Falha na atualização do cliente:\n" +"\n" +"%s." + +#. module: account_moloni +#: model:ir.model.fields,field_description:account_moloni.field_res_config_settings__moloni_username +#: model_terms:ir.ui.view,arch_db:account_moloni.res_config_settings_view_form +msgid "Username" +msgstr "Nome de Utilizador" + +#. module: account_moloni +#: code:addons/account_moloni/models/res_config_settings.py:0 +#, python-format +msgid "Value: %s\n" +msgstr "Valor: %s\n" diff --git a/l10n_pt_account_moloni/models/__init__.py b/l10n_pt_account_moloni/models/__init__.py new file mode 100644 index 00000000..af07c03b --- /dev/null +++ b/l10n_pt_account_moloni/models/__init__.py @@ -0,0 +1,3 @@ +from . import account +from . import res_company +from . import res_config_settings diff --git a/l10n_pt_account_moloni/models/account.py b/l10n_pt_account_moloni/models/account.py new file mode 100644 index 00000000..3e1091a7 --- /dev/null +++ b/l10n_pt_account_moloni/models/account.py @@ -0,0 +1,167 @@ +from datetime import datetime + +import requests + +from odoo import _, fields, models +from odoo.exceptions import ValidationError + +from .res_company import INVOICE_CREATE + + +class AbstractMoloni(models.AbstractModel): + _name = "abstract.moloni" + _description = "Moloni API" + + moloni_id = fields.Char("Moloni ID") + + +class AccountMove(models.Model): + _name = "account.move" + _inherit = ["account.move", "abstract.moloni"] + + def moloni_post(self): + """Post invoices to Moloni.""" + company = None + company_id = None + document_set_id = None + access_token = None + product_id = None + invoice_status = 0 + + invoices = self.filtered(lambda i: i.state == "posted" and not i.moloni_id) + # Sort invoices by company, each time we change company we need to authenticate + invoices = invoices.sorted(lambda i: i.company_id) + + for invoice in invoices: + if not company or invoice.company_id.id != company.id: + company = invoice.company_id + + ( + company_id, + document_set_id, + moloni_product_reference, + ) = company.get_moloni_settings() + access_token = company.moloni_authenticate() + + # Product + product_id = company.moloni_get_product_id( + access_token, company_id, moloni_product_reference + ) + if not product_id: + product_id = company.moloni_create_product( + access_token, company_id, moloni_product_reference + ) + + # Taxes + company_invoices = invoices.filtered( + lambda i: i.company_id.id == company.id + ) + taxes = company_invoices.mapped("invoice_line_ids").mapped("tax_ids") + company.moloni_create_taxes(access_token, company_id, taxes) + + # Draft or close invoice + invoice_status = company.get_moloni_invoice_status() + + partner = invoice.partner_id + + # Validate if VAT exists + if not partner.vat: + raise ValidationError( + _("Please configure VAT for customer %s.") % partner.display_name + ) + + # Customer + customer_id = company.moloni_get_customer_id( + access_token, company_id, partner + ) + if customer_id: + company.moloni_update_customer( + access_token, company_id, customer_id, partner + ) + else: + customer_id = company.moloni_create_customer( + access_token, company_id, partner + ) + + invoice_id = invoice.moloni_create_invoice( + access_token, + company_id, + document_set_id, + customer_id, + product_id, + invoice_status, + ) + + invoice.write({"moloni_id": invoice_id}) + + def moloni_create_invoice( + self, + access_token, + company_id, + document_set_id, + customer_id, + product_id, + invoice_status, + ): + """Create invoice.""" + now = datetime.now().date() + + payload = { + "company_id": company_id, + "date": now, + "expiration_date": now, + "document_set_id": document_set_id, + "customer_id": customer_id, + "status": invoice_status, + } + + for i, line in enumerate(self.invoice_line_ids): + payload.update( + { + "products[%d][product_id]" % i: product_id, + "products[%d][name]" % i: line.name, + "products[%d][qty]" % i: line.quantity, + "products[%d][price]" % i: line.price_unit, + } + ) + + if line.discount: + payload.update( + { + "products[%d][discount]" % i: line.discount, + } + ) + + for j, tax in enumerate(line.tax_ids): + payload.update( + { + "products[%d][taxes][%d][tax_id]" % (i, j): tax.moloni_id, + "products[%d][taxes][%d][value]" % (i, j): tax.amount, + "products[%d][taxes][%d][order]" % (i, j): 1, + "products[%d][taxes][%d][cumulative]" % (i, j): 0, + } + ) + + r = requests.post( + INVOICE_CREATE.format(access_token=access_token), data=payload + ) + if r.status_code != 200: + raise ValidationError( + _("Create invoice failed with status code %d.") % r.status_code + ) + + r_json = r.json() + + if ( + not isinstance(r_json, dict) + or not r_json.get("document_id", False) + or not r_json.get("valid", False) + ): + raise ValidationError(_("Create invoice failed:\n\n%s.") % r_json) + + return r_json["document_id"] + + +class AccountTax(models.Model): + _name = "account.tax" + _inherit = ["account.tax", "abstract.moloni"] diff --git a/l10n_pt_account_moloni/models/res_company.py b/l10n_pt_account_moloni/models/res_company.py new file mode 100644 index 00000000..986c6dd1 --- /dev/null +++ b/l10n_pt_account_moloni/models/res_company.py @@ -0,0 +1,639 @@ +import re + +import requests + +from odoo import _, fields, models +from odoo.exceptions import ValidationError +from odoo.tools import float_compare + +AUTH_ENDPOINT = ( + "https://api.moloni.pt/v1/grant/?grant_type=password&client_id={developer_id}" + "&client_secret={client_secret_code}&username={username}&password={password}" +) +COMPANY_GET_ALL = ( + "https://api.moloni.pt/v1/companies/getAll/?access_token={access_token}" +) +DOCUMENT_SET_GET_ALL = ( + "https://api.moloni.pt/v1/documentSets/getAll/?access_token={access_token}" +) +TAX_GET_ALL = "https://api.moloni.pt/v1/taxes/getAll/?access_token={access_token}" +TAX_CREATE = "https://api.moloni.pt/v1/taxes/insert/?access_token={access_token}" +PRODUCT_SEARCH = ( + "https://api.moloni.pt/v1/products/getByReference/?access_token={access_token}" +) +PRODUCT_CREATE = "https://api.moloni.pt/v1/products/insert/?access_token={access_token}" +PRODUCT_CATEGORY_GET_ALL = ( + "https://api.moloni.pt/v1/productCategories/getAll/?access_token={access_token}" +) +PRODUCT_CATEGORY_CREATE = ( + "https://api.moloni.pt/v1/productCategories/insert/?access_token={access_token}" +) +PRODUCT_UNIT_GET_ALL = ( + "https://api.moloni.pt/v1/measurementUnits/getAll/?access_token={access_token}" +) +PRODUCT_UNIT_CREATE = ( + "https://api.moloni.pt/v1/measurementUnits/insert/?access_token={access_token}" +) +CUSTOMER_SEARCH = ( + "https://api.moloni.pt/v1/customers/getBySearch/?access_token={access_token}" +) +CUSTOMER_NEXT_NUMBER = ( + "https://api.moloni.pt/v1/customers/getNextNumber/?access_token={access_token}" +) +CUSTOMER_CREATE = ( + "https://api.moloni.pt/v1/customers/insert/?access_token={access_token}" +) +CUSTOMER_GET_ONE = ( + "https://api.moloni.pt/v1/customers/getOne/?access_token={access_token}" +) +CUSTOMER_UPDATE = ( + "https://api.moloni.pt/v1/customers/update/?access_token={access_token}" +) +INVOICE_CREATE = "https://api.moloni.pt/v1/invoices/insert/?access_token={access_token}" + + +class ResCompany(models.Model): + _inherit = "res.company" + + moloni_developer_id = fields.Char("Developer ID") + moloni_client_secret_code = fields.Char("Client Secret Code") + moloni_username = fields.Char("Username") + moloni_password = fields.Char("Password") + moloni_company_id = fields.Char("Company ID") + moloni_invoice_status = fields.Selection( + [("0", "Draft"), ("1", "Closed")], "Status" + ) + moloni_document_set_id = fields.Char("Document Set ID") + moloni_tax_id = fields.Char("Tax ID") + moloni_product_reference = fields.Char("Product Reference") + moloni_product_category_name = fields.Char("Product Category Name") + moloni_product_unit_name = fields.Char("Product Unit Name") + + def get_moloni_company_id(self): + """Get Moloni company id settings.""" + if not self.moloni_company_id: + raise ValidationError( + _("Please configure Moloni company id in invoicing settings.") + ) + + return self.moloni_company_id + + def get_moloni_invoice_status(self): + """Get Moloni invoice status settings.""" + return int(self.moloni_invoice_status or 0) + + def get_moloni_settings(self): + """Get Moloni settings.""" + company_id = self.get_moloni_company_id() + + if not self.moloni_document_set_id: + raise ValidationError( + _("Please configure Moloni document set id in invoicing settings.") + ) + + if not self.moloni_product_reference: + raise ValidationError( + _("Please configure Moloni product reference in invoicing settings.") + ) + + return company_id, self.moloni_document_set_id, self.moloni_product_reference + + def get_moloni_product_settings(self): + """Get Moloni product settings.""" + if not self.moloni_tax_id: + raise ValidationError( + _("Please configure Moloni tax id in invoicing settings.") + ) + + if not self.moloni_product_category_name: + raise ValidationError( + _( + "Please configure Moloni product category name in invoicing settings." + ) + ) + + if not self.moloni_product_unit_name: + raise ValidationError( + _("Please configure Moloni product unit name in invoicing settings.") + ) + + return ( + self.moloni_tax_id, + self.moloni_product_category_name, + self.moloni_product_unit_name, + ) + + def moloni_authenticate(self): + """ + Authenticates in Moloni using the simple method for native applications + and get access_token if success. + """ + developer_id = self.moloni_developer_id + client_secret_code = self.moloni_client_secret_code + username = self.moloni_username + password = self.moloni_password + + if not developer_id or not client_secret_code or not username or not password: + raise ValidationError( + _( + "Please configure Moloni authentication credentials in invoicing settings." + ) + ) + + r = requests.get( + AUTH_ENDPOINT.format( + developer_id=developer_id, + client_secret_code=client_secret_code, + username=username, + password=password, + ) + ) + + if r.status_code != 200: + raise ValidationError(_("Incorrect Moloni authentication credentials.")) + + # Valid for 1h + r_json = r.json() + + access_token = isinstance(r_json, dict) and r_json.get("access_token", False) + if not access_token: + raise ValidationError(_("Authentication failed:\n\n%s.") % r_json) + + return access_token + + def moloni_get_all_companies(self, access_token): + """Get all companies.""" + r = requests.get(COMPANY_GET_ALL.format(access_token=access_token)) + if r.status_code != 200: + raise ValidationError( + _("Get all companies failed with status code %d.") % r.status_code + ) + + companies = r.json() + + if not isinstance(companies, list): + raise ValidationError(_("Get all companies failed:\n\n%s.") % companies) + + return companies + + def moloni_get_all_document_sets(self, access_token, company_id): + """Get all document sets.""" + payload = { + "company_id": company_id, + } + + r = requests.post( + DOCUMENT_SET_GET_ALL.format(access_token=access_token), data=payload + ) + if r.status_code != 200: + raise ValidationError( + _("Get all document sets failed with status code %d.") % r.status_code + ) + + document_sets = r.json() + + if not isinstance(document_sets, list): + raise ValidationError( + _("Get all document sets failed:\n\n%s.") % document_sets + ) + + return document_sets + + def moloni_get_all_taxes(self, access_token, company_id): + """Get all taxes.""" + payload = { + "company_id": company_id, + } + + r = requests.post(TAX_GET_ALL.format(access_token=access_token), data=payload) + if r.status_code != 200: + raise ValidationError( + _("Get all taxes failed with status code %d.") % r.status_code + ) + + taxes = r.json() + + if not isinstance(taxes, list): + raise ValidationError(_("Get all taxes failed:\n\n%s.") % taxes) + + return taxes + + def moloni_get_product_id(self, access_token, company_id, moloni_product_reference): + """Check if we have a product with the default product reference.""" + payload = { + "company_id": company_id, + "reference": moloni_product_reference, + } + + r = requests.post( + PRODUCT_SEARCH.format(access_token=access_token), data=payload + ) + if r.status_code != 200: + raise ValidationError( + _("Search for product failed with status code %d.") % r.status_code + ) + + r_json = r.json() + + return ( + r_json + and isinstance(r_json[0], dict) + and r_json[0].get("product_id", False) + ) + + def moloni_create_product(self, access_token, company_id, moloni_product_reference): + """Create product.""" + ( + moloni_tax_id, + moloni_product_category_name, + moloni_product_unit_name, + ) = self.get_moloni_product_settings() + + payload = { + "company_id": company_id, + "category_id": self.get_product_category_id( + access_token, company_id, moloni_product_category_name + ), + "type": 1, + "name": moloni_product_reference, + "reference": moloni_product_reference, + "price": 0.0, + "unit_id": self.get_product_unit_id( + access_token, company_id, moloni_product_unit_name + ), + "has_stock": False, + "taxes[0][tax_id]": moloni_tax_id, + "taxes[0][value]": self.get_tax_value( + access_token, company_id, moloni_tax_id + ), + "taxes[0][order]": 1, + "taxes[0][cumulative]": 0, + } + + r = requests.post( + PRODUCT_CREATE.format(access_token=access_token), data=payload + ) + if r.status_code != 200: + raise ValidationError( + _("Create product failed with status code %d.") % r.status_code + ) + + r_json = r.json() + + if ( + not isinstance(r_json, dict) + or not r_json.get("product_id", False) + or not r_json.get("valid", False) + ): + raise ValidationError(_("Create product failed:\n\n%s.") % r_json) + + return r_json["product_id"] + + def get_product_category_id( + self, access_token, company_id, moloni_product_category_name + ): + """ + Get product category id. + Create a new category if one with the default category name doesn't exist. + """ + payload = { + "company_id": company_id, + "parent_id": False, + } + + # Search for a product category with the name configured on invoice settings + r = requests.post( + PRODUCT_CATEGORY_GET_ALL.format(access_token=access_token), data=payload + ) + if r.status_code != 200: + raise ValidationError( + _("Get all product categories failed with status code %d.") + % r.status_code + ) + + product_categories = r.json() + + if not isinstance(product_categories, list): + raise ValidationError( + _("Get all product categories failed:\n\n%s.") % product_categories + ) + + for product_category in product_categories: + if product_category["name"] == moloni_product_category_name: + return product_category["category_id"] + + # Create new product category with the name configured on invoice settings + payload = { + "company_id": company_id, + "parent_id": False, + "name": moloni_product_category_name, + } + + r = requests.post( + PRODUCT_CATEGORY_CREATE.format(access_token=access_token), data=payload + ) + if r.status_code != 200: + raise ValidationError( + _("Create product category failed with status code %d.") % r.status_code + ) + + r_json = r.json() + + if ( + not isinstance(r_json, dict) + or not r_json.get("category_id", False) + or not r_json.get("valid", False) + ): + raise ValidationError(_("Create product category failed:\n\n%s.") % r_json) + + return r_json["category_id"] + + def get_product_unit_id(self, access_token, company_id, moloni_product_unit_name): + """ + Get product unit id. + Create a new unit if one with the default category name doesn't exist. + """ + payload = { + "company_id": company_id, + } + + # Search for a product unit with the name configured on invoice settings + r = requests.post( + PRODUCT_UNIT_GET_ALL.format(access_token=access_token), data=payload + ) + if r.status_code != 200: + raise ValidationError( + _("Get all product units failed with status code %d.") % r.status_code + ) + + product_units = r.json() + + if not isinstance(product_units, list): + raise ValidationError( + _("Get all product units failed:\n\n%s.") % product_units + ) + + for product_unit in product_units: + if product_unit["name"] == moloni_product_unit_name: + return product_unit["unit_id"] + + # Create new product unit with the name configured on invoice settings + payload = { + "company_id": company_id, + "name": moloni_product_unit_name, + "short_name": moloni_product_unit_name, + } + + r = requests.post( + PRODUCT_UNIT_CREATE.format(access_token=access_token), data=payload + ) + if r.status_code != 200: + raise ValidationError( + _("Create product unit failed with status code %d.") % r.status_code + ) + + r_json = r.json() + + if ( + not isinstance(r_json, dict) + or not r_json.get("unit_id", False) + or not r_json.get("valid", False) + ): + raise ValidationError(_("Create product unit failed:\n\n%s.") % r_json) + + return r_json["unit_id"] + + def get_tax_value(self, access_token, company_id, moloni_tax_id): + """Get tax value.""" + taxes = self.moloni_get_all_taxes(access_token, company_id) + + moloni_tax_id = int(moloni_tax_id) + + for tax in taxes: + if tax["tax_id"] == moloni_tax_id: + return tax["value"] + + raise ValidationError(_("Tax with ID %s not found.") % moloni_tax_id) + + def moloni_create_taxes(self, access_token, company_id, taxes): + """Create taxes.""" + moloni_taxes = self.moloni_get_all_taxes(access_token, company_id) + + for tax in taxes.filtered(lambda t: t.moloni_id): + self.moloni_check_if_tax_changed(moloni_taxes, tax) + + for tax in taxes.filtered(lambda t: not t.moloni_id): + self.moloni_create_tax(access_token, company_id, tax) + + def moloni_check_if_tax_changed(self, moloni_taxes, tax): + """If the tax changed in Moloni, unset moloni id so the tax can be recreated.""" + for moloni_tax in moloni_taxes: + if moloni_tax["tax_id"] == int(tax.moloni_id): + if ( + moloni_tax["name"] != tax.display_name + or float_compare(moloni_tax["value"], tax.amount, 2) != 0 + ): + tax.write({"moloni_id": False}) + break + else: + tax.write({"moloni_id": False}) + + def moloni_create_tax(self, access_token, company_id, tax): + """Create tax.""" + payload = { + "company_id": company_id, + "name": tax.display_name, + "value": tax.amount, + "type": 1, + "saft_type": 1, + "vat_type": "NOR", + "stamp_tax": False, + "exemption_reason": False, + "fiscal_zone": "PT", + "active_by_default": 1, + } + + r = requests.post(TAX_CREATE.format(access_token=access_token), data=payload) + if r.status_code != 200: + raise ValidationError( + _("Create tax failed with status code %d.") % r.status_code + ) + + r_json = r.json() + + if ( + not isinstance(r_json, dict) + or not r_json.get("tax_id", False) + or not r_json.get("valid", False) + ): + raise ValidationError(_("Create tax failed:\n\n%s.") % r_json) + + tax.write({"moloni_id": r_json["tax_id"]}) + + def moloni_get_customer_id(self, access_token, company_id, partner): + """ + Check if we have a customer with the required VAT already created in Moloni. + If the customer already has an invoice, VAT change is not possible in Moloni + and it will throw an error silently. + """ + payload = { + "company_id": company_id, + "search": partner.vat, + } + + r = requests.post( + CUSTOMER_SEARCH.format(access_token=access_token), data=payload + ) + if r.status_code != 200: + raise ValidationError( + _("Search for customer failed with status code %d.") % r.status_code + ) + + r_json = r.json() + + return ( + r_json + and isinstance(r_json[0], dict) + and r_json[0].get("customer_id", False) + ) + + def moloni_update_customer(self, access_token, company_id, customer_id, partner): + """Update customer.""" + payload = { + "company_id": company_id, + "customer_id": customer_id, + } + + r = requests.post( + CUSTOMER_GET_ONE.format(access_token=access_token), data=payload + ) + if r.status_code != 200: + raise ValidationError( + _("Get customer failed with status code %d.") % r.status_code + ) + + customer = r.json() + + if not isinstance(customer, dict) or not customer.get("name", False): + raise ValidationError(_("Get customer failed:\n\n%s.") % customer) + + payload = { + "company_id": company_id, + "customer_id": customer_id, + "vat": partner.vat, + "number": customer["number"], + "name": partner.display_name, + "language_id": customer["language_id"], + "address": self.moloni_get_partner_address(partner), + "zip_code": self.moloni_get_partner_zip_code(partner), + "city": partner.city or customer["city"], + "country_id": customer["country_id"], + "email": self.moloni_get_partner_email(partner), + "maturity_date_id": customer["maturity_date_id"], + "payment_method_id": customer["payment_method_id"], + } + + r = requests.post( + CUSTOMER_UPDATE.format(access_token=access_token), data=payload + ) + if r.status_code != 200: + raise ValidationError( + _("Update customer failed with status code %d.") % r.status_code + ) + + r_json = r.json() + + if ( + not isinstance(r_json, dict) + or not r_json.get("customer_id", False) + or not r_json.get("valid", False) + ): + raise ValidationError(_("Update customer failed:\n\n%s.") % r_json) + + def moloni_create_customer(self, access_token, company_id, partner): + """Create customer.""" + payload = { + "company_id": company_id, + "vat": partner.vat, + "number": self.moloni_get_next_customer_number(access_token, company_id), + "name": partner.display_name, + "language_id": 1, + "address": self.moloni_get_partner_address(partner), + "zip_code": self.moloni_get_partner_zip_code(partner), + "city": partner.city or "", + "country_id": 1, + "email": self.moloni_get_partner_email(partner), + "maturity_date_id": False, + "payment_method_id": False, + "salesman_id": False, + "payment_day": False, + "discount": False, + "credit_limit": False, + "delivery_method_id": False, + } + + r = requests.post( + CUSTOMER_CREATE.format(access_token=access_token), data=payload + ) + if r.status_code != 200: + raise ValidationError( + _("Create customer failed with status code %d.") % r.status_code + ) + + r_json = r.json() + + if ( + not isinstance(r_json, dict) + or not r_json.get("customer_id", False) + or not r_json.get("valid", False) + ): + raise ValidationError(_("Create customer failed:\n\n%s.") % r_json) + + return r_json["customer_id"] + + def moloni_get_partner_address(self, partner): + """Get partner address.""" + address = "" + if partner.street: + address = partner.street + if partner.street2: + if address: + address += ", " + address += partner.street2 + return address + + def moloni_get_partner_zip_code(self, partner): + """Return partner zip code.""" + if partner.zip: + pattern = re.compile(r"^\d{4}-\d{3}?$") + return pattern.match(partner.zip) and partner.zip or "" + return "" + + def moloni_get_partner_email(self, partner): + """Return partner email.""" + if partner.email: + pattern = re.compile(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$") + return pattern.match(partner.email) and partner.email or "" + return "" + + def moloni_get_next_customer_number(self, access_token, company_id): + """Returns the next available customer number.""" + payload = { + "company_id": company_id, + } + + r = requests.post( + CUSTOMER_NEXT_NUMBER.format(access_token=access_token), data=payload + ) + if r.status_code != 200: + raise ValidationError( + _("Get next customer number failed with status code %d.") + % r.status_code + ) + + r_json = r.json() + + if not isinstance(r_json, dict) or not r_json.get("number", False): + raise ValidationError(_("Get next customer number failed:\n\n%s.") % r_json) + + return r_json["number"] diff --git a/l10n_pt_account_moloni/models/res_config_settings.py b/l10n_pt_account_moloni/models/res_config_settings.py new file mode 100644 index 00000000..fcfa4684 --- /dev/null +++ b/l10n_pt_account_moloni/models/res_config_settings.py @@ -0,0 +1,76 @@ +from odoo import _, fields, models +from odoo.exceptions import ValidationError + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + moloni_developer_id = fields.Char( + related="company_id.moloni_developer_id", readonly=False + ) + moloni_client_secret_code = fields.Char( + related="company_id.moloni_client_secret_code", readonly=False + ) + moloni_username = fields.Char(related="company_id.moloni_username", readonly=False) + moloni_password = fields.Char(related="company_id.moloni_password", readonly=False) + moloni_company_id = fields.Char( + related="company_id.moloni_company_id", readonly=False + ) + moloni_invoice_status = fields.Selection( + related="company_id.moloni_invoice_status", readonly=False + ) + moloni_document_set_id = fields.Char( + related="company_id.moloni_document_set_id", readonly=False + ) + moloni_tax_id = fields.Char(related="company_id.moloni_tax_id", readonly=False) + moloni_product_reference = fields.Char( + related="company_id.moloni_product_reference", readonly=False + ) + moloni_product_category_name = fields.Char( + related="company_id.moloni_product_category_name", readonly=False + ) + moloni_product_unit_name = fields.Char( + related="company_id.moloni_product_unit_name", readonly=False + ) + + def moloni_get_all_companies(self): + """Show all companies.""" + company = self.env.company + access_token = company.moloni_authenticate() + companies = company.moloni_get_all_companies(access_token) + + msg = "" + for company in companies: + msg += _("ID: %s\n") % company["company_id"] + msg += _("Name: %s\n\n") % company["name"] + + raise ValidationError(_("Companies:\n\n%s.") % msg) + + def moloni_get_all_document_sets(self): + """Show all document sets.""" + company = self.env.company + company_id = company.get_moloni_company_id() + access_token = company.moloni_authenticate() + document_sets = company.moloni_get_all_document_sets(access_token, company_id) + + msg = "" + for document_set in document_sets: + msg += _("ID: %s\n") % document_set["document_set_id"] + msg += _("Name: %s\n\n") % document_set["name"] + + raise ValidationError(_("Document Sets:\n\n%s.") % msg) + + def moloni_get_all_taxes(self): + """Show all taxes.""" + company = self.env.company + company_id = company.get_moloni_company_id() + access_token = company.moloni_authenticate() + taxes = company.moloni_get_all_taxes(access_token, company_id) + + msg = "" + for tax in taxes: + msg += _("ID: %s\n") % tax["tax_id"] + msg += _("Value: %s\n") % tax["value"] + msg += _("Name: %s\n\n") % tax["name"] + + raise ValidationError(_("Taxes:\n\n%s.") % msg) diff --git a/l10n_pt_account_moloni/readme/CONTRIBUTORS.rst b/l10n_pt_account_moloni/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..0bf4ac5e --- /dev/null +++ b/l10n_pt_account_moloni/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* ERP Gap +* Daniel Reis , `Open Source Integrators `_ diff --git a/l10n_pt_account_moloni/readme/DESCRIPTION.rst b/l10n_pt_account_moloni/readme/DESCRIPTION.rst new file mode 100644 index 00000000..f469c23b --- /dev/null +++ b/l10n_pt_account_moloni/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +O melhor software certificado de gestão e facturação online! +Gerir a sua empresa nunca foi tão simples, barato e eficiente. +Emita faturas em segundos. diff --git a/l10n_pt_account_moloni/static/description/icon.png b/l10n_pt_account_moloni/static/description/icon.png new file mode 100644 index 00000000..02d4f6db Binary files /dev/null and b/l10n_pt_account_moloni/static/description/icon.png differ diff --git a/l10n_pt_account_moloni/static/description/index.html b/l10n_pt_account_moloni/static/description/index.html new file mode 100644 index 00000000..f6fc73bf --- /dev/null +++ b/l10n_pt_account_moloni/static/description/index.html @@ -0,0 +1,423 @@ + + + + + +Moloni Invoicing + + + +
+

Moloni Invoicing

+ + +

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

+

O melhor software certificado de gestão e facturação online! +Gerir a sua empresa nunca foi tão simples, barato e eficiente. +Emita faturas em segundos.

+

Table of contents

+ +
+

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

+
    +
  • ERp Gap
  • +
+
+ +
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

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

+

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

+
+
+
+ + diff --git a/l10n_pt_account_moloni/views/account_views.xml b/l10n_pt_account_moloni/views/account_views.xml new file mode 100644 index 00000000..9892091b --- /dev/null +++ b/l10n_pt_account_moloni/views/account_views.xml @@ -0,0 +1,35 @@ + + + + + account.move.form + account.move + + + +