Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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
116 changes: 116 additions & 0 deletions product_attribute_custom_value_variant/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
========================================
Create product variant from custom value
========================================

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

.. |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/18.0/product_attribute_custom_value_variant
: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-18-0/product-attribute-18-0-product_attribute_custom_value_variant
: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=18.0
:alt: Try me on Runboat

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

Create variants when configured custom attributes are used.

The created variants are useful to retrieve the stock quantity or for
purchase.

**Table of contents**

.. contents::
:local:

Configuration
=============

In any custom attribute value, enable Create custom variant.

Usage
=====

When a product template is added to a sale order line, the configured
custom values become real attribute values. Here is an example:

1. Create an attribute "Length (cm)"
2. Add some values to the attribute:

- 5
- 10
- Custom

3. For the "Custom" value, enable "Free text" and "Create custom
variant" (added by this module)
4. Create a product template "Glass" having the created attribute and
all its values
5. Create a sale order that includes the product template
6. Set the value 42 for the attribute "Length (cm)"

With the above steps, this module creates:

- | A new attribute value "42" for the attribute "Length (cm)"
| This attribute value is not linked to the product template, so it
does not appear as a choice when the template is sold.

- A variant for the template, linked to the created attribute value

The created variant can then be used to retrieve its stock quantity or
to purchase that exact product variant that had been sold.

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_custom_value_variant%0Aversion:%2018.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
-------

* Aion Tech

Contributors
------------

