-
-
Notifications
You must be signed in to change notification settings - Fork 820
[19.0][ADD] product_class: classes to group product attributes #2273
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Ricardoalso
wants to merge
1
commit into
OCA:19.0
Choose a base branch
from
camptocamp:product_class
base: 19.0
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,277 @@ | ||
| .. image:: https://odoo-community.org/readme-banner-image | ||
| :target: https://odoo-community.org/get-involved?utm_source=readme | ||
| :alt: Odoo Community Association | ||
|
|
||
| ============= | ||
| Product Class | ||
| ============= | ||
|
|
||
| .. | ||
| !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | ||
| !! This file is generated by oca-gen-addon-readme !! | ||
| !! changes will be overwritten. !! | ||
| !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | ||
| !! source digest: sha256:86503509d193aed6557f152cd812109c5ebc9c92c3d83812aa48459f5d0208f2 | ||
| !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | ||
|
|
||
| .. |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 | ||
| :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 | ||
| :target: https://github.com/OCA/product-attribute/tree//product_class | ||
| :alt: OCA/product-attribute | ||
| .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png | ||
| :target: https://translation.odoo-community.org/projects/product-attribute-/product-attribute--product_class | ||
| :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/product-attribute&target_branch= | ||
| :alt: Try me on Runboat | ||
|
|
||
| |badge1| |badge2| |badge3| |badge4| |badge5| | ||
|
|
||
| Product Class | ||
| ------------- | ||
|
|
||
| This module introduces **Product Classes** for standardizing product | ||
| setup and attribute management. It allows you to: | ||
|
|
||
| 1. **Define Product Classes** — Group related products into classes | ||
| (e.g., "Furniture", "Electronics") | ||
| 2. **Constrain Attributes per Class** — Specify which attributes are | ||
| allowed for each class | ||
| 3. **Mark Required Attributes** — Designate certain attributes as | ||
| mandatory for products in that class | ||
| 4. **Enforce Validation** — Prevent products from using attributes | ||
| outside their class or missing required attributes | ||
|
|
||
| Key Features | ||
| ~~~~~~~~~~~~ | ||
|
|
||
| - **Bridge Model (``product.class.attribute.line``)** — Manages the | ||
| relationship between classes and attributes, storing both allowed | ||
| attributes and a ``required`` flag | ||
| - **Strict Validation** — Products assigned to a class must: | ||
|
|
||
| - Use only attributes defined in that class | ||
| - Provide values for all required attributes | ||
|
|
||
| - **UI Enforcement** — Attribute selection in product forms is | ||
| restricted by the class domain filter | ||
| - **Management Views** — Full CRUD interface for product classes | ||
| (Inventory > Configuration > Product Classes, Sales > Configuration > | ||
| Product Classes) | ||
|
|
||
| Technical Architecture | ||
| ~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| - ``product.class`` — Main product classification model | ||
| - ``product.class.attribute.line`` — Bridge model linking classes to | ||
| attributes with a ``required`` flag | ||
| - ``product.attribute`` (inherited) — Extended with reverse one-to-many | ||
| to track which classes use it | ||
| - ``product.template`` (inherited) — Added class validation and computed | ||
| required-attribute tracking | ||
|
|
||
| **Table of contents** | ||
|
|
||
| .. contents:: | ||
| :local: | ||
|
|
||
| Usage | ||
| ===== | ||
|
|
||
| Usage | ||
| ----- | ||
|
|
||
| 1. Create a Product Class | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| **Navigation:** Inventory → Configuration → Product Classes (or Sales → | ||
| Configuration → Product Classes) | ||
|
|
||
| 1. Click **Create** button | ||
|
|
||
| 2. Enter a **Product Class Name** (e.g., "Chairs") | ||
|
|
||
| 3. In the **Attributes** section: | ||
|
|
||
| - Click **Add a line** | ||
| - Select an **Attribute** from the dropdown | ||
| - Check **Required** if products of this class must define this | ||
| attribute | ||
| - Repeat to add more attributes | ||
|
|
||
| 4. Click **Save** | ||
|
|
||
| **Example: "Furniture" Class** | ||
|
|
||
| ========= ======== ================================ | ||
| Attribute Required Notes | ||
| ========= ======== ================================ | ||
| Color ✓ All furniture must have a color | ||
| Size ✓ Sizes vary by product | ||
| Material Optional (not all items specify) | ||
| Finish Optional polish/coating | ||
| ========= ======== ================================ | ||
|
|
||
| 2. Assign a Product to a Class | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| **Navigation:** Inventory → Products → Products | ||
|
|
||
| 1. Create or edit a product | ||
| 2. Find the **Product Class** field in the Attributes & Variants tab | ||
| (added by this module) | ||
| 3. Select the class (e.g., "Furniture") | ||
| 4. The form will now restrict **Attributes** to only those allowed by | ||
| the class | ||
|
|
||
| 3. Add Attribute Lines to a Classed Product | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| Once a class is selected, you can add attribute lines: | ||
|
|
||
| 1. Go to the **Attributes & Variants** tab | ||
| 2. In **Attribute Lines**, click **Add a line** | ||
| 3. The **Attribute** field dropdown is now filtered to show only | ||
| class-allowed attributes | ||
| 4. Select an attribute and provide values | ||
| 5. **Validation will fail if:** | ||
|
|
||
| - You select an attribute not in the class → Error: "has attribute | ||
| lines that do not belong" | ||
| - You leave out a required attribute → Error: "is missing required | ||
| attributes" | ||
|
|
||
| 4. Remove an Attribute from a Class | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| **Scenario:** Your "Furniture" class allowed "Color" but wants to retire | ||
| it. | ||
|
|
||
| 1. Navigate to the class | ||
| 2. In **Attributes**, find the "Color" line and delete it | ||
| 3. **Odoo will raise an error if products in this class still use | ||
| Color** | ||
|
|
||
| - Fix: Remove Color from all products in the class first, then retry | ||
| the class update | ||
|
|
||
| Workflow Example | ||
| ~~~~~~~~~~~~~~~~ | ||
|
|
||
| **Step 1: Set Up "Chairs" Class** | ||
|
|
||
| - Color (required) | ||
| - Size (required) | ||
| - Leg Material (optional) | ||
|
|
||
| **Step 2: Create a "Wooden Chair" Product** | ||
|
|
||
| - Assign class: "Chairs" | ||
| - Add attribute "Color" = Red (satisfies required) | ||
| - Add attribute "Size" = Large (satisfies required) | ||
| - Add attribute "Leg Material" = Oak (optional, still valid) | ||
| - **Save** ✓ Success | ||
|
|
||
| **Step 3: Try Invalid Assignment** | ||
|
|
||
| - Assign class: "Chairs" | ||
| - Add attribute "Color" = Blue (satisfies required) | ||
| - **Try to save without Size** → Error: "is missing required attributes | ||
| for the selected class 'Chairs': Size" | ||
| - Add Size = Medium | ||
| - **Save** ✓ Success | ||
|
|
||
| Class Attribute Line Model | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| The bridge model ``product.class.attribute.line`` stores: | ||
|
|
||
| ============ ======== ======================================== | ||
| Field Type Purpose | ||
| ============ ======== ======================================== | ||
| class_id Many2one Product class (required, cascade delete) | ||
| attribute_id Many2one Product attribute (required) | ||
| required Boolean If true, products must define it | ||
| ============ ======== ======================================== | ||
|
|
||
| **Constraint:** A class cannot configure the same attribute twice | ||
| (unique on class_id + attribute_id). | ||
|
|
||
| Constraints & Validations | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| 1. **Class Constraint** (``_check_attribute_ids_used_by_products``) | ||
|
|
||
| - Prevents removing an attribute from a class if products still use | ||
| it | ||
| - Error: "Cannot remove attributes used in products assigned to | ||
| class..." | ||
|
|
||
| 2. **Product Constraint** (``_check_class_attributes``) | ||
|
|
||
| - Ensures all attribute lines belong to the class | ||
| - Ensures all required attributes are defined | ||
| - Error: "Product '...' has attribute lines that do not belong to the | ||
| selected class '...'" | ||
| - Error: "Product '...' is missing required attributes for the | ||
| selected class '...'" | ||
|
|
||
| Advanced: Access Control | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| - **User (base.group_user):** Can create/read/write product classes and | ||
| their attributes | ||
| - **System/Admin (base.group_system):** Full CRUD including delete | ||
|
|
||
| Product class attribute lines follow the same access rules. | ||
|
|
||
| Bug Tracker | ||
| =========== | ||
|
|
||
| Bugs are tracked on `GitHub Issues <https://github.com/OCA/product-attribute/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 <https://github.com/OCA/product-attribute/issues/new?body=module:%20product_class%0Aversion:%20%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_. | ||
|
|
||
| Do not contact contributors directly about support or help with technical issues. | ||
|
|
||
| Credits | ||
| ======= | ||
|
|
||
| Authors | ||
| ------- | ||
|
|
||
| * Camptocamp | ||
|
|
||
| Maintainers | ||
| ----------- | ||
|
|
||
| This module is maintained by the OCA. | ||
|
|
||
| .. image:: https://odoo-community.org/logo.png | ||
| :alt: Odoo Community Association | ||
| :target: https://odoo-community.org | ||
|
|
||
| OCA, or the Odoo Community Association, is a nonprofit organization whose | ||
| mission is to support the collaborative development of Odoo features and | ||
| promote its widespread use. | ||
|
|
||
| .. |maintainer-Ricardoalso| image:: https://github.com/Ricardoalso.png?size=40px | ||
| :target: https://github.com/Ricardoalso | ||
| :alt: Ricardoalso | ||
| .. |maintainer-ivantodorovich| image:: https://github.com/ivantodorovich.png?size=40px | ||
| :target: https://github.com/ivantodorovich | ||
| :alt: ivantodorovich | ||
|
|
||
| Current `maintainers <https://odoo-community.org/page/maintainer-role>`__: | ||
|
|
||
| |maintainer-Ricardoalso| |maintainer-ivantodorovich| | ||
|
|
||
| This module is part of the `OCA/product-attribute <https://github.com/OCA/product-attribute/tree//product_class>`_ project on GitHub. | ||
|
|
||
| You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| from . import models |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| { | ||
| "name": "Product Class", | ||
| "version": "19.0.1.0.0", | ||
| "summary": "Product classification and attribute constraints", | ||
| "author": "Camptocamp, Odoo Community Association (OCA)", | ||
| "website": "https://github.com/OCA/product-attribute", | ||
| "license": "AGPL-3", | ||
| "category": "Product", | ||
| "depends": ["product", "stock", "sale"], | ||
| "data": [ | ||
| "security/ir.model.access.csv", | ||
| "views/product_class_views.xml", | ||
| "views/product_attribute_views.xml", | ||
| "views/product_template_views.xml", | ||
| ], | ||
| "installable": True, | ||
| "maintainers": ["Ricardoalso", "ivantodorovich"], | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| from . import product_class_attribute_line | ||
| from . import product_attribute | ||
| from . import product_class | ||
| from . import product_template |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| # Copyright 2026 Camptocamp SA | ||
| # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) | ||
| from odoo import api, fields, models | ||
|
Ricardoalso marked this conversation as resolved.
|
||
|
|
||
|
|
||
| class ProductAttribute(models.Model): | ||
| _inherit = "product.attribute" | ||
|
|
||
| classes_count = fields.Integer( | ||
| string="Product Classes Count", | ||
| compute="_compute_classes_count", | ||
| ) | ||
|
|
||
| class_attribute_line_ids = fields.One2many( | ||
| comodel_name="product.class.attribute.line", | ||
| inverse_name="attribute_id", | ||
| string="Class Attribute Lines", | ||
|
Ricardoalso marked this conversation as resolved.
|
||
| help="Product classes that include this attribute.", | ||
| ) | ||
|
Ricardoalso marked this conversation as resolved.
|
||
|
|
||
| @api.depends("class_attribute_line_ids.class_id") | ||
| def _compute_classes_count(self): | ||
| for attribute in self: | ||
| attribute.classes_count = len(attribute.class_attribute_line_ids.class_id) | ||
|
|
||
| def action_open_product_classes(self): | ||
| self.ensure_one() | ||
| action = self.env["ir.actions.actions"]._for_xml_id( | ||
| "product_class.product_class_action" | ||
| ) | ||
| action["domain"] = [("id", "in", self.class_attribute_line_ids.class_id.ids)] | ||
| return action | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| # Copyright 2026 Camptocamp SA | ||
| # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) | ||
| from odoo import api, fields, models | ||
|
Ricardoalso marked this conversation as resolved.
|
||
| from odoo.exceptions import ValidationError | ||
|
|
||
|
|
||
| class ProductClass(models.Model): | ||
| _name = "product.class" | ||
| _description = "Product Class" | ||
| _order = "name" | ||
|
|
||
| _name_uniq = models.Constraint( | ||
| "unique(name)", | ||
| "A product class with this name already exists.", | ||
| ) | ||
|
|
||
| name = fields.Char(translate=True) | ||
| active = fields.Boolean(default=True) | ||
| attribute_line_ids = fields.One2many( | ||
| comodel_name="product.class.attribute.line", | ||
| inverse_name="class_id", | ||
| string="Attributes", | ||
| help="Allowed attributes for products of this" | ||
| "class and whether they are required.", | ||
| ) | ||
|
|
||
| @api.constrains("attribute_line_ids") | ||
| def _check_attribute_ids_used_by_products(self): | ||
| for product_class in self: | ||
| class_attribute_ids = product_class.attribute_line_ids.attribute_id.ids | ||
| invalid_lines = self.env["product.template.attribute.line"].search( | ||
| [ | ||
| ("product_tmpl_id.class_id", "=", product_class.id), | ||
| ("attribute_id", "not in", class_attribute_ids), | ||
| ] | ||
| ) | ||
| if not invalid_lines: | ||
| continue | ||
|
|
||
| invalid_names = ", ".join( | ||
| sorted(set(invalid_lines.mapped("attribute_id.display_name"))) | ||
| ) | ||
| raise ValidationError( | ||
| self.env._( | ||
| "Cannot remove attributes used in products assigned to class " | ||
| "'%(product_class)s': %(attrs)s. Please remove these attributes " | ||
| "or change the product class.", | ||
| product_class=product_class.name, | ||
| attrs=invalid_names, | ||
| ) | ||
| ) | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.