Skip to content
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

[14.0] [ADD] product_attribute_variant_rules #1435

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions product_attribute_variant_rules/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
===============================
Product Attribute Variant Rules
===============================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:3557e19d06bc602378a8840ca208031e48ff3106d82706f0ffe66904d612c18b
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |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%2Fproduct--attribute-lightgray.png?logo=github
:target: https://github.com/OCA/product-attribute/tree/14.0/product_attribute_variant_rules
: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-14-0/product-attribute-14-0-product_attribute_variant_rules
: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=14.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

This module adds a more powerful way to describe your product attributes combinations
than the default exclusions.

It allows to write rules like:

* All products with blue or green color in XL size will appear only with a V neck collar
and a short sleeve.
* All L size products will never appear with a sailor collar.

The rules are split between a precondition a type and a postcondition.

Different attributes are ANDed and same attributes are ORed.

For instance the rule::

All products with blue or green color in XL size will appear only with a V neck collar and a short sleeve.


Will be written as::

Precondition: (color: blue), (color: green), (size: XL)
Type: Only With
Postcondition: (collar: V neck), (sleeve: short)


**Table of contents**

.. contents::
:local:

Usage
=====

After saving your product click on the Use Attribute Rules checkbox in the product
variants tab.

Then add your different rules, specifying optionally some rule preconditions, a type of
inclusion/exclusion and the rule postconditions.

When you save your product the variant will be recomputed.

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_attribute_variant_rules%0Aversion:%2014.0%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
~~~~~~~

* Akretion

Contributors
~~~~~~~~~~~~

* Florian Mounier <[email protected]>

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/product-attribute <https://github.com/OCA/product-attribute/tree/14.0/product_attribute_variant_rules>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
1 change: 1 addition & 0 deletions product_attribute_variant_rules/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
16 changes: 16 additions & 0 deletions product_attribute_variant_rules/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copyright 2023 Akretion
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

{
"name": "Product Attribute Variant Rules",
"summary": "Add better rules for product variants generation from attributes",
"version": "14.0.1.0.0",
"license": "AGPL-3",
"author": "Akretion,Odoo Community Association (OCA)",
"depends": ["product"],
"data": [
"security/ir.model.access.csv",
"views/product_template_views.xml",
],
"website": "https://github.com/OCA/product-attribute",
}
2 changes: 2 additions & 0 deletions product_attribute_variant_rules/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import product_attribute_rule
from . import product_template
152 changes: 152 additions & 0 deletions product_attribute_variant_rules/models/product_attribute_rule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# Copyright 2023 Akretion (https://www.akretion.com).
# @author Florian Mounier <[email protected]>
from collections import defaultdict

from odoo import api, fields, models


class ProductAttributeRule(models.Model):
_name = "product.attribute.rule"
_description = "Product Attribute Rule"

product_tmpl_id = fields.Many2one(
comodel_name="product.template",
string="Product Template",
required=True,
ondelete="cascade",
)

product_attribute_value_precondition_ids = fields.Many2many(
comodel_name="product.attribute.value",
relation="product_attribute_rule_precondition_rel",
string="Rule preconditions",
help="This rule will only be applied if all the preconditions are met.\n"
"The attribute values are ANDed together except if they are from the "
"same attribute in which case they are ORed.\n"
"If empty, the rule will always be applied.",
)

type = fields.Selection(
[
("only", "Only With"),
("never", "Never With"),
],
string="Type",
default="only",
required=True,
)

product_attribute_value_postcondition_ids = fields.Many2many(
comodel_name="product.attribute.value",
relation="product_attribute_rule_postcondition_rel",
string="Rule postconditions",
help="The product variant will exist only if these conditions are met "
"if the precondition is met.\n"
"The attribute values are ANDed together except if they are from the "
"same attribute in which case they are ORed.",
required=True,
)

available_precondition_attribute_ids = fields.Many2many(
comodel_name="product.attribute",
compute="_compute_available_precondition_attribute_ids",
string="Available preconditions",
)

available_postcondition_attribute_ids = fields.Many2many(
comodel_name="product.attribute",
compute="_compute_available_postcondition_attribute_ids",
string="Available postconditions",
)

@api.depends(
"product_tmpl_id.attribute_line_ids",
"product_attribute_value_postcondition_ids",
)
def _compute_available_precondition_attribute_ids(self):
"""
Compute the available preconditions.
"""
for rule in self:
rule.available_precondition_attribute_ids = (
rule.product_tmpl_id.attribute_line_ids.mapped("attribute_id")
- rule.product_attribute_value_postcondition_ids.mapped("attribute_id")
)

@api.depends(
"product_tmpl_id.attribute_line_ids", "product_attribute_value_precondition_ids"
)
def _compute_available_postcondition_attribute_ids(self):
"""
Compute the available postconditions.
"""
for rule in self:
rule.available_postcondition_attribute_ids = (
rule.product_tmpl_id.attribute_line_ids.mapped("attribute_id")
- rule.product_attribute_value_precondition_ids.mapped("attribute_id")
)

