Maintainers
+Maintainers
This module is maintained by the OCA.
@@ -458,6 +453,5 @@ diff --git a/product_abc_classification/README.rst b/product_abc_classification/README.rst
index cc67bd9ddd4..02fdb720416 100644
--- a/product_abc_classification/README.rst
+++ b/product_abc_classification/README.rst
@@ -1,7 +1,3 @@
-.. image:: https://odoo-community.org/readme-banner-image
- :target: https://odoo-community.org/get-involved?utm_source=readme
- :alt: Odoo Community Association
-
==========================
Product Abc Classification
==========================
@@ -17,7 +13,7 @@ Product Abc Classification
.. |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/license-AGPL--3-blue.png
+.. |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%2Fproduct--attribute-lightgray.png?logo=github
diff --git a/product_abc_classification/__manifest__.py b/product_abc_classification/__manifest__.py
index e4a4ea074ab..0b7f47ab15b 100644
--- a/product_abc_classification/__manifest__.py
+++ b/product_abc_classification/__manifest__.py
@@ -6,7 +6,7 @@
"name": "Product Abc Classification",
"summary": """
ABC classification for sales and warehouse management""",
- "version": "18.0.1.0.0",
+ "version": "18.0.1.1.0",
"license": "AGPL-3",
"author": "ACSONE SA/NV, ForgeFlow, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/product-attribute",
@@ -18,6 +18,7 @@
"views/product_product.xml",
"views/product_category.xml",
"security/ir.model.access.csv",
+ "security/security.xml",
"data/ir_cron.xml",
],
}
diff --git a/product_abc_classification/migrations/18.0.1.1.0/post-migration.py b/product_abc_classification/migrations/18.0.1.1.0/post-migration.py
new file mode 100644
index 00000000000..df84acdd11a
--- /dev/null
+++ b/product_abc_classification/migrations/18.0.1.1.0/post-migration.py
@@ -0,0 +1,26 @@
+# Copyright 2026 ForgeFlow
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+import logging
+
+_logger = logging.getLogger(__name__)
+
+
+def migrate(cr, version):
+ if not version:
+ return
+ cr.execute(
+ """
+ UPDATE abc_classification_product_level AS lvl
+ SET company_id = tmpl.company_id
+ FROM product_product AS pp,
+ product_template AS tmpl
+ WHERE lvl.product_id = pp.id
+ AND pp.product_tmpl_id = tmpl.id
+ AND tmpl.company_id IS NOT NULL
+ AND lvl.company_id IS NULL;
+ """
+ )
+ _logger.info(
+ "Backfilled company_id on %s abc.classification.product.level rows",
+ cr.rowcount,
+ )
diff --git a/product_abc_classification/models/abc_classification_product_level.py b/product_abc_classification/models/abc_classification_product_level.py
index 505642afb2d..bd6d13b38f0 100644
--- a/product_abc_classification/models/abc_classification_product_level.py
+++ b/product_abc_classification/models/abc_classification_product_level.py
@@ -10,6 +10,7 @@ class AbcClassificationProductLevel(models.Model):
_inherit = "mail.thread"
_description = "Abc Classification Product Level"
_rec_name = "level_id"
+ _check_company_auto = True
manual_level_id = fields.Many2one(
"abc.classification.level",
@@ -43,6 +44,7 @@ class AbcClassificationProductLevel(models.Model):
index=True,
required=True,
ondelete="cascade",
+ check_company=True,
)
product_tmpl_id = fields.Many2one(
"product.template",
@@ -50,11 +52,19 @@ class AbcClassificationProductLevel(models.Model):
index=True,
readonly=True,
)
+ company_id = fields.Many2one(
+ "res.company",
+ compute="_compute_company_id",
+ store=True,
+ readonly=True,
+ index=True,
+ )
# percentage
profile_id = fields.Many2one(
"abc.classification.profile",
string="Profile",
required=True,
+ check_company=True,
)
profile_type = fields.Selection(
related="profile_id.profile_type",
@@ -124,6 +134,13 @@ def _compute_flag(self):
rec.computed_level_id and rec.manual_level_id != rec.computed_level_id
)
+ @api.depends("product_id.company_id", "profile_id.company_id")
+ def _compute_company_id(self):
+ for rec in self:
+ rec.company_id = (
+ rec.profile_id.company_id or rec.product_id.company_id or False
+ )
+
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
diff --git a/product_abc_classification/models/abc_classification_profile.py b/product_abc_classification/models/abc_classification_profile.py
index 996186af797..e88574b20f9 100644
--- a/product_abc_classification/models/abc_classification_profile.py
+++ b/product_abc_classification/models/abc_classification_profile.py
@@ -28,6 +28,10 @@ class AbcClassificationProfile(models.Model):
string="Period on which to compute the classification (Days)",
required=True,
)
+ company_id = fields.Many2one(
+ comodel_name="res.company",
+ string="Company",
+ )
product_variant_ids = fields.Many2many(
comodel_name="product.product",
@@ -47,6 +51,30 @@ class AbcClassificationProfile(models.Model):
_sql_constraints = [("name_uniq", "UNIQUE(name)", "Profile name must be unique")]
+ @api.constrains("company_id", "product_variant_ids")
+ def _check_company_products(self):
+ for profile in self:
+ if not profile.company_id:
+ continue
+ bad = self.env["product.product"].search(
+ [
+ ("id", "in", profile.product_variant_ids.ids),
+ ("company_id", "!=", False),
+ ("company_id", "!=", profile.company_id.id),
+ ]
+ )
+ if bad:
+ raise ValidationError(
+ self.env._(
+ "The ABC Classification Profile %(profile)s is assigned "
+ "to company %(company)s, but the following products "
+ "belong to another company: %(products)s.",
+ profile=profile.display_name,
+ company=profile.company_id.display_name,
+ products=", ".join(bad.mapped("display_name")),
+ )
+ )
+
@api.constrains("level_ids")
def _check_levels(self):
for profile in self:
diff --git a/product_abc_classification/models/product_product.py b/product_abc_classification/models/product_product.py
index 31c79263d73..07196ef15ee 100644
--- a/product_abc_classification/models/product_product.py
+++ b/product_abc_classification/models/product_product.py
@@ -17,6 +17,7 @@ class ProductProduct(models.Model):
column1="product_id",
column2="profile_id",
index=True,
+ check_company=True,
)
abc_classification_profile_updatable_from_category = fields.Boolean(default=True)
diff --git a/product_abc_classification/models/product_template.py b/product_abc_classification/models/product_template.py
index 48c01bbc47d..2f73ca74e40 100644
--- a/product_abc_classification/models/product_template.py
+++ b/product_abc_classification/models/product_template.py
@@ -13,6 +13,7 @@ class ProductTemplate(models.Model):
compute="_compute_abc_classification_profile_ids",
inverse="_inverse_abc_classification_profile_ids",
store=True,
+ check_company=True,
)
abc_classification_product_level_ids = fields.One2many(
"abc.classification.product.level",
diff --git a/product_abc_classification/security/security.xml b/product_abc_classification/security/security.xml
new file mode 100644
index 00000000000..9663d5b74df
--- /dev/null
+++ b/product_abc_classification/security/security.xml
@@ -0,0 +1,19 @@
+
+
This modules provides the bases to build ABC analysis (or ABC classification) addons. These classification are used by inventory management teams to help identify the most important products in their @@ -400,7 +395,7 @@
To use this module, you need to:
#. Go to Inventory menu, then to Configuration/Products/ABC Classification Profile and create a profile with levels, knowing that @@ -413,7 +408,7 @@
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 @@ -421,16 +416,16 @@
Do not contact contributors directly about support or help with technical issues.
The migration of this module from 17.0 to 18.0 was financially supported by Camptocamp