diff --git a/account_fiscal_position_product/README.rst b/account_fiscal_position_product/README.rst new file mode 100644 index 000000000..f7b592aa2 --- /dev/null +++ b/account_fiscal_position_product/README.rst @@ -0,0 +1,70 @@ +================================= +Account Fiscal Position - Product +================================= + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Faccount--fiscal--rule-lightgray.png?logo=github + :target: https://github.com/OCA/account-fiscal-rule/tree/12.0/account_fiscal_position_product + :alt: OCA/account-fiscal-rule +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/account-fiscal-rule-12-0/account-fiscal-rule-12-0-account_fiscal_position_product + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/93/12.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Allow to configure products in the Fiscal Position tax lines. + +When the fiscal position is applied, the taxes are mapped only if the product or its category is configured in the tax line. + +**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 smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* TAKOBI + +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/account-fiscal-rule `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/account_fiscal_position_product/__init__.py b/account_fiscal_position_product/__init__.py new file mode 100644 index 000000000..b18eea379 --- /dev/null +++ b/account_fiscal_position_product/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import models diff --git a/account_fiscal_position_product/__manifest__.py b/account_fiscal_position_product/__manifest__.py new file mode 100644 index 000000000..cad3e26c1 --- /dev/null +++ b/account_fiscal_position_product/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2022 Simone Rubino - TAKOBI +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + 'name': 'Account Fiscal Position - Product', + 'summary': 'Apply fiscal position only for configured products', + 'version': '12.0.1.0.0', + 'category': 'Accounting', + 'author': 'TAKOBI, Odoo Community Association (OCA)', + 'website': 'https://github.com/OCA/account-fiscal-rule' + '/tree/12.0/account_fiscal_position_product', + 'license': 'AGPL-3', + 'development_status': 'Beta', + 'depends': [ + 'account', + ], + 'data': [ + 'views/account_fiscal_position_views.xml', + ], +} diff --git a/account_fiscal_position_product/i18n/it.po b/account_fiscal_position_product/i18n/it.po new file mode 100644 index 000000000..e94aa23b7 --- /dev/null +++ b/account_fiscal_position_product/i18n/it.po @@ -0,0 +1,36 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_fiscal_position_product +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-11-23 14:39+0000\n" +"PO-Revision-Date: 2022-11-23 14:39+0000\n" +"Last-Translator: Simone Rubino \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_fiscal_position_product +#: model:ir.model,name:account_fiscal_position_product.model_account_fiscal_position +msgid "Fiscal Position" +msgstr "Posizione fiscale" + +#. module: account_fiscal_position_product +#: model:ir.model.fields,field_description:account_fiscal_position_product.field_account_fiscal_position_tax__product_category_ids +msgid "Product Categories" +msgstr "Categorie prodotto" + +#. module: account_fiscal_position_product +#: model:ir.model.fields,field_description:account_fiscal_position_product.field_account_fiscal_position_tax__product_ids +msgid "Products" +msgstr "Prodotti" + +#. module: account_fiscal_position_product +#: model:ir.model,name:account_fiscal_position_product.model_account_fiscal_position_tax +msgid "Tax Mapping of Fiscal Position" +msgstr "Mappatura imposta per la posizione fiscale" diff --git a/account_fiscal_position_product/models/__init__.py b/account_fiscal_position_product/models/__init__.py new file mode 100644 index 000000000..278194b7f --- /dev/null +++ b/account_fiscal_position_product/models/__init__.py @@ -0,0 +1,4 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import account_fiscal_position +from . import account_fiscal_position_tax diff --git a/account_fiscal_position_product/models/account_fiscal_position.py b/account_fiscal_position_product/models/account_fiscal_position.py new file mode 100644 index 000000000..d25d1e32b --- /dev/null +++ b/account_fiscal_position_product/models/account_fiscal_position.py @@ -0,0 +1,40 @@ +# Copyright 2022 Simone Rubino - TAKOBI +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, models + + +class AccountFiscalPosition (models.Model): + _inherit = 'account.fiscal.position' + + def is_product_fiscal_position(self): + """ + True if any tax mapping in `self.tax_ids` + has a product or a product category. + """ + tax_mappings = self.mapped('tax_ids') + return tax_mappings.is_product_tax_mapping() + + @api.model + def map_tax(self, taxes, product=None, partner=None): + """ + If this is a product fiscal position, + only apply tax mappings matching `product`. + Otherwise, return the usual mapping (defined in `super`). + """ + if self.is_product_fiscal_position(): + if product is not None: + matching_tax_mappings = self.tax_ids.filtered( + lambda tax_mapping: tax_mapping.match_product(product) + ) + if matching_tax_mappings: + result = matching_tax_mappings.map_taxes(taxes) + else: + # No mapping matching `product`: return original taxes + result = taxes + else: + # No `product`: return original taxes + result = taxes + else: + result = super().map_tax(taxes, product=product, partner=partner) + return result diff --git a/account_fiscal_position_product/models/account_fiscal_position_tax.py b/account_fiscal_position_product/models/account_fiscal_position_tax.py new file mode 100644 index 000000000..71935f37e --- /dev/null +++ b/account_fiscal_position_product/models/account_fiscal_position_tax.py @@ -0,0 +1,69 @@ +# Copyright 2022 Simone Rubino - TAKOBI +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class AccountFiscalPositionTax (models.Model): + _inherit = 'account.fiscal.position.tax' + + product_ids = fields.Many2many( + comodel_name='product.product', + string="Products", + ) + product_category_ids = fields.Many2many( + comodel_name='product.category', + string="Product Categories", + ) + + @api.multi + def is_product_tax_mapping(self): + """ + True if any tax mapping in `self` + has a product or a product category. + """ + for tax_mapping in self: + if tax_mapping.product_ids or tax_mapping.product_category_ids: + is_product = True + break + else: + is_product = False + return is_product + + @api.multi + def match_product(self, product): + """ + Return the first mapping in `self` that matches `product`. + + A mapping matches `product` + when `product` is declared in the mapping, + or when its category is declared in the mapping. + """ + for tax_line in self: + if product in tax_line.product_ids \ + or product.categ_id in tax_line.product_category_ids: + break + else: + tax_line = self.browse() + return tax_line + + def map_taxes(self, taxes): + """ + Map each tax in `taxes` using mappings in `self`. + + When a tax matches `tax_src_id`, + then `tax_dest_id` is its mapped tax, + otherwise the tax is mapped to itself. + """ + result = self.env['account.tax'].browse() + for tax in taxes: + for tax_mapping in self: + tax_src = tax_mapping.tax_src_id + tax_dest = tax_mapping.tax_dest_id + if tax_src == tax and tax_dest: + result |= tax_dest + break + else: + # No mapping matching `tax`: return original tax + result |= tax + return result diff --git a/account_fiscal_position_product/readme/DESCRIPTION.rst b/account_fiscal_position_product/readme/DESCRIPTION.rst new file mode 100644 index 000000000..c0363b67f --- /dev/null +++ b/account_fiscal_position_product/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +Allow to configure products in the Fiscal Position tax lines. + +When the fiscal position is applied, the taxes are mapped only if the product or its category is configured in the tax line. diff --git a/account_fiscal_position_product/static/description/index.html b/account_fiscal_position_product/static/description/index.html new file mode 100644 index 000000000..59c7d7b5f --- /dev/null +++ b/account_fiscal_position_product/static/description/index.html @@ -0,0 +1,413 @@ + + + + + + +Account Fiscal Position - Product + + + +
+

