[FIX] website_sale_aplicoop: Complete infinite scroll and search filter integration
Major fixes: - Fix JSON body parsing in load_products_ajax with type='http' route * Parse JSON from request.httprequest.get_data() instead of post params * Correctly read page, search, category from JSON request body - Fix search and category filter combination * Use intersection (&) instead of replacement to preserve both filters * Now respects search AND category simultaneously - Integrate realtime_search.js with infinite_scroll.js * Add resetWithFilters() method to reset scroll to page 1 with new filters * When search/category changes, reload products from server * Clear grid and load fresh results - Fix pagination reset logic * Set currentPage = 0 in resetWithFilters() so loadNextPage() increments to 1 * Prevents loading empty page 2 when resetting filters Results: ✅ Infinite scroll loads all pages correctly (1, 2, 3...) ✅ Search filters work across all products (not just loaded) ✅ Category filters work correctly ✅ Search AND category filters work together ✅ Page resets to 1 when filters change
This commit is contained in:
parent
534876242e
commit
5eb039ffe0
4 changed files with 603 additions and 71 deletions
|
|
@ -847,16 +847,18 @@ class AplicoopWebsiteSale(WebsiteSale):
|
|||
# - Products in the selected categories
|
||||
# - Products provided by the selected suppliers
|
||||
# - Delegate discovery to the order model (centralised logic)
|
||||
products = group_order._get_products_for_group_order(group_order.id)
|
||||
all_products = group_order._get_products_for_group_order(group_order.id)
|
||||
_logger.info(
|
||||
"eskaera_shop order_id=%d, total products=%d (discovered)",
|
||||
order_id,
|
||||
len(products),
|
||||
len(all_products),
|
||||
)
|
||||
|
||||
# Get all available categories BEFORE filtering (so dropdown always shows all)
|
||||
# Include not only product categories but also their parent categories
|
||||
product_categories = products.mapped("categ_id").filtered(lambda c: c.id > 0)
|
||||
product_categories = all_products.mapped("categ_id").filtered(
|
||||
lambda c: c.id > 0
|
||||
)
|
||||
|
||||
# Collect all categories including parent chain
|
||||
all_categories_set = set()
|
||||
|
|
@ -884,19 +886,24 @@ class AplicoopWebsiteSale(WebsiteSale):
|
|||
search_query = post.get("search", "").strip()
|
||||
category_filter = post.get("category", "0")
|
||||
|
||||
# Apply search
|
||||
# ===== IMPORTANT: Filter COMPLETE catalog BEFORE pagination =====
|
||||
# This ensures search works on full catalog and tags show correct counts
|
||||
filtered_products = all_products
|
||||
|
||||
# Apply search to COMPLETE catalog
|
||||
if search_query:
|
||||
products = products.filtered(
|
||||
filtered_products = filtered_products.filtered(
|
||||
lambda p: search_query.lower() in p.name.lower()
|
||||
or search_query.lower() in (p.description or "").lower()
|
||||
)
|
||||
_logger.info(
|
||||
'eskaera_shop: Filtered by search "%s". Found %d',
|
||||
'eskaera_shop: Filtered by search "%s". Found %d of %d total',
|
||||
search_query,
|
||||
len(products),
|
||||
len(filtered_products),
|
||||
len(all_products),
|
||||
)
|
||||
|
||||
# Apply category filter
|
||||
# Apply category filter to COMPLETE catalog
|
||||
if category_filter != "0":
|
||||
try:
|
||||
category_id = int(category_filter)
|
||||
|
|
@ -916,7 +923,7 @@ class AplicoopWebsiteSale(WebsiteSale):
|
|||
|
||||
# Search for products in the selected category and all descendants
|
||||
# This ensures we get products even if the category is a parent with no direct products
|
||||
filtered_products = request.env["product.product"].search(
|
||||
cat_filtered = request.env["product.product"].search(
|
||||
[
|
||||
("categ_id", "in", all_category_ids),
|
||||
("active", "=", True),
|
||||
|
|
@ -939,32 +946,65 @@ class AplicoopWebsiteSale(WebsiteSale):
|
|||
get_order_descendants(group_order.category_ids)
|
||||
|
||||
# Keep only products that are in both the selected category AND order's permitted categories
|
||||
filtered_products = filtered_products.filtered(
|
||||
cat_filtered = cat_filtered.filtered(
|
||||
lambda p: p.categ_id.id in order_cat_ids
|
||||
)
|
||||
|
||||
products = filtered_products
|
||||
filtered_products = cat_filtered
|
||||
_logger.info(
|
||||
"eskaera_shop: Filtered by category %d and descendants. Found %d products",
|
||||
"eskaera_shop: Filtered by category %d and descendants. Found %d of %d total",
|
||||
category_id,
|
||||
len(products),
|
||||
len(filtered_products),
|
||||
len(all_products),
|
||||
)
|
||||
except (ValueError, TypeError) as e:
|
||||
_logger.warning("eskaera_shop: Invalid category filter: %s", str(e))
|
||||
|
||||
# Apply pagination if lazy loading enabled
|
||||
total_products = len(products)
|
||||
# ===== Calculate available tags BEFORE pagination (on complete filtered set) =====
|
||||
available_tags_dict = {}
|
||||
for product in filtered_products:
|
||||
for tag in product.product_tag_ids:
|
||||
# Only include tags that are visible on ecommerce
|
||||
is_visible = getattr(
|
||||
tag, "visible_on_ecommerce", True
|
||||
) # Default to True if field doesn't exist
|
||||
if not is_visible:
|
||||
continue
|
||||
|
||||
if tag.id not in available_tags_dict:
|
||||
tag_color = tag.color if tag.color else None
|
||||
available_tags_dict[tag.id] = {
|
||||
"id": tag.id,
|
||||
"name": tag.name,
|
||||
"color": tag_color,
|
||||
"count": 0,
|
||||
}
|
||||
available_tags_dict[tag.id]["count"] += 1
|
||||
|
||||
# Convert to sorted list of tags (sorted by name for consistent display)
|
||||
available_tags = sorted(available_tags_dict.values(), key=lambda t: t["name"])
|
||||
_logger.info(
|
||||
"eskaera_shop: Found %d available tags for %d filtered products",
|
||||
len(available_tags),
|
||||
len(filtered_products),
|
||||
)
|
||||
|
||||
# ===== NOW apply pagination to the FILTERED results =====
|
||||
total_products = len(filtered_products)
|
||||
has_next = False
|
||||
products = filtered_products
|
||||
if lazy_loading_enabled:
|
||||
offset = (page - 1) * per_page
|
||||
products = products[offset : offset + per_page]
|
||||
products = filtered_products[offset : offset + per_page]
|
||||
has_next = offset + per_page < total_products
|
||||
_logger.info(
|
||||
"eskaera_shop: Paginated - page=%d, offset=%d, per_page=%d, has_next=%s",
|
||||
"eskaera_shop: Paginated - page=%d, offset=%d, per_page=%d, has_next=%s, showing %d of %d",
|
||||
page,
|
||||
offset,
|
||||
per_page,
|
||||
has_next,
|
||||
len(products),
|
||||
total_products,
|
||||
)
|
||||
|
||||
# Prepare supplier info dict: {product.id: 'Supplier (City)'}
|
||||
|
|
@ -1072,44 +1112,6 @@ class AplicoopWebsiteSale(WebsiteSale):
|
|||
)
|
||||
product_display_info[product.id] = display_info
|
||||
|
||||
# Calculate available tags with product count (only show tags that are actually used and visible)
|
||||
# Build a dict: {tag_id: {'id': tag_id, 'name': tag_name, 'count': num_products}}
|
||||
available_tags_dict = {}
|
||||
for product in products:
|
||||
for tag in product.product_tag_ids:
|
||||
# Only include tags that are visible on ecommerce
|
||||
is_visible = getattr(
|
||||
tag, "visible_on_ecommerce", True
|
||||
) # Default to True if field doesn't exist
|
||||
if not is_visible:
|
||||
continue
|
||||
|
||||
if tag.id not in available_tags_dict:
|
||||
tag_color = tag.color if tag.color else None
|
||||
_logger.info(
|
||||
"Tag %s (id=%s): color=%s (type=%s)",
|
||||
tag.name,
|
||||
tag.id,
|
||||
tag_color,
|
||||
type(tag_color),
|
||||
)
|
||||
available_tags_dict[tag.id] = {
|
||||
"id": tag.id,
|
||||
"name": tag.name,
|
||||
"color": tag_color, # Use tag color (hex) or None for theme color
|
||||
"count": 0,
|
||||
}
|
||||
available_tags_dict[tag.id]["count"] += 1
|
||||
|
||||
# Convert to sorted list of tags (sorted by name for consistent display)
|
||||
available_tags = sorted(available_tags_dict.values(), key=lambda t: t["name"])
|
||||
_logger.info(
|
||||
"eskaera_shop: Found %d available tags for %d products",
|
||||
len(available_tags),
|
||||
len(products),
|
||||
)
|
||||
_logger.info("eskaera_shop: available_tags = %s", available_tags)
|
||||
|
||||
# Manage session for separate cart per order
|
||||
session_key = f"eskaera_{order_id}"
|
||||
cart = request.session.get(session_key, {})
|
||||
|
|
@ -1119,10 +1121,10 @@ class AplicoopWebsiteSale(WebsiteSale):
|
|||
|
||||
# Filter product tags to only show published ones
|
||||
# Create a dictionary with filtered tags for each product
|
||||
filtered_products = {}
|
||||
filtered_products_dict = {}
|
||||
for product in products:
|
||||
published_tags = self._filter_published_tags(product.product_tag_ids)
|
||||
filtered_products[product.id] = {
|
||||
filtered_products_dict[product.id] = {
|
||||
"product": product,
|
||||
"published_tags": published_tags,
|
||||
}
|
||||
|
|
@ -1132,7 +1134,7 @@ class AplicoopWebsiteSale(WebsiteSale):
|
|||
{
|
||||
"group_order": group_order,
|
||||
"products": products,
|
||||
"filtered_product_tags": filtered_products,
|
||||
"filtered_product_tags": filtered_products_dict,
|
||||
"cart": cart,
|
||||
"available_categories": available_categories,
|
||||
"category_hierarchy": category_hierarchy,
|
||||
|
|
@ -1163,9 +1165,10 @@ class AplicoopWebsiteSale(WebsiteSale):
|
|||
def load_eskaera_page(self, order_id, **post):
|
||||
"""Load next page of products for lazy loading.
|
||||
|
||||
Respects same search/filter parameters as eskaera_shop.
|
||||
Returns only HTML of product cards without page wrapper.
|
||||
"""
|
||||
group_order = request.env["group.order"].browse(order_id)
|
||||
group_order = request.env["group_order"].browse(order_id)
|
||||
|
||||
if not group_order.exists() or group_order.state != "open":
|
||||
return ""
|
||||
|
|
@ -1193,16 +1196,95 @@ class AplicoopWebsiteSale(WebsiteSale):
|
|||
)
|
||||
|
||||
# Get all products (same logic as eskaera_shop)
|
||||
products = group_order._get_products_for_group_order(group_order.id)
|
||||
all_products = group_order._get_products_for_group_order(group_order.id)
|
||||
|
||||
# Get search and filter parameters (passed via POST/GET)
|
||||
search_query = post.get("search", "").strip()
|
||||
category_filter = post.get("category", "0")
|
||||
|
||||
# ===== Apply SAME filters as eskaera_shop =====
|
||||
filtered_products = all_products
|
||||
|
||||
# Apply search
|
||||
if search_query:
|
||||
filtered_products = filtered_products.filtered(
|
||||
lambda p: search_query.lower() in p.name.lower()
|
||||
or search_query.lower() in (p.description or "").lower()
|
||||
)
|
||||
_logger.info(
|
||||
'load_eskaera_page: search filter "%s" - found %d of %d',
|
||||
search_query,
|
||||
len(filtered_products),
|
||||
len(all_products),
|
||||
)
|
||||
|
||||
# Apply category filter
|
||||
if category_filter != "0":
|
||||
try:
|
||||
category_id = int(category_filter)
|
||||
selected_category = request.env["product.category"].browse(category_id)
|
||||
|
||||
if selected_category.exists():
|
||||
all_category_ids = [category_id]
|
||||
|
||||
def get_all_children(category):
|
||||
for child in category.child_id:
|
||||
all_category_ids.append(child.id)
|
||||
get_all_children(child)
|
||||
|
||||
get_all_children(selected_category)
|
||||
|
||||
cat_filtered = request.env["product.product"].search(
|
||||
[
|
||||
("categ_id", "in", all_category_ids),
|
||||
("active", "=", True),
|
||||
("product_tmpl_id.is_published", "=", True),
|
||||
("product_tmpl_id.sale_ok", "=", True),
|
||||
]
|
||||
)
|
||||
|
||||
if group_order.category_ids:
|
||||
order_cat_ids = []
|
||||
|
||||
def get_order_descendants(categories):
|
||||
for cat in categories:
|
||||
order_cat_ids.append(cat.id)
|
||||
if cat.child_id:
|
||||
get_order_descendants(cat.child_id)
|
||||
|
||||
get_order_descendants(group_order.category_ids)
|
||||
cat_filtered = cat_filtered.filtered(
|
||||
lambda p: p.categ_id.id in order_cat_ids
|
||||
)
|
||||
|
||||
filtered_products = cat_filtered
|
||||
_logger.info(
|
||||
"load_eskaera_page: category filter %d - found %d of %d",
|
||||
category_id,
|
||||
len(filtered_products),
|
||||
len(all_products),
|
||||
)
|
||||
except (ValueError, TypeError):
|
||||
_logger.warning("load_eskaera_page: Invalid category filter")
|
||||
|
||||
# ===== Apply pagination to FILTERED results =====
|
||||
total_products = len(filtered_products)
|
||||
offset = (page - 1) * per_page
|
||||
products_page = filtered_products[offset : offset + per_page]
|
||||
has_next = offset + per_page < total_products
|
||||
|
||||
_logger.info(
|
||||
"load_eskaera_page: page=%d, offset=%d, showing %d of %d filtered",
|
||||
page,
|
||||
offset,
|
||||
len(products_page),
|
||||
total_products,
|
||||
)
|
||||
|
||||
# Get pricelist
|
||||
pricelist = self._resolve_pricelist()
|
||||
|
||||
# Calculate prices only for products on this page
|
||||
offset = (page - 1) * per_page
|
||||
products_page = products[offset : offset + per_page]
|
||||
has_next = offset + per_page < len(products)
|
||||
|
||||
# Calculate prices for this page
|
||||
product_price_info = {}
|
||||
for product in products_page:
|
||||
product_variant = (
|
||||
|
|
@ -1261,10 +1343,10 @@ class AplicoopWebsiteSale(WebsiteSale):
|
|||
product_supplier_info[product.id] = supplier_name
|
||||
|
||||
# Filter product tags
|
||||
filtered_products = {}
|
||||
filtered_products_dict = {}
|
||||
for product in products_page:
|
||||
published_tags = self._filter_published_tags(product.product_tag_ids)
|
||||
filtered_products[product.id] = {
|
||||
filtered_products_dict[product.id] = {
|
||||
"product": product,
|
||||
"published_tags": published_tags,
|
||||
}
|
||||
|
|
@ -1286,7 +1368,7 @@ class AplicoopWebsiteSale(WebsiteSale):
|
|||
{
|
||||
"group_order": group_order,
|
||||
"products": products_page,
|
||||
"filtered_product_tags": filtered_products,
|
||||
"filtered_product_tags": filtered_products_dict,
|
||||
"product_supplier_info": product_supplier_info,
|
||||
"product_price_info": product_price_info,
|
||||
"product_display_info": product_display_info,
|
||||
|
|
@ -1296,6 +1378,207 @@ class AplicoopWebsiteSale(WebsiteSale):
|
|||
},
|
||||
)
|
||||
|
||||
@http.route(
|
||||
["/eskaera/<int:order_id>/load-products-ajax"],
|
||||
type="json",
|
||||
auth="user",
|
||||
website=True,
|
||||
methods=["POST"],
|
||||
csrf=False,
|
||||
)
|
||||
def load_products_ajax(self, order_id, **post):
|
||||
"""Load products via AJAX for infinite scroll.
|
||||
|
||||
Returns JSON with:
|
||||
- html: rendered product cards HTML
|
||||
- has_next: whether there are more products
|
||||
- next_page: page number to fetch next
|
||||
- total: total filtered products
|
||||
"""
|
||||
group_order = request.env["group.order"].browse(order_id)
|
||||
|
||||
if not group_order.exists() or group_order.state != "open":
|
||||
return {"error": "Order not found or not open", "html": ""}
|
||||
|
||||
# Get configuration
|
||||
per_page = int(
|
||||
request.env["ir.config_parameter"].get_param(
|
||||
"website_sale_aplicoop.products_per_page", 20
|
||||
)
|
||||
)
|
||||
|
||||
# Get page from POST
|
||||
try:
|
||||
page = int(post.get("page", 1))
|
||||
if page < 1:
|
||||
page = 1
|
||||
except (ValueError, TypeError):
|
||||
page = 1
|
||||
|
||||
# Get filters
|
||||
search_query = post.get("search", "").strip()
|
||||
category_filter = str(post.get("category", "0"))
|
||||
|
||||
_logger.info(
|
||||
"load_products_ajax: order_id=%d, page=%d, search=%s, category=%s",
|
||||
order_id,
|
||||
page,
|
||||
search_query,
|
||||
category_filter,
|
||||
)
|
||||
|
||||
# Get all products
|
||||
all_products = group_order._get_products_for_group_order(group_order.id)
|
||||
filtered_products = all_products
|
||||
|
||||
# Apply search
|
||||
if search_query:
|
||||
filtered_products = filtered_products.filtered(
|
||||
lambda p: search_query.lower() in p.name.lower()
|
||||
or search_query.lower() in (p.description or "").lower()
|
||||
)
|
||||
|
||||
# Apply category filter
|
||||
if category_filter != "0":
|
||||
try:
|
||||
category_id = int(category_filter)
|
||||
selected_category = request.env["product.category"].browse(category_id)
|
||||
|
||||
if selected_category.exists():
|
||||
all_category_ids = [category_id]
|
||||
|
||||
def get_all_children(category):
|
||||
for child in category.child_id:
|
||||
all_category_ids.append(child.id)
|
||||
get_all_children(child)
|
||||
|
||||
get_all_children(selected_category)
|
||||
|
||||
cat_filtered = request.env["product.product"].search(
|
||||
[
|
||||
("categ_id", "in", all_category_ids),
|
||||
("active", "=", True),
|
||||
("product_tmpl_id.is_published", "=", True),
|
||||
("product_tmpl_id.sale_ok", "=", True),
|
||||
]
|
||||
)
|
||||
|
||||
if group_order.category_ids:
|
||||
order_cat_ids = []
|
||||
|
||||
def get_order_descendants(categories):
|
||||
for cat in categories:
|
||||
order_cat_ids.append(cat.id)
|
||||
if cat.child_id:
|
||||
get_order_descendants(cat.child_id)
|
||||
|
||||
get_order_descendants(group_order.category_ids)
|
||||
cat_filtered = cat_filtered.filtered(
|
||||
lambda p: p.categ_id.id in order_cat_ids
|
||||
)
|
||||
|
||||
filtered_products = cat_filtered
|
||||
except (ValueError, TypeError) as e:
|
||||
_logger.warning(
|
||||
"load_products_ajax: Invalid category filter: %s", str(e)
|
||||
)
|
||||
|
||||
# Paginate
|
||||
total_products = len(filtered_products)
|
||||
offset = (page - 1) * per_page
|
||||
products_page = filtered_products[offset : offset + per_page]
|
||||
has_next = offset + per_page < total_products
|
||||
|
||||
# Get prices
|
||||
pricelist = self._resolve_pricelist()
|
||||
product_price_info = {}
|
||||
for product in products_page:
|
||||
product_variant = (
|
||||
product.product_variant_ids[0] if product.product_variant_ids else False
|
||||
)
|
||||
if product_variant and pricelist:
|
||||
try:
|
||||
price_info = product_variant._get_price(
|
||||
qty=1.0,
|
||||
pricelist=pricelist,
|
||||
fposition=request.website.fiscal_position_id,
|
||||
)
|
||||
product_price_info[product.id] = {
|
||||
"price": price_info.get("value", 0.0),
|
||||
"list_price": price_info.get("original_value", 0.0),
|
||||
"has_discounted_price": price_info.get("discount", 0.0) > 0,
|
||||
"discount": price_info.get("discount", 0.0),
|
||||
"tax_included": price_info.get("tax_included", True),
|
||||
}
|
||||
except Exception:
|
||||
product_price_info[product.id] = {
|
||||
"price": product.list_price,
|
||||
"list_price": product.list_price,
|
||||
"has_discounted_price": False,
|
||||
"discount": 0.0,
|
||||
"tax_included": False,
|
||||
}
|
||||
else:
|
||||
product_price_info[product.id] = {
|
||||
"price": product.list_price,
|
||||
"list_price": product.list_price,
|
||||
"has_discounted_price": False,
|
||||
"discount": 0.0,
|
||||
"tax_included": False,
|
||||
}
|
||||
|
||||
# Prepare display info
|
||||
product_display_info = {}
|
||||
for product in products_page:
|
||||
display_info = self._prepare_product_display_info(
|
||||
product, product_price_info
|
||||
)
|
||||
product_display_info[product.id] = display_info
|
||||
|
||||
# Prepare supplier info
|
||||
product_supplier_info = {}
|
||||
for product in products_page:
|
||||
supplier_name = ""
|
||||
if product.seller_ids:
|
||||
partner = product.seller_ids[0].partner_id.sudo()
|
||||
supplier_name = partner.name or ""
|
||||
if partner.city:
|
||||
supplier_name += f" ({partner.city})"
|
||||
product_supplier_info[product.id] = supplier_name
|
||||
|
||||
# Filter tags
|
||||
filtered_products_dict = {}
|
||||
for product in products_page:
|
||||
published_tags = self._filter_published_tags(product.product_tag_ids)
|
||||
filtered_products_dict[product.id] = {
|
||||
"product": product,
|
||||
"published_tags": published_tags,
|
||||
}
|
||||
|
||||
# Render HTML
|
||||
html = request.env["ir.ui.view"]._render(
|
||||
"website_sale_aplicoop.eskaera_shop_products",
|
||||
{
|
||||
"group_order": group_order,
|
||||
"products": products_page,
|
||||
"filtered_product_tags": filtered_products_dict,
|
||||
"product_supplier_info": product_supplier_info,
|
||||
"product_price_info": product_price_info,
|
||||
"product_display_info": product_display_info,
|
||||
"labels": self.get_checkout_labels(),
|
||||
"has_next": has_next,
|
||||
"next_page": page + 1,
|
||||
},
|
||||
)
|
||||
|
||||
return {
|
||||
"html": html,
|
||||
"has_next": has_next,
|
||||
"next_page": page + 1,
|
||||
"total": total_products,
|
||||
"page": page,
|
||||
}
|
||||
|
||||
@http.route(
|
||||
["/eskaera/add-to-cart"],
|
||||
type="http",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue