[ADD] website_sale_aplicoop: Product blacklist feature for group orders

- Add excluded_product_ids field for explicit product exclusion
- Blacklist has absolute priority over all inclusion sources (product_ids, category_ids, supplier_ids)
- Update _get_products_for_group_order() with blacklist filter logic
- Rename 'Associations' section to 'Catálogo de Productos' with subsections:
  * Productos Incluidos (whitelist: suppliers, categories, direct products)
  * Productos Excluidos (blacklist: explicit exclusions)
- Add comprehensive test suite (TestProductBlacklist class with 7 tests)
- Add Spanish and Euskera translations
- Update available_products_count computation to include excluded_product_ids
- Version bump to 18.0.1.4.0

Use case: Bulk inclusion via categories/suppliers + fine-grained exclusion via blacklist
Example: Select a category with 100 products, exclude 5 unwanted → 95 available
This commit is contained in:
snt 2026-02-22 20:41:11 +01:00
parent 4a4639f13a
commit 75ebb7b907
7 changed files with 4765 additions and 7222 deletions

View file

@ -497,3 +497,239 @@ class TestProductDiscoveryOrdering(TransactionCase):
discovered_ids = {p.id for p in discovered}
expected_ids = {p.id for p in self.products}
self.assertEqual(discovered_ids, expected_ids)
class TestProductBlacklist(TransactionCase):
"""Test blacklist (excluded_product_ids) functionality.
The blacklist must have absolute priority over all inclusion sources:
- Direct product_ids
- Products from category_ids
- Products from supplier_ids
If a product is in excluded_product_ids, it should NEVER appear in
the discovered products, regardless of how it was included.
"""
def setUp(self):
super().setUp()
self.group = self.env["res.partner"].create(
{
"name": "Test Group",
"is_company": True,
}
)
# Create a supplier
self.supplier = self.env["res.partner"].create(
{
"name": "Test Supplier",
"is_company": True,
"supplier_rank": 1,
}
)
# Create category
self.category = self.env["product.category"].create(
{
"name": "Test Category",
}
)
# Create products
# 1. Direct product (will be added to product_ids)
self.direct_product = self.env["product.product"].create(
{
"name": "Direct Product",
"type": "consu",
"list_price": 10.0,
"is_published": True,
"sale_ok": True,
}
)
# 2. Category product (will be included via category_ids)
self.category_product = self.env["product.product"].create(
{
"name": "Category Product",
"type": "consu",
"list_price": 20.0,
"categ_id": self.category.id,
"is_published": True,
"sale_ok": True,
}
)
# 3. Supplier product (will be included via supplier_ids)
product_tmpl = self.env["product.template"].create(
{
"name": "Supplier Product",
"type": "consu",
"list_price": 30.0,
"is_published": True,
"sale_ok": True,
}
)
self.supplier_product = product_tmpl.product_variant_ids[0]
self.env["product.supplierinfo"].create(
{
"partner_id": self.supplier.id,
"product_tmpl_id": product_tmpl.id,
"price": 25.0,
}
)
# 4. Multi-source product (in all three sources)
multi_tmpl = self.env["product.template"].create(
{
"name": "Multi Source Product",
"type": "consu",
"list_price": 40.0,
"categ_id": self.category.id,
"is_published": True,
"sale_ok": True,
}
)
self.multi_product = multi_tmpl.product_variant_ids[0]
self.env["product.supplierinfo"].create(
{
"partner_id": self.supplier.id,
"product_tmpl_id": multi_tmpl.id,
"price": 35.0,
}
)
start_date = datetime.now().date()
self.group_order = self.env["group.order"].create(
{
"name": "Test Blacklist Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start_date,
"end_date": start_date + timedelta(days=7),
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
def test_blacklist_excludes_direct_product(self):
"""Test that excluded_product_ids filters out directly linked products."""
# Add product directly
self.group_order.product_ids = [(4, self.direct_product.id)]
# Product should be discoverable
products = self.group_order._get_products_for_group_order(self.group_order.id)
self.assertIn(self.direct_product, products)
# Now add to blacklist
self.group_order.excluded_product_ids = [(4, self.direct_product.id)]
# Product should NOT be discoverable anymore
products = self.group_order._get_products_for_group_order(self.group_order.id)
self.assertNotIn(self.direct_product, products)
def test_blacklist_excludes_category_product(self):
"""Test that excluded_product_ids filters out products from categories."""
# Add category (includes category_product)
self.group_order.category_ids = [(4, self.category.id)]
# Product should be discoverable
products = self.group_order._get_products_for_group_order(self.group_order.id)
self.assertIn(self.category_product, products)
# Now add to blacklist
self.group_order.excluded_product_ids = [(4, self.category_product.id)]
# Product should NOT be discoverable anymore
products = self.group_order._get_products_for_group_order(self.group_order.id)
self.assertNotIn(self.category_product, products)
def test_blacklist_excludes_supplier_product(self):
"""Test that excluded_product_ids filters out products from suppliers."""
# Add supplier (includes supplier_product)
self.group_order.supplier_ids = [(4, self.supplier.id)]
# Product should be discoverable
products = self.group_order._get_products_for_group_order(self.group_order.id)
self.assertIn(self.supplier_product, products)
# Now add to blacklist
self.group_order.excluded_product_ids = [(4, self.supplier_product.id)]
# Product should NOT be discoverable anymore
products = self.group_order._get_products_for_group_order(self.group_order.id)
self.assertNotIn(self.supplier_product, products)
def test_blacklist_priority_over_all_sources(self):
"""Test that blacklist has absolute priority even for multi-source products."""
# Add multi_product via all three sources
self.group_order.product_ids = [(4, self.multi_product.id)]
self.group_order.category_ids = [(4, self.category.id)]
self.group_order.supplier_ids = [(4, self.supplier.id)]
# Product should be discoverable
products = self.group_order._get_products_for_group_order(self.group_order.id)
self.assertIn(self.multi_product, products)
# Now add to blacklist
self.group_order.excluded_product_ids = [(4, self.multi_product.id)]
# Product should NOT be discoverable anymore, despite being in all sources
products = self.group_order._get_products_for_group_order(self.group_order.id)
self.assertNotIn(self.multi_product, products)
def test_empty_blacklist_no_effect(self):
"""Test that empty excluded_product_ids doesn't affect discovery."""
# Add products via various sources
self.group_order.product_ids = [(4, self.direct_product.id)]
self.group_order.category_ids = [(4, self.category.id)]
self.group_order.supplier_ids = [(4, self.supplier.id)]
# All products should be discoverable
products = self.group_order._get_products_for_group_order(self.group_order.id)
self.assertIn(self.direct_product, products)
self.assertIn(self.category_product, products)
self.assertIn(self.supplier_product, products)
# Excluded list is empty - should have no effect
self.assertEqual(len(self.group_order.excluded_product_ids), 0)
def test_blacklist_multiple_products(self):
"""Test excluding multiple products at once."""
# Add all products
self.group_order.product_ids = [(4, self.direct_product.id)]
self.group_order.category_ids = [(4, self.category.id)]
self.group_order.supplier_ids = [(4, self.supplier.id)]
# Exclude two products
self.group_order.excluded_product_ids = [
(4, self.direct_product.id),
(4, self.category_product.id),
]
products = self.group_order._get_products_for_group_order(self.group_order.id)
# These two should NOT be in results
self.assertNotIn(self.direct_product, products)
self.assertNotIn(self.category_product, products)
# But supplier_product should still be there
self.assertIn(self.supplier_product, products)
def test_blacklist_available_products_count(self):
"""Test that available_products_count reflects blacklist."""
# Add products
self.group_order.product_ids = [(4, self.direct_product.id)]
self.group_order.category_ids = [(4, self.category.id)]
# Count should include direct + category products
initial_count = self.group_order.available_products_count
self.assertGreater(initial_count, 0)
# Exclude one product
self.group_order.excluded_product_ids = [(4, self.category_product.id)]
# Count should decrease by 1
new_count = self.group_order.available_products_count
self.assertEqual(new_count, initial_count - 1)