Files
Odoo-Modules/fusion-woo-odoo/fusion_woocommerce/controllers/product_search.py
gsinghpal b3fb2ef40c feat: add Cost and Margin % columns with inline editing
Cost column shows Odoo standard_price (editable). Margin % is calculated
from cost and sale price. Editing margin auto-calculates the sale price
using: price = cost / (1 - margin/100). All cells are inline-editable.

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

173 lines
6.1 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, **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).
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),
]
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,
}
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/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,
}