From 61869b16739d015a79fdbedfefda9e7eed717bda Mon Sep 17 00:00:00 2001 From: eLBati Date: Wed, 21 Oct 2026 16:22:34 +0200 Subject: [PATCH] [OU-FIX] product: archive duplicated variants Before odoo/odoo@088edee2ce71 ("[FIX] product: do not create duplicate variants", odoo/odoo#35013), Odoo 12 reactivated archived variants whose combination already had an active variant (sell the variants, remove an attribute from the template, add it again). That fix prevents new duplicates but doesn't clean the existing ones, so v12 databases can contain several active variants for the same combination, and the creation of the product_product_combination_unique index on v13 fails. Variants without attribute values get an empty combination_indices, as Odoo computes it, so they are also covered. For each template and combination, the variant with the lowest ID is kept active and the others are archived: unlike #2513, no records are merged or deleted. Assisted-by: Claude Opus 5.5 --- .../migrations/13.0.1.2/pre-migration.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/addons/product/migrations/13.0.1.2/pre-migration.py b/addons/product/migrations/13.0.1.2/pre-migration.py index 39b155067afb..b9fa84daa7c1 100644 --- a/addons/product/migrations/13.0.1.2/pre-migration.py +++ b/addons/product/migrations/13.0.1.2/pre-migration.py @@ -190,6 +190,37 @@ def calculate_product_product_combination_indices(env): ) grouped_pvc WHERE grouped_pvc.product_product_id = pp.id""", ) + # variants without attribute values: Odoo computes an empty string + openupgrade.logged_query( + env.cr, """ + UPDATE product_product pp + SET combination_indices = '' + WHERE NOT EXISTS ( + SELECT 1 FROM product_attribute_value_product_product_rel pavppr + WHERE pavppr.product_product_id = pp.id)""", + ) + + +def archive_duplicated_variants(env): + """Before odoo/odoo@088edee2ce71, v12 could reactivate an archived variant + whose combination already had an active variant (remove an attribute from + the template and add it again). On v13 there can be only one active + variant per combination (product_product_combination_unique), so the + extra ones are archived, keeping the one with the lowest ID. + """ + openupgrade.logged_query( + env.cr, """ + UPDATE product_product pp + SET active = False + FROM ( + SELECT id, ROW_NUMBER() OVER ( + PARTITION BY product_tmpl_id, combination_indices + ORDER BY id) AS rn + FROM product_product + WHERE active AND combination_indices IS NOT NULL + ) dup + WHERE dup.id = pp.id AND dup.rn > 1""", + ) def create_and_fill_product_variant_combination(env): @@ -270,4 +301,5 @@ def migrate(env, version): insert_missing_product_template_attribute_value(env) fill_product_template_attribute_value__attribute_id_related(env) calculate_product_product_combination_indices(env) + archive_duplicated_variants(env) create_and_fill_product_variant_combination(env)