diff --git a/fusion-woo-odoo/fusion_woocommerce/controllers/product_search.py b/fusion-woo-odoo/fusion_woocommerce/controllers/product_search.py index 5ce89524..57173896 100644 --- a/fusion-woo-odoo/fusion_woocommerce/controllers/product_search.py +++ b/fusion-woo-odoo/fusion_woocommerce/controllers/product_search.py @@ -21,7 +21,8 @@ class WooProductSearchController(http.Controller): '/woo/search/odoo_products', type='jsonrpc', auth='user', methods=['POST'], ) - def search_odoo_products(self, query='', instance_id=None, limit=20, offset=0, **kw): + 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). @@ -30,6 +31,8 @@ class WooProductSearchController(http.Controller): 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 @@ -45,6 +48,19 @@ class WooProductSearchController(http.Controller): ('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) @@ -56,6 +72,7 @@ class WooProductSearchController(http.Controller): '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 ], @@ -110,6 +127,18 @@ class WooProductSearchController(http.Controller): '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'], diff --git a/fusion-woo-odoo/fusion_woocommerce/static/src/css/woo_styles.css b/fusion-woo-odoo/fusion_woocommerce/static/src/css/woo_styles.css index 2aa8caf8..fa780b99 100644 --- a/fusion-woo-odoo/fusion_woocommerce/static/src/css/woo_styles.css +++ b/fusion-woo-odoo/fusion_woocommerce/static/src/css/woo_styles.css @@ -644,6 +644,21 @@ html[style*="color-scheme: dark"] { box-shadow: 0 0 0 2px var(--woo-accent-glow); } +/* Category filter dropdown */ +.woo-filter-select { + padding: 3px 8px; + border: 1px solid var(--woo-input-border); + border-radius: 4px; + font-size: 0.8rem; + background: var(--woo-input-bg); + color: var(--woo-text-primary); + max-width: 200px; +} +.woo-filter-select option { + background: var(--woo-bg-primary); + color: var(--woo-text-primary); +} + .woo-edit-input-text { text-align: left; width: 120px; diff --git a/fusion-woo-odoo/fusion_woocommerce/static/src/js/product_mapping.js b/fusion-woo-odoo/fusion_woocommerce/static/src/js/product_mapping.js index 7d5a3e42..f9100bef 100644 --- a/fusion-woo-odoo/fusion_woocommerce/static/src/js/product_mapping.js +++ b/fusion-woo-odoo/fusion_woocommerce/static/src/js/product_mapping.js @@ -60,12 +60,18 @@ export class ProductMapping extends Component { // Pagination pageSize: 50, + // Category filters + odooCategories: [], + odooFilterCategoryId: false, + odooExcludeCategoryIds: [], + // Conflicts tab conflicts: [], }); onWillStart(async () => { await this._loadInstances(); + await this._loadOdooCategories(); await this._refreshAll(); }); } @@ -125,6 +131,15 @@ export class ProductMapping extends Component { } } + async _loadOdooCategories() { + try { + const result = await rpc("/woo/search/odoo_categories", {}); + this.state.odooCategories = result || []; + } catch (err) { + console.error("[ProductMapping] _loadOdooCategories error:", err); + } + } + async _loadOdooProducts(query = "") { try { const params = { @@ -135,6 +150,12 @@ export class ProductMapping extends Component { if (this.state.instanceId) { params.instance_id = this.state.instanceId; } + if (this.state.odooFilterCategoryId) { + params.category_id = this.state.odooFilterCategoryId; + } + if (this.state.odooExcludeCategoryIds.length) { + params.exclude_category_ids = JSON.stringify(this.state.odooExcludeCategoryIds); + } const result = await rpc("/woo/search/odoo_products", params); this.state.odooProducts = (result && result.results) || []; this.state.unmatchedOdooTotal = (result && result.total) || 0; @@ -260,6 +281,42 @@ export class ProductMapping extends Component { await this._refreshAll(); } + // ------------------------------------------------------------------------- + // Category filter + // ------------------------------------------------------------------------- + + async onOdooCategoryFilter(ev) { + const val = ev.target.value; + this.state.odooFilterCategoryId = val ? parseInt(val, 10) : false; + this.state.unmatchedOdooPage = 1; + await this._loadOdooProducts(""); + } + + toggleExcludeCategory(catId) { + const idx = this.state.odooExcludeCategoryIds.indexOf(catId); + if (idx >= 0) { + this.state.odooExcludeCategoryIds.splice(idx, 1); + } else { + this.state.odooExcludeCategoryIds.push(catId); + } + } + + async applyExcludeCategories() { + this.state.unmatchedOdooPage = 1; + await this._loadOdooProducts(""); + } + + isCategoryExcluded(catId) { + return this.state.odooExcludeCategoryIds.includes(catId); + } + + async clearCategoryFilter() { + this.state.odooFilterCategoryId = false; + this.state.odooExcludeCategoryIds = []; + this.state.unmatchedOdooPage = 1; + await this._loadOdooProducts(""); + } + // ------------------------------------------------------------------------- // Mapped tab // ------------------------------------------------------------------------- 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 af80bc82..f4bdbe0d 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 @@ -333,8 +333,24 @@