- Simone Rubino - Aion Tech
- Denis Roussel [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/18.0/product_attribute_custom_value_variant>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
3 changes: 3 additions & 0 deletions product_attribute_custom_value_variant/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from . import models
18 changes: 18 additions & 0 deletions product_attribute_custom_value_variant/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright 2025 Simone Rubino - Aion Tech
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

{
"name": "Create product variant from custom value",
"summary": "When a custom value is assigned, create a variant instead.",
"version": "18.0.1.0.0",
"website": "https://github.com/OCA/product-attribute",
"author": "Aion Tech, Odoo Community Association (OCA)",
"license": "AGPL-3",
"category": "Sales/Sales",
"depends": [
"product",
],
"data": [
"views/product_attribute_value_views.xml",
],
}
6 changes: 6 additions & 0 deletions product_attribute_custom_value_variant/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from . import product_attribute_value
from . import product_attribute_custom_value
from . import product_attribute
from . import product_product
35 changes: 35 additions & 0 deletions product_attribute_custom_value_variant/models/product_attribute.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Copyright 2025 Simone Rubino - Aion Tech
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import models


class ProductAttribute(models.Model):
_inherit = "product.attribute"

def _prepare_variant_custom_attribute_value(self, custom_value):
self.ensure_one()
return {
"attribute_id": self.id,
"name": custom_value,
}

def _get_variant_custom_attribute_value(self, custom_value):
self.ensure_one()
new_attribute_value = self.env["product.attribute.value"].search(
[
(
"attribute_id",
"=",
self.id,
),
("name", "=", custom_value),
],
limit=1,
)
if not new_attribute_value:
new_attribute_value = self.env["product.attribute.value"].create(
self._prepare_variant_custom_attribute_value(custom_value)
)

return new_attribute_value
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Copyright 2025 Simone Rubino - Aion Tech
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import models

from odoo.addons.product.models.product_attribute_custom_value import (
ProductAttributeCustomValue,
)
from odoo.addons.product.models.product_attribute_value import ProductAttributeValue


class ProductAttribute(models.Model):
_inherit = "product.attribute.custom.value"

def _get_new_and_unlink_custom_attribute(
self,
) -> tuple[ProductAttributeValue, ProductAttributeCustomValue]:
"""
Retrieve the new custom attribute from custom value and
the one to remove
"""
values_to_unlink = self.env["product.attribute.custom.value"].browse()
new_attribute_values = self.env["product.attribute.value"].browse()
# Create new attribute values for each "Create custom variant" custom value
for custom_attribute_value in self:
template_attribute_value = (
custom_attribute_value.custom_product_template_attribute_value_id
)
attribute_value = template_attribute_value.product_attribute_value_id
if attribute_value.create_custom_variant:
attribute = template_attribute_value.attribute_id
new_attribute_value = attribute._get_variant_custom_attribute_value(
custom_attribute_value.custom_value,
)
new_attribute_values |= new_attribute_value
template_attribute_value.attribute_line_id.value_ids |= (
new_attribute_value
)

values_to_unlink |= custom_attribute_value
return new_attribute_values, values_to_unlink
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright 2025 Simone Rubino - Aion Tech
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

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


class ProductAttributeValue(models.Model):
_inherit = "product.attribute.value"

create_custom_variant = fields.Boolean(
string="Create custom variant",
help="When this custom attribute is used, \
a new value and variant will be generated.",
)

@api.constrains(
"create_custom_variant",
"is_custom",
)
def _constrain_create_custom_variant(self):
for value in self:
if value.create_custom_variant and not value.is_custom:
raise ValidationError(
_("'Create custom variant' can only be set on custom values")
)
65 changes: 65 additions & 0 deletions product_attribute_custom_value_variant/models/product_product.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Copyright 2025 Simone Rubino - Aion Tech
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from collections.abc import Generator
from contextlib import contextmanager

from odoo import models

from odoo.addons.product.models.product_product import ProductProduct as ProductBase


class ProductProduct(models.Model):
_inherit = "product.product"

@contextmanager
def _get_attribute_custom_value_variant(
self, custom_values
) -> Generator[ProductBase | None]:
"""
This will retrieve the new created variant as context manager return.

At the end, it will:

- Remove the attributes
- Remove the
"""

new_attribute_values, custom_values_to_unlink = (
custom_values._get_new_and_unlink_custom_attribute()
)
# If new attribute values have been created,
# use them to create a new variant and set it in the line
attribute_lines = self.product_tmpl_id.attribute_line_ids
custom_combination = attribute_lines.product_template_value_ids.filtered(
lambda ptav, new=new_attribute_values: ptav.product_attribute_value_id
in new
)
if custom_combination:
new_variant = self._create_custom_attribute_combination(custom_combination)
yield new_variant
# The new attribute values must not be available for new models
for attribute_line in attribute_lines:
attribute_line.with_context(
no_remove_custom_variants=new_variant.ids,
).value_ids -= attribute_line.value_ids & new_attribute_values
custom_values_to_unlink.unlink()

def _create_custom_attribute_combination(self, custom_combination):
self.ensure_one()
variant_combination = self.product_template_attribute_value_ids
new_variant_combination = (
variant_combination.filtered(
lambda ptav: not ptav.product_attribute_value_id.create_custom_variant
)
| custom_combination
)
new_variant = self.product_tmpl_id._create_product_variant(
new_variant_combination
)
return new_variant

def _unlink_or_archive(self, check_access=True):
custom_variants_to_keep_ids = self.env.context.get("no_remove_custom_variants")
return super(
ProductProduct, self - self.browse(custom_variants_to_keep_ids)
)._unlink_or_archive(check_access=check_access)
3 changes: 3 additions & 0 deletions product_attribute_custom_value_variant/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"
1 change: 1 addition & 0 deletions product_attribute_custom_value_variant/readme/CONFIGURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
In any custom attribute value, enable Create custom variant.
2 changes: 2 additions & 0 deletions product_attribute_custom_value_variant/readme/CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Simone Rubino - Aion Tech
- Denis Roussel <[email protected]>
4 changes: 4 additions & 0 deletions product_attribute_custom_value_variant/readme/DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Create variants when configured custom attributes are used.

The created variants are useful to retrieve the stock quantity or for
purchase.
25 changes: 25 additions & 0 deletions product_attribute_custom_value_variant/readme/USAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
When a product template is added to a sale order line, the configured
custom values become real attribute values. Here is an example:

1. Create an attribute "Length (cm)"
2. Add some values to the attribute:
- 5
- 10
- Custom
3. For the "Custom" value, enable "Free text" and "Create custom variant"
(added by this module)
4. Create a product template "Glass" having the created attribute and
all its values
5. Create a sale order that includes the product template
6. Set the value 42 for the attribute "Length (cm)"

With the above steps, this module creates:

- A new attribute value "42" for the attribute "Length (cm)"
This attribute value is not linked to the product template, so it does
not appear as a choice when the template is sold.

- A variant for the template, linked to the created attribute value

The created variant can then be used to retrieve its stock quantity or
to purchase that exact product variant that had been sold.
Loading