Account Fiscal Position - Product

+ + +

Beta License: AGPL-3 OCA/account-fiscal-rule Translate me on Weblate Try me on Runbot

+

Allow to configure products in the Fiscal Position tax lines.

+

When the fiscal position is applied, the taxes are mapped only if the product or its category is configured in the tax line.

+

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 smashing it by providing a detailed and welcomed +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • TAKOBI
  • +
+
+
+

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/account-fiscal-rule project on GitHub.

+

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

+
+
+
+ + diff --git a/account_fiscal_position_product/tests/__init__.py b/account_fiscal_position_product/tests/__init__.py new file mode 100644 index 000000000..cbb05fdbb --- /dev/null +++ b/account_fiscal_position_product/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import test_fiscal_position_product diff --git a/account_fiscal_position_product/tests/test_fiscal_position_product.py b/account_fiscal_position_product/tests/test_fiscal_position_product.py new file mode 100644 index 000000000..aa666f866 --- /dev/null +++ b/account_fiscal_position_product/tests/test_fiscal_position_product.py @@ -0,0 +1,79 @@ +# Copyright 2022 Simone Rubino - TAKOBI +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo.tests import SavepointCase, Form + + +class TestFiscalPositionProduct (SavepointCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + product_form = Form(cls.env['product.product']) + product_form.name = "Test Product" + cls.product = product_form.save() + + dest_tax_form = Form(cls.env['account.tax']) + dest_tax_form.name = "Destination Tax" + cls.dest_tax = dest_tax_form.save() + + fiscal_position_form = Form(cls.env['account.fiscal.position']) + fiscal_position_form.name = "Test Fiscal Position for Product" + with fiscal_position_form.tax_ids.new() as map_tax: + map_tax.tax_src_id = cls.product.taxes_id + map_tax.tax_dest_id = cls.dest_tax + map_tax.product_ids.add(cls.product) + cls.fiscal_position = fiscal_position_form.save() + + partner_form = Form(cls.env['res.partner']) + partner_form.name = "Test partner with Product" + partner_form.property_account_position_id = cls.fiscal_position + cls.partner = partner_form.save() + + def _create_invoice(self, partner, products): + invoice_form = Form(self.env['account.invoice']) + invoice_form.partner_id = partner + for product in products: + with invoice_form.invoice_line_ids.new() as line: + line.product_id = product + invoice = invoice_form.save() + return invoice + + def test_fiscal_position_product(self): + """ + When the invoice contains + a product configured in the fiscal position, + the mapping is applied. + """ + # Arrange: the invoiced product is in the fiscal position + partner = self.partner + product = self.product + invoice = self._create_invoice(partner, product) + fiscal_position = invoice.fiscal_position_id + self.assertTrue(fiscal_position.tax_ids.match_product(product)) + + # Assert: the tax in the invoice comes from the fiscal position + invoice_line_tax = invoice.invoice_line_ids.invoice_line_tax_ids + self.assertEqual(invoice_line_tax, self.dest_tax) + + def test_fiscal_position_no_product_match(self): + """ + When the invoice does not contain + a product configured in the fiscal position, + the mapping is not applied. + """ + # Arrange: the invoiced product is not in the fiscal position + partner = self.partner + other_product_form = Form(self.env['product.product']) + other_product_form.name = "Test Other Product" + other_product = other_product_form.save() + invoice = self._create_invoice(partner, other_product) + fiscal_position = invoice.fiscal_position_id + self.assertFalse(fiscal_position.tax_ids.match_product(other_product)) + + # Assert: the tax in the invoice comes from the product + invoice_line_tax = invoice.invoice_line_ids.invoice_line_tax_ids + self.assertEqual( + invoice_line_tax, + other_product.taxes_id, + ) diff --git a/account_fiscal_position_product/views/account_fiscal_position_views.xml b/account_fiscal_position_product/views/account_fiscal_position_views.xml new file mode 100644 index 000000000..7a2520286 --- /dev/null +++ b/account_fiscal_position_product/views/account_fiscal_position_views.xml @@ -0,0 +1,22 @@ + + + + + Add Product to Fiscal Position form view + account.fiscal.position + + + + + + + + + + + + + diff --git a/l10n_eu_oss/wizard/l10n_eu_oss_wizard.py b/l10n_eu_oss/wizard/l10n_eu_oss_wizard.py index 633ee4d98..1b03d84a1 100644 --- a/l10n_eu_oss/wizard/l10n_eu_oss_wizard.py +++ b/l10n_eu_oss/wizard/l10n_eu_oss_wizard.py @@ -2,7 +2,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from odoo import _, fields, models -from odoo.exceptions import Warning +from odoo.exceptions import UserError class L10nEuOssWizard(models.TransientModel): @@ -15,7 +15,7 @@ def _get_default_company_id(self): def _get_eu_res_country_group(self): eu_group = self.env.ref("base.europe", raise_if_not_found=False) if not eu_group: - raise Warning( + raise UserError( _( "The Europe country group cannot be found. " "Please update the base module." diff --git a/product_refund_account/models/account_invoice_line.py b/product_refund_account/models/account_invoice_line.py index d0157fe38..646662018 100644 --- a/product_refund_account/models/account_invoice_line.py +++ b/product_refund_account/models/account_invoice_line.py @@ -6,15 +6,15 @@ class AccountInvoiceLine(models.Model): _inherit = "account.invoice.line" - def get_invoice_line_account(self, type, product, fpos, company): + def get_invoice_line_account(self, type_, product, fpos, company): account_in = product.property_account_refund_in_id or \ product.categ_id.property_account_refund_in_categ_id account_out = product.property_account_refund_out_id or \ product.categ_id.property_account_refund_out_categ_id - if type in ["in_refund"] and account_in: + if type_ in ["in_refund"] and account_in: return account_in - elif type in ["out_refund"] and account_out: + elif type_ in ["out_refund"] and account_out: return account_out else: return super().get_invoice_line_account( - type, product, fpos, company) + type_, product, fpos, company) diff --git a/setup/account_fiscal_position_product/odoo/addons/account_fiscal_position_product b/setup/account_fiscal_position_product/odoo/addons/account_fiscal_position_product new file mode 120000 index 000000000..cdc76f4ba --- /dev/null +++ b/setup/account_fiscal_position_product/odoo/addons/account_fiscal_position_product @@ -0,0 +1 @@ +../../../../account_fiscal_position_product \ No newline at end of file diff --git a/setup/account_fiscal_position_product/setup.py b/setup/account_fiscal_position_product/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/account_fiscal_position_product/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)