Skip to content

Commit

Permalink
Merge PR #441 into 16.0
Browse files Browse the repository at this point in the history
Signed-off-by legalsylvain
  • Loading branch information
OCA-git-bot committed Jul 4, 2024
2 parents c2f474e + da630aa commit f2919ce
Show file tree
Hide file tree
Showing 2 changed files with 192 additions and 79 deletions.
79 changes: 61 additions & 18 deletions account_product_fiscal_classification/models/product_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

import json
import logging

from lxml import etree

from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
from odoo import api, fields, models

_logger = logging.getLogger(__name__)


class ProductTemplate(models.Model):
Expand All @@ -28,16 +30,32 @@ class ProductTemplate(models.Model):
" manager if you don't have the access right.",
)

@api.constrains("categ_id", "fiscal_classification_id")
def _check_rules_fiscal_classification(self):
self.env["account.product.fiscal.rule"].check_product_templates_integrity(self)

# Overload Section
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
self._update_vals_fiscal_classification(vals)
return super().create(vals_list)
self._fiscal_classification_update_taxes(vals)
templates = super().create(vals_list)
for template in templates.filtered(lambda x: not x.fiscal_classification_id):
template.fiscal_classification_id = (
template._fiscal_classification_get_or_create()[0]
)
return templates

def write(self, vals):
self._update_vals_fiscal_classification(vals)
self._fiscal_classification_update_taxes(vals)
res = super().write(vals)
if ({"supplier_taxes_id", "taxes_id"} & vals.keys()) and (
"fiscal_classification_id" not in vals.keys()
):
for template in self:
new_classification = template._fiscal_classification_get_or_create()[0]
if template.fiscal_classification_id != new_classification:
template.fiscal_classification_id = new_classification
return res

# View Section
Expand All @@ -48,6 +66,9 @@ def _onchange_fiscal_classification_id(self):

@api.model
def get_view(self, view_id=None, view_type="form", **options):
"""Set fiscal_classification_id required on all views.
We don't set the field required by field definition to avoid
incompatibility with other modules, errors on import, etc..."""
result = super().get_view(view_id=view_id, view_type=view_type, **options)
doc = etree.fromstring(result["arch"])
nodes = doc.xpath("//field[@name='fiscal_classification_id']")
Expand All @@ -60,9 +81,11 @@ def get_view(self, view_id=None, view_type="form", **options):
return result

# Custom Section
def _update_vals_fiscal_classification(self, vals):
def _fiscal_classification_update_taxes(self, vals):
"""if fiscal classification is in vals, update vals to set
according purchase and sale taxes"""
FiscalClassification = self.env["account.product.fiscal.classification"]
if vals.get("fiscal_classification_id", False):
if vals.get("fiscal_classification_id"):
# We use sudo to have access to all the taxes, even taxes that belong
# to companies that the user can't access in the current context
classification = FiscalClassification.sudo().browse(
Expand All @@ -74,16 +97,36 @@ def _update_vals_fiscal_classification(self, vals):
"taxes_id": [(6, 0, classification.sale_tax_ids.ids)],
}
)
elif vals.get("supplier_taxes_id") or vals.get("taxes_id"):
raise ValidationError(
_(
"You can not create or write products with"
" 'Customer Taxes' or 'Supplier Taxes'\n."
"Please, use instead the 'Fiscal Classification' field."
)
)
return vals

@api.constrains("categ_id", "fiscal_classification_id")
def _check_rules_fiscal_classification(self):
self.env["account.product.fiscal.rule"].check_product_templates_integrity(self)
def _fiscal_classification_get_or_create(self):
"""get the classification(s) that matches with the fiscal settings
of the current product.
If no configuration is found, create a new one.
This will raise an error, if current user doesn't have the access right
to create one classification."""

self.ensure_one()

FiscalClassification = self.env["account.product.fiscal.classification"]
FiscalClassificationSudo = found_classifications = self.env[
"account.product.fiscal.classification"
].sudo()
all_classifications = FiscalClassificationSudo.search(
[("company_id", "in", [self.company_id.id, False])]
)

for classification in all_classifications:
if sorted(self.supplier_taxes_id.ids) == sorted(
classification.purchase_tax_ids.ids
) and sorted(self.taxes_id.ids) == sorted(classification.sale_tax_ids.ids):
found_classifications |= classification

if len(found_classifications) == 0:
vals = FiscalClassification._prepare_vals_from_taxes(
self.supplier_taxes_id, self.taxes_id
)
_logger.info(f"Creating new Fiscal Classification '{vals['name']}' ...")
return FiscalClassification.create(vals)

return found_classifications
192 changes: 131 additions & 61 deletions account_product_fiscal_classification/tests/test_module.py
Original file line number Diff line number Diff line change
@@ -1,101 +1,115 @@
# Copyright (C) 2014-Today GRAP (http://www.grap.coop)
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from odoo.exceptions import ValidationError
from odoo import Command
from odoo.exceptions import AccessError, ValidationError
from odoo.tests.common import TransactionCase


