From a3fa1ced16bd557fa2c5bdefb54bfe248da26102 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Wed, 1 Apr 2026 17:42:01 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20group=20Odoo=20products=20by=20template?= =?UTF-8?q?=20=E2=80=94=20show=20variant=20count=20instead=20of=20duplicat?= =?UTF-8?q?es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Search endpoint now queries product.template instead of product.product, so a product with 20 variants shows as 1 row with a "20 variants" badge instead of 20 duplicate rows. Category name shown in sub-text. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../controllers/product_search.py | 57 ++++++++++++++----- .../static/src/xml/product_mapping.xml | 12 +++- 2 files changed, 54 insertions(+), 15 deletions(-) diff --git a/fusion-woo-odoo/fusion_woocommerce/controllers/product_search.py b/fusion-woo-odoo/fusion_woocommerce/controllers/product_search.py index 8bba4dfe..449cc65f 100644 --- a/fusion-woo-odoo/fusion_woocommerce/controllers/product_search.py +++ b/fusion-woo-odoo/fusion_woocommerce/controllers/product_search.py @@ -68,21 +68,52 @@ class WooProductSearchController(http.Controller): if instance.exists() and instance.excluded_category_ids: domain.append(('categ_id', 'not in', instance.excluded_category_ids.ids)) - total = request.env['product.product'].search_count(domain) - products = request.env['product.product'].search(domain, limit=limit, offset=offset) + # Search product.template to group variants together + tmpl_domain = [] + if query: + tmpl_domain = [ + '|', + ('name', 'ilike', query), + ('default_code', 'ilike', query), + ] + if category_id: + tmpl_domain.append(('categ_id', '=', int(category_id))) + if exclude_category_ids: + if isinstance(exclude_category_ids, str): + import json as _json2 + try: + exclude_category_ids = _json2.loads(exclude_category_ids) + except (ValueError, TypeError): + exclude_category_ids = [] + if exclude_category_ids: + tmpl_domain.append(('categ_id', 'not in', [int(x) for x in exclude_category_ids])) + if apply_excluded and instance_id: + instance = request.env['woo.instance'].browse(int(instance_id)) + if instance.exists() and instance.excluded_category_ids: + tmpl_domain.append(('categ_id', 'not in', instance.excluded_category_ids.ids)) + + total = request.env['product.template'].search_count(tmpl_domain) + templates = request.env['product.template'].search(tmpl_domain, limit=limit, offset=offset) + + results = [] + for tmpl in templates: + variant_count = len(tmpl.product_variant_ids) + # Use first variant as representative + first_variant = tmpl.product_variant_ids[:1] + results.append({ + 'id': first_variant.id if first_variant else tmpl.id, + 'template_id': tmpl.id, + 'name': tmpl.name, + 'default_code': tmpl.default_code or '', + 'list_price': tmpl.list_price, + 'qty_available': sum(tmpl.product_variant_ids.mapped('qty_available')), + 'categ_name': tmpl.categ_id.name if tmpl.categ_id else '', + 'variant_count': variant_count, + 'has_variants': variant_count > 1, + }) return { - 'results': [ - { - 'id': p.id, - 'name': p.name, - 'default_code': p.default_code or '', - 'list_price': p.list_price, - 'qty_available': p.qty_available, - 'categ_name': p.categ_id.name if p.categ_id else '', - } - for p in products - ], + 'results': results, 'total': total, } diff --git a/fusion-woo-odoo/fusion_woocommerce/static/src/xml/product_mapping.xml b/fusion-woo-odoo/fusion_woocommerce/static/src/xml/product_mapping.xml index a434acef..8cd5b220 100644 --- a/fusion-woo-odoo/fusion_woocommerce/static/src/xml/product_mapping.xml +++ b/fusion-woo-odoo/fusion_woocommerce/static/src/xml/product_mapping.xml @@ -368,10 +368,18 @@
-
+
+ + + + variants + + +
SKU: · - $ + + ·