Files
Odoo-Modules/fusion-woo-odoo/fusion_woocommerce/controllers/product_search.py
gsinghpal 52be90c10d feat: category filter dropdown on unmatched Odoo products panel
Filter by Odoo product category or clear filter. Backend supports
both include and exclude category filtering. Loads all categories
on init for the dropdown.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 15:56:13 -04:00

202 lines
7.3 KiB
Python

# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
import logging
from odoo import http
from odoo.http import request
_logger = logging.getLogger(__name__)
class WooProductSearchController(http.Controller):
"""AJAX search endpoints used by the product mapping UI."""
# -------------------------------------------------------------------------
# Endpoints
# -------------------------------------------------------------------------
@http.route(
'/woo/search/odoo_products',
type='jsonrpc', auth='user', methods=['POST'],
)
def search_odoo_products(self, query='', instance_id=None, limit=20, offset=0,
category_id=None, exclude_category_ids=None, **kw):
"""
Search Odoo products by name or internal reference (SKU).
Params:
query (str): Search string matched against name and default_code.
instance_id (int): woo.instance ID (used for future per-instance filtering).
limit (int): Max results to return (default 20).
offset (int): Offset for pagination (default 0).
category_id (int): Filter by Odoo product category.
exclude_category_ids (list): Exclude these category IDs.
Returns:
dict with 'results' list and 'total' count
"""
limit = min(int(limit or 20), 100)
offset = int(offset or 0)
domain = []
if query:
domain = [
'|',
('name', 'ilike', query),
('default_code', 'ilike', query),
]
if category_id:
domain.append(('categ_id', '=', int(category_id)))
if exclude_category_ids:
if isinstance(exclude_category_ids, str):
import json as _json
try:
exclude_category_ids = _json.loads(exclude_category_ids)
except (ValueError, TypeError):
exclude_category_ids = []
if exclude_category_ids:
domain.append(('categ_id', 'not in', [int(x) for x in exclude_category_ids]))
total = request.env['product.product'].search_count(domain)
products = request.env['product.product'].search(domain, limit=limit, offset=offset)
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
],
'total': total,
}
@http.route(
'/woo/search/woo_products',
type='jsonrpc', auth='user', methods=['POST'],
)
def search_woo_products(self, query='', instance_id=None, limit=20, offset=0, **kw):
"""
Search unmapped WooCommerce products from the woo.product.map model.
Params:
query (str): Search string matched against woo_product_name and woo_sku.
instance_id (int): woo.instance ID — filters results to this instance.
limit (int): Max results to return (default 20).
offset (int): Offset for pagination (default 0).
Returns:
dict with 'results' list and 'total' count
"""
limit = min(int(limit or 20), 100)
offset = int(offset or 0)
domain = [('state', '=', 'unmapped')]
if instance_id:
domain.append(('instance_id', '=', int(instance_id)))
if query:
domain += [
'|',
('woo_product_name', 'ilike', query),
('woo_sku', 'ilike', query),
]
total = request.env['woo.product.map'].search_count(domain)
maps = request.env['woo.product.map'].search(domain, limit=limit, offset=offset)
return {
'results': [
{
'id': m.id,
'woo_product_id': m.woo_product_id,
'woo_product_name': m.woo_product_name or '',
'woo_sku': m.woo_sku or '',
'woo_product_type': m.woo_product_type or '',
}
for m in maps
],
'total': total,
}
@http.route(
'/woo/search/odoo_categories',
type='jsonrpc', auth='user', methods=['POST'],
)
def get_odoo_categories(self, **kw):
"""Return all Odoo product categories for filtering."""
categories = request.env['product.category'].search([], order='complete_name')
return [
{'id': c.id, 'name': c.name, 'complete_name': c.complete_name}
for c in categories
]
@http.route(
'/woo/search/mapped',
type='jsonrpc', auth='user', methods=['POST'],
)
def search_mapped(self, query='', instance_id=None, limit=20, offset=0, **kw):
"""
Search mapped WooCommerce ↔ Odoo product pairs.
Params:
query (str): Matched against woo_product_name, woo_sku, and linked product name.
instance_id (int): woo.instance ID — filters results to this instance.
limit (int): Max results to return (default 20).
offset (int): Offset for pagination (default 0).
Returns:
dict with 'results' list and 'total' count
"""
limit = min(int(limit or 20), 100)
offset = int(offset or 0)
domain = [('state', '=', 'mapped')]
if instance_id:
domain.append(('instance_id', '=', int(instance_id)))
if query:
domain += [
'|', '|',
('woo_product_name', 'ilike', query),
('woo_sku', 'ilike', query),
('product_id.name', 'ilike', query),
]
total = request.env['woo.product.map'].search_count(domain)
maps = request.env['woo.product.map'].search(domain, limit=limit, offset=offset)
return {
'results': [
{
'id': m.id,
'woo_product_id': m.woo_product_id,
'woo_product_name': m.woo_product_name or '',
'woo_sku': m.woo_sku or '',
'woo_product_type': m.woo_product_type or '',
'woo_permalink': m.woo_permalink or '',
'odoo_product_id': m.product_id.id if m.product_id else False,
'odoo_product_name': m.product_id.name if m.product_id else '',
'odoo_default_code': m.product_id.default_code or '' if m.product_id else '',
'odoo_price': m.product_id.list_price if m.product_id else 0.0,
'odoo_cost': m.product_id.standard_price if m.product_id else 0.0,
'woo_regular_price': m.woo_regular_price or 0.0,
'woo_sale_price': m.woo_sale_price or 0.0,
'sync_price': m.sync_price,
'sync_inventory': m.sync_inventory,
'instance_id': m.instance_id.id if m.instance_id else False,
'instance_name': m.instance_id.name if m.instance_id else '',
}
for m in maps
],
'total': total,
}