class Tests(TransactionCase):
def setUp(self):
super().setUp()
self.ProductTemplate = self.env["product.template"]
self.ResCompany = self.env["res.company"]
self.FiscalClassification = self.env["account.product.fiscal.classification"]
self.WizardChange = self.env["wizard.change.fiscal.classification"]

self.main_company = self.env.ref("base.main_company")
self.group_accountant = self.env.ref("account.group_account_manager")
self.group_system = self.env.ref("base.group_system")
self.company_2 = self.env.ref("account_product_fiscal_classification.company_2")
self.user_demo = self.env.ref("base.user_demo")

self.fiscal_classification_A_company_1 = self.env.ref(
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.ProductTemplate = cls.env["product.template"]
cls.ResCompany = cls.env["res.company"]
cls.FiscalClassification = cls.env["account.product.fiscal.classification"]
cls.WizardChange = cls.env["wizard.change.fiscal.classification"]

cls.main_company = cls.env.ref("base.main_company")
cls.group_accountant = cls.env.ref("account.group_account_manager")
cls.group_system = cls.env.ref("base.group_system")
cls.company_2 = cls.env.ref("account_product_fiscal_classification.company_2")

cls.classification_A_company_1 = cls.env.ref(
"account_product_fiscal_classification.fiscal_classification_A_company_1"
)
self.fiscal_classification_B_company_1 = self.env.ref(
cls.classification_B_company_1 = cls.env.ref(
"account_product_fiscal_classification.fiscal_classification_B_company_1"
)
self.fiscal_classification_D_global = self.env.ref(
cls.classification_D_global = cls.env.ref(
"account_product_fiscal_classification.fiscal_classification_D_global"
)
self.product_template_A_company_1 = self.env.ref(
cls.product_template_A_company_1 = cls.env.ref(
"account_product_fiscal_classification.product_template_A_company_1"
)
self.account_tax_purchase_20_company_1 = self.env.ref(
cls.account_tax_purchase_20_company_1 = cls.env.ref(
"account_product_fiscal_classification.account_tax_purchase_20_company_1"
)
self.account_tax_sale_20_company_1 = self.env.ref(
cls.account_tax_sale_5_company_1 = cls.env.ref(
"account_product_fiscal_classification.account_tax_sale_5_company_1"
)
cls.account_tax_sale_20_company_1 = cls.env.ref(
"account_product_fiscal_classification.account_tax_sale_20_company_1"
)
self.account_tax_purchase_7_company_2 = self.env.ref(
cls.account_tax_purchase_7_company_2 = cls.env.ref(
"account_product_fiscal_classification.account_tax_purchase_7_company_2"
)
self.chart_template = self.env.ref(
cls.chart_template = cls.env.ref(
"account_product_fiscal_classification.chart_template"
)
# self.sale_tax_2 = self.env.ref(
# "account_product_fiscal_classification.account_tax_sale_5_company_1"
# )

self.category_all = self.env.ref("product.product_category_all")
self.category_wine = self.env.ref(
cls.category_all = cls.env.ref("product.product_category_all")
cls.category_wine = cls.env.ref(
"account_product_fiscal_classification.category_wine"
)

# # Group to create product
# self.product_group = self.env.ref("account.group_account_manager")
# self.restricted_group = self.env.ref("base.group_system")
cls.initial_classif_count = cls.FiscalClassification.search_count([])

# Test Section
vals = {
"company_id": cls.env.ref("base.main_company").id,
"name": "New User",
"login": "new_user",
"email": "new_user@yourcompany.com",
"groups_id": [Command.set(cls.env.ref("base.group_system").ids)],
}
cls.user_demo = cls.env["res.users"].create(vals)

def _create_product(self, extra_vals, user=False):
if not user:
user = self.env.user
vals = {
"name": "Test Product",
"company_id": self.main_company.id,
"categ_id": self.category_all.id,
}
vals.update(extra_vals)
return self.ProductTemplate.with_user(user).create(vals)

# # Test Section
def test_01_change_classification(self):
"""Test the behaviour when we change Fiscal Classification for
products."""
wizard = self.WizardChange.create(
{
"old_fiscal_classification_id": self.fiscal_classification_A_company_1.id,
"new_fiscal_classification_id": self.fiscal_classification_B_company_1.id,
"old_fiscal_classification_id": self.classification_A_company_1.id,
"new_fiscal_classification_id": self.classification_B_company_1.id,
}
)
wizard.button_change_fiscal_classification()
self.assertEqual(
self.product_template_A_company_1.fiscal_classification_id,
self.fiscal_classification_B_company_1,
self.classification_B_company_1,
"Fiscal Classification change has failed for products via Wizard.",
)

def test_02_create_product(self):
"""Test if creating a product with fiscal classification set correct taxes"""
vals = {
"name": "Product Product Name",
"company_id": self.main_company.id,
"fiscal_classification_id": self.fiscal_classification_D_global.id,
}
newTemplate = self.ProductTemplate.create(vals)
newTemplate = self._create_product(
{"fiscal_classification_id": self.classification_D_global.id}
)
# Test that all taxes are set (in sudo mode)
self.assertEqual(
set(newTemplate.sudo().taxes_id.ids),
set(self.fiscal_classification_D_global.sudo().sale_tax_ids.ids),
set(self.classification_D_global.sudo().sale_tax_ids.ids),
)
self.assertEqual(
set(newTemplate.sudo().supplier_taxes_id.ids),
set(self.fiscal_classification_D_global.sudo().purchase_tax_ids.ids),
set(self.classification_D_global.sudo().purchase_tax_ids.ids),
)

def test_03_update_fiscal_classification(self):
"""Test if changing a Configuration of a Fiscal Classification changes
the product."""
self.fiscal_classification_A_company_1.write(
self.classification_A_company_1.write(
{"sale_tax_ids": [(6, 0, [self.account_tax_sale_20_company_1.id])]}
)
self.assertEqual(
Expand All @@ -107,7 +121,7 @@ def test_03_update_fiscal_classification(self):
def test_05_unlink_fiscal_classification(self):
"""Test if unlinking a Fiscal Classification with products fails."""
with self.assertRaises(ValidationError):
self.fiscal_classification_A_company_1.unlink()
self.classification_A_company_1.unlink()

def test_10_chart_template(self):
"""Test if installing new CoA creates correct classification"""
Expand Down Expand Up @@ -148,29 +162,85 @@ def test_30_rules(self):

# Create a product without rules should success
self._create_product(
self.env.user, self.category_all, self.fiscal_classification_B_company_1
{"fiscal_classification_id": self.classification_B_company_1.id}
)
self._create_product(
self.user_demo, self.category_all, self.fiscal_classification_B_company_1
{"fiscal_classification_id": self.classification_B_company_1.id},
user=self.user_demo,
)

vals = {
"categ_id": self.category_wine.id,
"fiscal_classification_id": self.classification_B_company_1.id,
}
# create a product not respecting rules should succeed with accountant perfil
self._create_product(
self.env.user, self.category_wine, self.fiscal_classification_B_company_1
)
self._create_product(vals)

# create a product not respecting rules should fail without accountant perfil
with self.assertRaises(ValidationError):
self._create_product(
self.user_demo,
self.category_wine,
self.fiscal_classification_B_company_1,
)
self._create_product(vals, user=self.user_demo)

def _create_product(self, user, category, classification):
def test_no_classification_and_find_one(self):
vals = {
"name": "Test Product",
"categ_id": category.id,
"fiscal_classification_id": classification.id,
"taxes_id": self.classification_B_company_1.sale_tax_ids.ids,
"supplier_taxes_id": self.classification_B_company_1.purchase_tax_ids.ids,
}
product = self._create_product(vals, user=self.user_demo)

# no new classification is created
classif_count_after = self.FiscalClassification.search_count([])
self.assertEqual(classif_count_after, self.initial_classif_count)
# product is linked to the correct classification
self.assertEqual(
product.fiscal_classification_id, self.classification_B_company_1
)

def test_no_classification_one_more_tax_and_create_one(self):
"""Create a product with fiscal settings that looks like
classification_B_company_1 but with an additional supplier tax.
"""

vals = {
"taxes_id": self.classification_B_company_1.sale_tax_ids.ids,
"supplier_taxes_id": self.account_tax_purchase_20_company_1.ids,
}

# create a product with fiscal settings that doesn't match
# existing classification should fail with non accountant user
with self.assertRaises(AccessError):
self._create_product(vals, user=self.user_demo)

# create a product with fiscal settings that doesn't match
# existing classification should success with accountant user
product = self._create_product(vals)
self.assertNotEqual(product.fiscal_classification_id, False)
classif_count_after = self.FiscalClassification.search_count([])
self.assertEqual(classif_count_after, self.initial_classif_count + 1)

def test_no_classification_one_less_tax_and_create_one(self):
"""Create a product with fiscal settings that looks like
classification_B_company_1 but with one less tax
"""

vals = {
"taxes_id": self.account_tax_sale_5_company_1.ids,
"supplier_taxes_id": [],
}
self.ProductTemplate.with_user(user).create(vals)
# create a product with fiscal settings that doesn't match
# existing classification should fail with non accountant user
with self.assertRaises(AccessError):
self._create_product(vals, user=self.user_demo)

# create a product with fiscal settings that doesn't match
# existing classification should success with accountant user
product = self._create_product(vals)

self.assertNotEqual(product.fiscal_classification_id, False)
classif_count_after = self.FiscalClassification.search_count([])
self.assertEqual(classif_count_after, self.initial_classif_count + 1)

def test_no_tax_nor_classification(self):
product = self._create_product({"taxes_id": [], "supplier_taxes_id": []})
classif = product.fiscal_classification_id
self.assertEqual(classif.purchase_tax_ids.ids, [])
self.assertEqual(classif.sale_tax_ids.ids, [])

0 comments on commit f2919ce

Please sign in to comment.