def _is_combination_possible(self, combination):
"""
Check if the combination is possible with the rules defined on the
product template.
"""
# Check if the combination matches the preconditions
if not self._combination_matches_conditions(
combination, self.product_attribute_value_precondition_ids
):
# If the combination does not match the preconditions,
# the rule is not applied
return True

# Check if the combination matches the postconditions
match = self._combination_matches_conditions(
combination, self.product_attribute_value_postcondition_ids
)

if self.type == "only" and match:
# Both conditions are met in only, the combination is possible
return True
elif self.type == "never" and not match:
# Precondition is met but postcondition is not met in never,
# the combination is possible
return True

# The combination is not possible
return False

def _combination_matches_conditions(self, combination, conditions):
"""
Check if the combination matches the given conditions.
"""
# If there is no condition, the combination matches
# (only possible for the preconditions)
if not conditions:
return True

# Check if the combination matches the preconditions ANDed between
# different attributes
for attribute, attribute_values in self._aggregate_conditions(
paradoxxxzero marked this conversation as resolved.
Show resolved Hide resolved
conditions
).items():
if (
combination.filtered(
lambda value: value.attribute_id == attribute
).product_attribute_value_id
not in attribute_values # The OR between the same attribute values
):
# The combination does not match a precondition
return False

# The combination matches all preconditions
return True

def _aggregate_conditions(self, conditions):
"""
Group the attribute values by attribute.
"""
aggregated_conditions = defaultdict(set)
for condition in conditions:
aggregated_conditions[condition.attribute_id].add(condition)

return aggregated_conditions
72 changes: 72 additions & 0 deletions product_attribute_variant_rules/models/product_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Copyright 2023 Akretion (https://www.akretion.com).
# @author Florian Mounier <[email protected]>

from odoo import api, fields, models


class ProductTemplate(models.Model):
_inherit = "product.template"

product_attribute_rule_ids = fields.One2many(
comodel_name="product.attribute.rule",
inverse_name="product_tmpl_id",
string="Product Attribute Rules",
)

use_attribute_rules = fields.Boolean(
string="Use Attribute Rules",
help="If checked, the product variants will be generated based on the rules "
"defined below.",
)

product_attribute_value_ids = fields.Many2many(
string="Technical Attributes",
comodel_name="product.attribute.value",
compute="_compute_product_attribute_value_ids",
)

@api.depends("attribute_line_ids.value_ids")
def _compute_product_attribute_value_ids(self):
for template in self:
template.product_attribute_value_ids = template.mapped(
"attribute_line_ids.value_ids"
)

def _is_combination_possible_by_config(self, combination, ignore_no_variant=False):
rv = super()._is_combination_possible_by_config(
combination, ignore_no_variant=ignore_no_variant
)
if (
not rv # Combination is not possible
or not self.use_attribute_rules # Rules are not enabled
or not self.product_attribute_rule_ids # No rules defined
):
return rv

# Check if the combination matches the rules
return self._is_combination_possible_with_rules(combination)

def _is_combination_possible_with_rules(self, combination):
"""
Check if the combination is possible with the rules defined on the product template.
"""
# Rules are ANDed together
for rule in self.product_attribute_rule_ids:
if not rule._is_combination_possible(combination):
return False
return True

def write(self, vals):
res = super(ProductTemplate, self).write(vals)
paradoxxxzero marked this conversation as resolved.
Show resolved Hide resolved

# Recreate variants if the rules have changed
empty = vals.get("active") and len(self.product_variant_ids) == 0
if "attribute_line_ids" in vals or empty:
# Already done in super
return res

if "use_attribute_rules" in vals or "product_attribute_rule_ids" in vals:
# Recreate variants
self._create_variant_ids()

return res
1 change: 1 addition & 0 deletions product_attribute_variant_rules/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Florian Mounier <[email protected]>
23 changes: 23 additions & 0 deletions product_attribute_variant_rules/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
This module adds a more powerful way to describe your product attributes combinations
than the default exclusions.

It allows to write rules like:

* All products with blue or green color in XL size will appear only with a V neck collar
and a short sleeve.
* All L size products will never appear with a sailor collar.

The rules are split between a precondition a type and a postcondition.

Different attributes are ANDed and same attributes are ORed.

For instance the rule::

All products with blue or green color in XL size will appear only with a V neck collar and a short sleeve.


Will be written as::

Precondition: (color: blue), (color: green), (size: XL)
Type: Only With
Postcondition: (collar: V neck), (sleeve: short)
7 changes: 7 additions & 0 deletions product_attribute_variant_rules/readme/USAGE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
After saving your product click on the Use Attribute Rules checkbox in the product
variants tab.

Then add your different rules, specifying optionally some rule preconditions, a type of
inclusion/exclusion and the rule postconditions.

When you save your product the variant will be recomputed.
3 changes: 3 additions & 0 deletions product_attribute_variant_rules/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_product_product_attribute_rule,product.template.attribute rule,model_product_attribute_rule,base.group_user,1,0,0,0
access_product_product_attribute_rule_manager,product.template.attribute rule manager,model_product_attribute_rule,base.group_system,1,1,1,1
Loading