feat: add pagination, individual price sync arrows, and bulk price sync
- Pagination with page nav for mapped, unmatched Odoo, and unmatched WC tabs - Per-product arrow buttons to push price in either direction - Bulk price sync buttons: All Prices Odoo→WC and All Prices WC→Odoo - Server-side offset/limit with total count in search endpoints Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -21,7 +21,7 @@ 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, **kw):
|
||||
def search_odoo_products(self, query='', instance_id=None, limit=20, offset=0, **kw):
|
||||
"""
|
||||
Search Odoo products by name or internal reference (SKU).
|
||||
|
||||
@@ -29,11 +29,13 @@ class WooProductSearchController(http.Controller):
|
||||
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:
|
||||
list of {id, name, default_code, list_price, qty_available}
|
||||
dict with 'results' list and 'total' count
|
||||
"""
|
||||
limit = min(int(limit or 20), 100)
|
||||
offset = int(offset or 0)
|
||||
domain = []
|
||||
|
||||
if query:
|
||||
@@ -43,24 +45,28 @@ class WooProductSearchController(http.Controller):
|
||||
('default_code', 'ilike', query),
|
||||
]
|
||||
|
||||
products = request.env['product.product'].search(domain, limit=limit)
|
||||
total = request.env['product.product'].search_count(domain)
|
||||
products = request.env['product.product'].search(domain, limit=limit, offset=offset)
|
||||
|
||||
return [
|
||||
{
|
||||
'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
|
||||
]
|
||||
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, **kw):
|
||||
def search_woo_products(self, query='', instance_id=None, limit=20, offset=0, **kw):
|
||||
"""
|
||||
Search unmapped WooCommerce products from the woo.product.map model.
|
||||
|
||||
@@ -68,11 +74,13 @@ class WooProductSearchController(http.Controller):
|
||||
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:
|
||||
list of {id, woo_product_id, woo_product_name, woo_sku, woo_product_type}
|
||||
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:
|
||||
@@ -85,24 +93,28 @@ class WooProductSearchController(http.Controller):
|
||||
('woo_sku', 'ilike', query),
|
||||
]
|
||||
|
||||
maps = request.env['woo.product.map'].search(domain, limit=limit)
|
||||
total = request.env['woo.product.map'].search_count(domain)
|
||||
maps = request.env['woo.product.map'].search(domain, limit=limit, offset=offset)
|
||||
|
||||
return [
|
||||
{
|
||||
'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
|
||||
]
|
||||
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, **kw):
|
||||
def search_mapped(self, query='', instance_id=None, limit=20, offset=0, **kw):
|
||||
"""
|
||||
Search mapped WooCommerce ↔ Odoo product pairs.
|
||||
|
||||
@@ -110,11 +122,13 @@ class WooProductSearchController(http.Controller):
|
||||
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:
|
||||
list of mapped product data dicts
|
||||
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:
|
||||
@@ -128,24 +142,28 @@ class WooProductSearchController(http.Controller):
|
||||
('product_id.name', 'ilike', query),
|
||||
]
|
||||
|
||||
maps = request.env['woo.product.map'].search(domain, limit=limit)
|
||||
total = request.env['woo.product.map'].search_count(domain)
|
||||
maps = request.env['woo.product.map'].search(domain, limit=limit, offset=offset)
|
||||
|
||||
return [
|
||||
{
|
||||
'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 '',
|
||||
'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,
|
||||
'woo_price': m.woo_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
|
||||
]
|
||||
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 '',
|
||||
'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,
|
||||
'woo_price': m.woo_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,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user