Compare commits
3 commits
4a4639f13a
...
c1226e720b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1226e720b | ||
|
|
d90f2cdc61 | ||
|
|
75ebb7b907 |
7 changed files with 5544 additions and 7222 deletions
|
|
@ -1,5 +1,114 @@
|
||||||
# Changelog - Website Sale Aplicoop
|
# Changelog - Website Sale Aplicoop
|
||||||
|
|
||||||
|
## [18.0.1.6.0] - 2026-02-22
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Category Blacklist Feature**: Category-based exclusion system for group orders
|
||||||
|
- New field: `excluded_category_ids` (Many2many to product.category)
|
||||||
|
- Recursive exclusion: Excludes products in selected categories AND all subcategories
|
||||||
|
- Blacklist has absolute priority over all inclusion sources
|
||||||
|
- Helper method: `get_all_excluded_descendants()` for recursive category tree traversal
|
||||||
|
- Comprehensive test suite in `test_product_discovery.py` (TestCategoryBlacklist class with 9 tests)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **Product Discovery Logic**: Extended to filter by category blacklist
|
||||||
|
- `_get_products_for_group_order()` now applies `excluded_category_ids` filter recursively
|
||||||
|
- Products in excluded categories and their subcategories are filtered out
|
||||||
|
- `_compute_available_products_count()` now depends on `excluded_category_ids`
|
||||||
|
- Detailed logging for excluded categories and affected products
|
||||||
|
|
||||||
|
- **UI Updates**: "Productos Excluidos" section now includes three blacklist types:
|
||||||
|
- `excluded_supplier_ids`: Blacklist suppliers
|
||||||
|
- `excluded_category_ids`: Blacklist categories (recursive)
|
||||||
|
- `excluded_product_ids`: Blacklist specific products
|
||||||
|
|
||||||
|
### Technical Details
|
||||||
|
|
||||||
|
- New M2M relation: `group_order_excluded_category_rel`
|
||||||
|
- Recursive logic: Walks category tree to find all descendants
|
||||||
|
- Filter logic: `products.filtered(lambda p: p.categ_id not in all_excluded_categories)`
|
||||||
|
- Works in combination with product and supplier blacklists (all filters apply)
|
||||||
|
|
||||||
|
### Use Case
|
||||||
|
|
||||||
|
- Admin wants to exclude all products in a category and its subcategories
|
||||||
|
- Example: Exclude "Fresh Produce" → automatically excludes "Fruits", "Vegetables", etc.
|
||||||
|
- Add parent category to inclusion → add problematic subcategory to exclusion
|
||||||
|
- Result: Fine-grained control over product catalog with minimal configuration
|
||||||
|
|
||||||
|
## [18.0.1.5.0] - 2026-02-22
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Supplier Blacklist Feature**: New exclusion system by supplier for group orders
|
||||||
|
- New field: `excluded_supplier_ids` (Many2many to res.partner)
|
||||||
|
- Filters products by `main_seller_id` (from product_main_seller addon)
|
||||||
|
- Blacklist has absolute priority over all inclusion sources
|
||||||
|
- Products whose main supplier is blacklisted never appear
|
||||||
|
- Comprehensive test suite in `test_product_discovery.py` (TestSupplierBlacklist class with 9 tests)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **Product Discovery Logic**: Extended to filter by supplier blacklist
|
||||||
|
- `_get_products_for_group_order()` now applies `excluded_supplier_ids` filter
|
||||||
|
- Products with `main_seller_id` in excluded_supplier_ids are filtered out
|
||||||
|
- `_compute_available_products_count()` now depends on `excluded_supplier_ids`
|
||||||
|
- Detailed logging for excluded suppliers and affected products
|
||||||
|
|
||||||
|
- **UI Updates**: "Productos Excluidos" section now includes both:
|
||||||
|
- `excluded_supplier_ids`: Blacklist suppliers
|
||||||
|
- `excluded_product_ids`: Blacklist specific products
|
||||||
|
|
||||||
|
### Technical Details
|
||||||
|
|
||||||
|
- New M2M relation: `group_order_excluded_supplier_rel`
|
||||||
|
- Filter logic: `products.filtered(lambda p: p.product_tmpl_id.main_seller_id not in excluded_supplier_ids)`
|
||||||
|
- Works in combination with product blacklist (both filters apply)
|
||||||
|
- Uses `main_seller_id` from product_main_seller addon (NOT default_supplier_id)
|
||||||
|
|
||||||
|
### Use Case
|
||||||
|
|
||||||
|
- Admin wants to exclude all products from a specific supplier (e.g., temporary unavailability)
|
||||||
|
- Add category with 100 products → add problematic supplier to excluded_supplier_ids
|
||||||
|
- Result: All products from that supplier are excluded, even if directly included
|
||||||
|
- Combined workflow: Category inclusion + supplier exclusion + individual product exclusion
|
||||||
|
|
||||||
|
## [18.0.1.4.0] - 2026-02-22
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Product Blacklist Feature**: New exclusion system for group orders
|
||||||
|
- New field: `excluded_product_ids` (Many2many to product.product)
|
||||||
|
- Blacklist has absolute priority over all inclusion sources (product_ids, category_ids, supplier_ids)
|
||||||
|
- Model method: Updated `_get_products_for_group_order()` with blacklist filter
|
||||||
|
- Comprehensive test suite in `test_product_discovery.py` (TestProductBlacklist class)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **UI Improvements**: Renamed "Associations" section to "Catálogo de Productos" for better user clarity
|
||||||
|
- New subsection: "Productos Incluidos" (whitelist: suppliers, categories, direct products)
|
||||||
|
- New subsection: "Productos Excluidos" (blacklist: explicit exclusions)
|
||||||
|
- Updated help texts for all inclusion fields
|
||||||
|
- Complete Spanish and Euskera translations
|
||||||
|
|
||||||
|
- **Product Discovery Logic**:
|
||||||
|
- `_get_products_for_group_order()` now applies `excluded_product_ids` filter at the end
|
||||||
|
- Products in blacklist never appear, regardless of inclusion source
|
||||||
|
- `_compute_available_products_count()` now depends on `excluded_product_ids`
|
||||||
|
- Detailed logging for excluded product count
|
||||||
|
|
||||||
|
### Technical Details
|
||||||
|
- New M2M relation: `group_order_excluded_product_rel` (separate from whitelist relations)
|
||||||
|
- Blacklist filter uses set subtraction: `products = products - order.excluded_product_ids`
|
||||||
|
- All tests validate absolute priority: direct products, category products, supplier products, and multi-source products all respect blacklist
|
||||||
|
|
||||||
|
### Use Case
|
||||||
|
- Admin selects a category with 100 products → adds to category_ids
|
||||||
|
- Admin identifies 5 unwanted products → adds to excluded_product_ids
|
||||||
|
- Result: 95 products available in the order
|
||||||
|
- Workflow: Bulk inclusion via categories/suppliers + fine-grained exclusion via blacklist
|
||||||
|
|
||||||
## [18.0.1.3.0] - 2026-02-16
|
## [18.0.1.3.0] - 2026-02-16
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
{ # noqa: B018
|
{ # noqa: B018
|
||||||
"name": "Website Sale - Aplicoop",
|
"name": "Website Sale - Aplicoop",
|
||||||
"version": "18.0.1.3.1",
|
"version": "18.0.1.6.0",
|
||||||
"category": "Website/Sale",
|
"category": "Website/Sale",
|
||||||
"summary": "Modern replacement of legacy Aplicoop - Collaborative consumption group orders",
|
"summary": "Modern replacement of legacy Aplicoop - Collaborative consumption group orders",
|
||||||
"author": "Odoo Community Association (OCA), Criptomart",
|
"author": "Odoo Community Association (OCA), Criptomart",
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -188,6 +188,31 @@ class GroupOrder(models.Model):
|
||||||
tracking=True,
|
tracking=True,
|
||||||
help="Products in these categories will be available",
|
help="Products in these categories will be available",
|
||||||
)
|
)
|
||||||
|
excluded_product_ids = fields.Many2many(
|
||||||
|
"product.product",
|
||||||
|
"group_order_excluded_product_rel",
|
||||||
|
"order_id",
|
||||||
|
"product_id",
|
||||||
|
tracking=True,
|
||||||
|
help="Products explicitly excluded from this order (blacklist has absolute priority)",
|
||||||
|
)
|
||||||
|
excluded_supplier_ids = fields.Many2many(
|
||||||
|
"res.partner",
|
||||||
|
"group_order_excluded_supplier_rel",
|
||||||
|
"order_id",
|
||||||
|
"supplier_id",
|
||||||
|
domain=[("supplier_rank", ">", 0)],
|
||||||
|
tracking=True,
|
||||||
|
help="Suppliers excluded from this order. Products with these suppliers as main seller will not be available (blacklist has absolute priority)",
|
||||||
|
)
|
||||||
|
excluded_category_ids = fields.Many2many(
|
||||||
|
"product.category",
|
||||||
|
"group_order_excluded_category_rel",
|
||||||
|
"order_id",
|
||||||
|
"category_id",
|
||||||
|
tracking=True,
|
||||||
|
help="Categories excluded from this order. Products in these categories and all their subcategories will not be available (blacklist has absolute priority)",
|
||||||
|
)
|
||||||
|
|
||||||
# === Estado ===
|
# === Estado ===
|
||||||
state = fields.Selection(
|
state = fields.Selection(
|
||||||
|
|
@ -233,7 +258,14 @@ class GroupOrder(models.Model):
|
||||||
help="Total count of available products from all sources",
|
help="Total count of available products from all sources",
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.depends("product_ids", "category_ids", "supplier_ids")
|
@api.depends(
|
||||||
|
"product_ids",
|
||||||
|
"category_ids",
|
||||||
|
"supplier_ids",
|
||||||
|
"excluded_product_ids",
|
||||||
|
"excluded_supplier_ids",
|
||||||
|
"excluded_category_ids",
|
||||||
|
)
|
||||||
def _compute_available_products_count(self):
|
def _compute_available_products_count(self):
|
||||||
"""Count all available products from all sources."""
|
"""Count all available products from all sources."""
|
||||||
for record in self:
|
for record in self:
|
||||||
|
|
@ -387,6 +419,64 @@ class GroupOrder(models.Model):
|
||||||
).filtered("active")
|
).filtered("active")
|
||||||
products |= supplier_products
|
products |= supplier_products
|
||||||
|
|
||||||
|
# 4) Apply product blacklist filter (absolute priority)
|
||||||
|
if order.excluded_product_ids:
|
||||||
|
excluded_count = len(products & order.excluded_product_ids)
|
||||||
|
products = products - order.excluded_product_ids
|
||||||
|
_logger.info(
|
||||||
|
"Group order %d: Excluded %d products from product blacklist (total: %d)",
|
||||||
|
order.id,
|
||||||
|
excluded_count,
|
||||||
|
len(products),
|
||||||
|
)
|
||||||
|
|
||||||
|
# 5) Apply supplier blacklist filter (absolute priority)
|
||||||
|
# Exclude products whose main seller is in the excluded suppliers list
|
||||||
|
if order.excluded_supplier_ids:
|
||||||
|
# Filter products where main_seller_id is in excluded_supplier_ids
|
||||||
|
excluded_by_supplier = products.filtered(
|
||||||
|
lambda p: p.product_tmpl_id.main_seller_id
|
||||||
|
and p.product_tmpl_id.main_seller_id in order.excluded_supplier_ids
|
||||||
|
)
|
||||||
|
if excluded_by_supplier:
|
||||||
|
products = products - excluded_by_supplier
|
||||||
|
_logger.info(
|
||||||
|
"Group order %d: Excluded %d products from supplier blacklist (main sellers: %s) (total: %d)",
|
||||||
|
order.id,
|
||||||
|
len(excluded_by_supplier),
|
||||||
|
", ".join(order.excluded_supplier_ids.mapped("name")),
|
||||||
|
len(products),
|
||||||
|
)
|
||||||
|
|
||||||
|
# 6) Apply category blacklist filter (absolute priority)
|
||||||
|
# Exclude products in excluded categories and all their subcategories (recursive)
|
||||||
|
if order.excluded_category_ids:
|
||||||
|
# Collect all excluded category IDs including descendants
|
||||||
|
excluded_cat_ids = []
|
||||||
|
|
||||||
|
def get_all_excluded_descendants(categories):
|
||||||
|
"""Recursively collect all excluded category IDs including children."""
|
||||||
|
for cat in categories:
|
||||||
|
excluded_cat_ids.append(cat.id)
|
||||||
|
if cat.child_id:
|
||||||
|
get_all_excluded_descendants(cat.child_id)
|
||||||
|
|
||||||
|
get_all_excluded_descendants(order.excluded_category_ids)
|
||||||
|
|
||||||
|
# Filter products whose category is in the excluded list
|
||||||
|
excluded_by_category = products.filtered(
|
||||||
|
lambda p: p.categ_id.id in excluded_cat_ids
|
||||||
|
)
|
||||||
|
if excluded_by_category:
|
||||||
|
products = products - excluded_by_category
|
||||||
|
_logger.info(
|
||||||
|
"Group order %d: Excluded %d products from category blacklist (categories: %s, including subcategories) (total: %d)",
|
||||||
|
order.id,
|
||||||
|
len(excluded_by_category),
|
||||||
|
", ".join(order.excluded_category_ids.mapped("name")),
|
||||||
|
len(products),
|
||||||
|
)
|
||||||
|
|
||||||
return products
|
return products
|
||||||
|
|
||||||
def _get_products_paginated(self, order_id, page=1, per_page=20):
|
def _get_products_paginated(self, order_id, page=1, per_page=20):
|
||||||
|
|
|
||||||
|
|
@ -497,3 +497,810 @@ class TestProductDiscoveryOrdering(TransactionCase):
|
||||||
discovered_ids = {p.id for p in discovered}
|
discovered_ids = {p.id for p in discovered}
|
||||||
expected_ids = {p.id for p in self.products}
|
expected_ids = {p.id for p in self.products}
|
||||||
self.assertEqual(discovered_ids, expected_ids)
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
class TestSupplierBlacklist(TransactionCase):
|
||||||
|
"""Test supplier blacklist (excluded_supplier_ids) functionality.
|
||||||
|
|
||||||
|
The supplier blacklist filters out products whose main_seller_id
|
||||||
|
(from product_main_seller addon) is in the excluded suppliers list.
|
||||||
|
|
||||||
|
Blacklist has absolute priority over inclusion sources.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.group = self.env["res.partner"].create(
|
||||||
|
{
|
||||||
|
"name": "Test Group",
|
||||||
|
"is_company": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create suppliers
|
||||||
|
self.supplier_A = self.env["res.partner"].create(
|
||||||
|
{
|
||||||
|
"name": "Supplier A",
|
||||||
|
"is_company": True,
|
||||||
|
"supplier_rank": 1,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.supplier_B = self.env["res.partner"].create(
|
||||||
|
{
|
||||||
|
"name": "Supplier B",
|
||||||
|
"is_company": True,
|
||||||
|
"supplier_rank": 1,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.supplier_C = self.env["res.partner"].create(
|
||||||
|
{
|
||||||
|
"name": "Supplier C",
|
||||||
|
"is_company": True,
|
||||||
|
"supplier_rank": 1,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create category
|
||||||
|
self.category = self.env["product.category"].create(
|
||||||
|
{
|
||||||
|
"name": "Test Category",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create products with different main sellers
|
||||||
|
# Product 1: main seller = Supplier A
|
||||||
|
tmpl_1 = self.env["product.template"].create(
|
||||||
|
{
|
||||||
|
"name": "Product from Supplier A",
|
||||||
|
"type": "consu",
|
||||||
|
"list_price": 10.0,
|
||||||
|
"categ_id": self.category.id,
|
||||||
|
"is_published": True,
|
||||||
|
"sale_ok": True,
|
||||||
|
"main_seller_id": self.supplier_A.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.product_A = tmpl_1.product_variant_ids[0]
|
||||||
|
|
||||||
|
# Product 2: main seller = Supplier B
|
||||||
|
tmpl_2 = self.env["product.template"].create(
|
||||||
|
{
|
||||||
|
"name": "Product from Supplier B",
|
||||||
|
"type": "consu",
|
||||||
|
"list_price": 20.0,
|
||||||
|
"categ_id": self.category.id,
|
||||||
|
"is_published": True,
|
||||||
|
"sale_ok": True,
|
||||||
|
"main_seller_id": self.supplier_B.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.product_B = tmpl_2.product_variant_ids[0]
|
||||||
|
|
||||||
|
# Product 3: main seller = Supplier C
|
||||||
|
tmpl_3 = self.env["product.template"].create(
|
||||||
|
{
|
||||||
|
"name": "Product from Supplier C",
|
||||||
|
"type": "consu",
|
||||||
|
"list_price": 30.0,
|
||||||
|
"categ_id": self.category.id,
|
||||||
|
"is_published": True,
|
||||||
|
"sale_ok": True,
|
||||||
|
"main_seller_id": self.supplier_C.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.product_C = tmpl_3.product_variant_ids[0]
|
||||||
|
|
||||||
|
# Product 4: no main seller
|
||||||
|
tmpl_4 = self.env["product.template"].create(
|
||||||
|
{
|
||||||
|
"name": "Product without main seller",
|
||||||
|
"type": "consu",
|
||||||
|
"list_price": 40.0,
|
||||||
|
"categ_id": self.category.id,
|
||||||
|
"is_published": True,
|
||||||
|
"sale_ok": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.product_no_seller = tmpl_4.product_variant_ids[0]
|
||||||
|
|
||||||
|
start_date = datetime.now().date()
|
||||||
|
self.group_order = self.env["group.order"].create(
|
||||||
|
{
|
||||||
|
"name": "Test Supplier 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_supplier_blacklist_excludes_by_main_seller(self):
|
||||||
|
"""Test that supplier blacklist excludes products by main_seller_id."""
|
||||||
|
# Add all products via category
|
||||||
|
self.group_order.category_ids = [(4, self.category.id)]
|
||||||
|
|
||||||
|
# All products should be discoverable initially
|
||||||
|
products = self.group_order._get_products_for_group_order(self.group_order.id)
|
||||||
|
self.assertIn(self.product_A, products)
|
||||||
|
self.assertIn(self.product_B, products)
|
||||||
|
self.assertIn(self.product_C, products)
|
||||||
|
self.assertIn(self.product_no_seller, products)
|
||||||
|
|
||||||
|
# Exclude Supplier A
|
||||||
|
self.group_order.excluded_supplier_ids = [(4, self.supplier_A.id)]
|
||||||
|
|
||||||
|
# Product A should NOT be discoverable anymore
|
||||||
|
products = self.group_order._get_products_for_group_order(self.group_order.id)
|
||||||
|
self.assertNotIn(self.product_A, products)
|
||||||
|
self.assertIn(self.product_B, products)
|
||||||
|
self.assertIn(self.product_C, products)
|
||||||
|
self.assertIn(self.product_no_seller, products)
|
||||||
|
|
||||||
|
def test_supplier_blacklist_multiple_suppliers(self):
|
||||||
|
"""Test excluding multiple suppliers at once."""
|
||||||
|
# Add all products via category
|
||||||
|
self.group_order.category_ids = [(4, self.category.id)]
|
||||||
|
|
||||||
|
# Exclude Suppliers A and B
|
||||||
|
self.group_order.excluded_supplier_ids = [
|
||||||
|
(4, self.supplier_A.id),
|
||||||
|
(4, self.supplier_B.id),
|
||||||
|
]
|
||||||
|
|
||||||
|
products = self.group_order._get_products_for_group_order(self.group_order.id)
|
||||||
|
|
||||||
|
# Products A and B should NOT be in results
|
||||||
|
self.assertNotIn(self.product_A, products)
|
||||||
|
self.assertNotIn(self.product_B, products)
|
||||||
|
|
||||||
|
# But product C and no-seller product should be there
|
||||||
|
self.assertIn(self.product_C, products)
|
||||||
|
self.assertIn(self.product_no_seller, products)
|
||||||
|
|
||||||
|
def test_supplier_blacklist_does_not_affect_no_main_seller(self):
|
||||||
|
"""Test that products without main_seller_id are not affected by supplier blacklist."""
|
||||||
|
# Add all products via category
|
||||||
|
self.group_order.category_ids = [(4, self.category.id)]
|
||||||
|
|
||||||
|
# Exclude all suppliers
|
||||||
|
self.group_order.excluded_supplier_ids = [
|
||||||
|
(4, self.supplier_A.id),
|
||||||
|
(4, self.supplier_B.id),
|
||||||
|
(4, self.supplier_C.id),
|
||||||
|
]
|
||||||
|
|
||||||
|
products = self.group_order._get_products_for_group_order(self.group_order.id)
|
||||||
|
|
||||||
|
# Products with main sellers should NOT be in results
|
||||||
|
self.assertNotIn(self.product_A, products)
|
||||||
|
self.assertNotIn(self.product_B, products)
|
||||||
|
self.assertNotIn(self.product_C, products)
|
||||||
|
|
||||||
|
# But product without main seller should still be there
|
||||||
|
self.assertIn(self.product_no_seller, products)
|
||||||
|
|
||||||
|
def test_supplier_blacklist_with_direct_product_inclusion(self):
|
||||||
|
"""Test that supplier blacklist affects even directly included products."""
|
||||||
|
# Add product A directly
|
||||||
|
self.group_order.product_ids = [(4, self.product_A.id)]
|
||||||
|
|
||||||
|
# Product should be discoverable
|
||||||
|
products = self.group_order._get_products_for_group_order(self.group_order.id)
|
||||||
|
self.assertIn(self.product_A, products)
|
||||||
|
|
||||||
|
# Exclude Supplier A
|
||||||
|
self.group_order.excluded_supplier_ids = [(4, self.supplier_A.id)]
|
||||||
|
|
||||||
|
# Product A should NOT be discoverable anymore, even though directly included
|
||||||
|
products = self.group_order._get_products_for_group_order(self.group_order.id)
|
||||||
|
self.assertNotIn(self.product_A, products)
|
||||||
|
|
||||||
|
def test_supplier_blacklist_with_supplier_inclusion(self):
|
||||||
|
"""Test that supplier blacklist has priority over supplier inclusion."""
|
||||||
|
# Add Supplier A to included suppliers
|
||||||
|
self.group_order.supplier_ids = [(4, self.supplier_A.id)]
|
||||||
|
|
||||||
|
# Also add Supplier A to excluded suppliers (blacklist has priority)
|
||||||
|
self.group_order.excluded_supplier_ids = [(4, self.supplier_A.id)]
|
||||||
|
|
||||||
|
products = self.group_order._get_products_for_group_order(self.group_order.id)
|
||||||
|
|
||||||
|
# Product A should NOT be discoverable (blacklist wins)
|
||||||
|
self.assertNotIn(self.product_A, products)
|
||||||
|
|
||||||
|
def test_empty_supplier_blacklist_no_effect(self):
|
||||||
|
"""Test that empty excluded_supplier_ids doesn't affect discovery."""
|
||||||
|
# Add products via category
|
||||||
|
self.group_order.category_ids = [(4, self.category.id)]
|
||||||
|
|
||||||
|
# All products should be discoverable
|
||||||
|
products = self.group_order._get_products_for_group_order(self.group_order.id)
|
||||||
|
self.assertIn(self.product_A, products)
|
||||||
|
self.assertIn(self.product_B, products)
|
||||||
|
self.assertIn(self.product_C, products)
|
||||||
|
|
||||||
|
# Excluded supplier list is empty - should have no effect
|
||||||
|
self.assertEqual(len(self.group_order.excluded_supplier_ids), 0)
|
||||||
|
|
||||||
|
def test_supplier_and_product_blacklist_combined(self):
|
||||||
|
"""Test that both product and supplier blacklists work together."""
|
||||||
|
# Add all products via category
|
||||||
|
self.group_order.category_ids = [(4, self.category.id)]
|
||||||
|
|
||||||
|
# Exclude Supplier A (affects product_A)
|
||||||
|
self.group_order.excluded_supplier_ids = [(4, self.supplier_A.id)]
|
||||||
|
|
||||||
|
# Exclude product B directly
|
||||||
|
self.group_order.excluded_product_ids = [(4, self.product_B.id)]
|
||||||
|
|
||||||
|
products = self.group_order._get_products_for_group_order(self.group_order.id)
|
||||||
|
|
||||||
|
# Products A and B should NOT be in results
|
||||||
|
self.assertNotIn(self.product_A, products)
|
||||||
|
self.assertNotIn(self.product_B, products)
|
||||||
|
|
||||||
|
# But products C and no-seller should be there
|
||||||
|
self.assertIn(self.product_C, products)
|
||||||
|
self.assertIn(self.product_no_seller, products)
|
||||||
|
|
||||||
|
def test_supplier_blacklist_available_products_count(self):
|
||||||
|
"""Test that available_products_count reflects supplier blacklist."""
|
||||||
|
# Add products
|
||||||
|
self.group_order.category_ids = [(4, self.category.id)]
|
||||||
|
|
||||||
|
# Count should include all 4 products
|
||||||
|
initial_count = self.group_order.available_products_count
|
||||||
|
self.assertEqual(initial_count, 4)
|
||||||
|
|
||||||
|
# Exclude Supplier A
|
||||||
|
self.group_order.excluded_supplier_ids = [(4, self.supplier_A.id)]
|
||||||
|
|
||||||
|
# Count should decrease by 1 (product_A excluded)
|
||||||
|
new_count = self.group_order.available_products_count
|
||||||
|
self.assertEqual(new_count, 3)
|
||||||
|
|
||||||
|
|
||||||
|
class TestCategoryBlacklist(TransactionCase):
|
||||||
|
"""Test category blacklist (excluded_category_ids) functionality.
|
||||||
|
|
||||||
|
The category blacklist filters out products in the excluded categories
|
||||||
|
AND all their subcategories (recursive).
|
||||||
|
|
||||||
|
Blacklist has absolute priority over inclusion sources.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.group = self.env["res.partner"].create(
|
||||||
|
{
|
||||||
|
"name": "Test Group",
|
||||||
|
"is_company": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create category hierarchy:
|
||||||
|
# Parent Category
|
||||||
|
# ├── Child Category A
|
||||||
|
# │ └── Grandchild Category A1
|
||||||
|
# └── Child Category B
|
||||||
|
self.parent_category = self.env["product.category"].create(
|
||||||
|
{
|
||||||
|
"name": "Parent Category",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.child_category_A = self.env["product.category"].create(
|
||||||
|
{
|
||||||
|
"name": "Child Category A",
|
||||||
|
"parent_id": self.parent_category.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.grandchild_category_A1 = self.env["product.category"].create(
|
||||||
|
{
|
||||||
|
"name": "Grandchild Category A1",
|
||||||
|
"parent_id": self.child_category_A.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.child_category_B = self.env["product.category"].create(
|
||||||
|
{
|
||||||
|
"name": "Child Category B",
|
||||||
|
"parent_id": self.parent_category.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.other_category = self.env["product.category"].create(
|
||||||
|
{
|
||||||
|
"name": "Other Category (not in hierarchy)",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create products in different categories
|
||||||
|
# Product in parent category
|
||||||
|
self.product_parent = self.env["product.product"].create(
|
||||||
|
{
|
||||||
|
"name": "Product in Parent Category",
|
||||||
|
"type": "consu",
|
||||||
|
"list_price": 10.0,
|
||||||
|
"categ_id": self.parent_category.id,
|
||||||
|
"is_published": True,
|
||||||
|
"sale_ok": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Product in Child A
|
||||||
|
self.product_child_A = self.env["product.product"].create(
|
||||||
|
{
|
||||||
|
"name": "Product in Child A",
|
||||||
|
"type": "consu",
|
||||||
|
"list_price": 20.0,
|
||||||
|
"categ_id": self.child_category_A.id,
|
||||||
|
"is_published": True,
|
||||||
|
"sale_ok": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Product in Grandchild A1
|
||||||
|
self.product_grandchild_A1 = self.env["product.product"].create(
|
||||||
|
{
|
||||||
|
"name": "Product in Grandchild A1",
|
||||||
|
"type": "consu",
|
||||||
|
"list_price": 30.0,
|
||||||
|
"categ_id": self.grandchild_category_A1.id,
|
||||||
|
"is_published": True,
|
||||||
|
"sale_ok": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Product in Child B
|
||||||
|
self.product_child_B = self.env["product.product"].create(
|
||||||
|
{
|
||||||
|
"name": "Product in Child B",
|
||||||
|
"type": "consu",
|
||||||
|
"list_price": 40.0,
|
||||||
|
"categ_id": self.child_category_B.id,
|
||||||
|
"is_published": True,
|
||||||
|
"sale_ok": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Product in Other Category
|
||||||
|
self.product_other = self.env["product.product"].create(
|
||||||
|
{
|
||||||
|
"name": "Product in Other Category",
|
||||||
|
"type": "consu",
|
||||||
|
"list_price": 50.0,
|
||||||
|
"categ_id": self.other_category.id,
|
||||||
|
"is_published": True,
|
||||||
|
"sale_ok": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
start_date = datetime.now().date()
|
||||||
|
self.group_order = self.env["group.order"].create(
|
||||||
|
{
|
||||||
|
"name": "Test Category 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_category_blacklist_excludes_single_category(self):
|
||||||
|
"""Test that category blacklist excludes products in that category."""
|
||||||
|
# Add parent category to inclusion (includes all products in hierarchy)
|
||||||
|
self.group_order.category_ids = [(4, self.parent_category.id)]
|
||||||
|
|
||||||
|
# All products should be discoverable initially
|
||||||
|
products = self.group_order._get_products_for_group_order(self.group_order.id)
|
||||||
|
self.assertIn(self.product_parent, products)
|
||||||
|
self.assertIn(self.product_child_A, products)
|
||||||
|
self.assertIn(self.product_grandchild_A1, products)
|
||||||
|
self.assertIn(self.product_child_B, products)
|
||||||
|
|
||||||
|
# Exclude Child Category B
|
||||||
|
self.group_order.excluded_category_ids = [(4, self.child_category_B.id)]
|
||||||
|
|
||||||
|
# Product in Child B should NOT be discoverable
|
||||||
|
products = self.group_order._get_products_for_group_order(self.group_order.id)
|
||||||
|
self.assertNotIn(self.product_child_B, products)
|
||||||
|
# But others should still be there
|
||||||
|
self.assertIn(self.product_parent, products)
|
||||||
|
self.assertIn(self.product_child_A, products)
|
||||||
|
self.assertIn(self.product_grandchild_A1, products)
|
||||||
|
|
||||||
|
def test_category_blacklist_excludes_with_subcategories(self):
|
||||||
|
"""Test that excluding a category also excludes all its subcategories (recursive)."""
|
||||||
|
# Add parent category to inclusion
|
||||||
|
self.group_order.category_ids = [(4, self.parent_category.id)]
|
||||||
|
|
||||||
|
# Exclude Child Category A (should also exclude Grandchild A1)
|
||||||
|
self.group_order.excluded_category_ids = [(4, self.child_category_A.id)]
|
||||||
|
|
||||||
|
products = self.group_order._get_products_for_group_order(self.group_order.id)
|
||||||
|
|
||||||
|
# Products in Child A and Grandchild A1 should NOT be discoverable
|
||||||
|
self.assertNotIn(self.product_child_A, products)
|
||||||
|
self.assertNotIn(self.product_grandchild_A1, products) # Recursive exclusion
|
||||||
|
|
||||||
|
# But products in parent and Child B should still be there
|
||||||
|
self.assertIn(self.product_parent, products)
|
||||||
|
self.assertIn(self.product_child_B, products)
|
||||||
|
|
||||||
|
def test_category_blacklist_excludes_parent_excludes_all_children(self):
|
||||||
|
"""Test that excluding parent category excludes ALL descendants."""
|
||||||
|
# Add parent category to inclusion (includes all)
|
||||||
|
self.group_order.category_ids = [(4, self.parent_category.id)]
|
||||||
|
|
||||||
|
# Exclude the parent category (should exclude ALL products in hierarchy)
|
||||||
|
self.group_order.excluded_category_ids = [(4, self.parent_category.id)]
|
||||||
|
|
||||||
|
products = self.group_order._get_products_for_group_order(self.group_order.id)
|
||||||
|
|
||||||
|
# ALL products in the hierarchy should be excluded
|
||||||
|
self.assertNotIn(self.product_parent, products)
|
||||||
|
self.assertNotIn(self.product_child_A, products)
|
||||||
|
self.assertNotIn(self.product_grandchild_A1, products)
|
||||||
|
self.assertNotIn(self.product_child_B, products)
|
||||||
|
|
||||||
|
# Result should be empty (no products available)
|
||||||
|
self.assertEqual(len(products), 0)
|
||||||
|
|
||||||
|
def test_category_blacklist_with_direct_product_inclusion(self):
|
||||||
|
"""Test that category blacklist affects even directly included products."""
|
||||||
|
# Add product directly
|
||||||
|
self.group_order.product_ids = [(4, self.product_child_A.id)]
|
||||||
|
|
||||||
|
# Product should be discoverable
|
||||||
|
products = self.group_order._get_products_for_group_order(self.group_order.id)
|
||||||
|
self.assertIn(self.product_child_A, products)
|
||||||
|
|
||||||
|
# Exclude the category
|
||||||
|
self.group_order.excluded_category_ids = [(4, self.child_category_A.id)]
|
||||||
|
|
||||||
|
# Product should NOT be discoverable anymore, even though directly included
|
||||||
|
products = self.group_order._get_products_for_group_order(self.group_order.id)
|
||||||
|
self.assertNotIn(self.product_child_A, products)
|
||||||
|
|
||||||
|
def test_category_blacklist_does_not_affect_other_categories(self):
|
||||||
|
"""Test that excluding a category doesn't affect products in unrelated categories."""
|
||||||
|
# Add all categories to inclusion
|
||||||
|
self.group_order.category_ids = [
|
||||||
|
(4, self.parent_category.id),
|
||||||
|
(4, self.other_category.id),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Exclude parent category
|
||||||
|
self.group_order.excluded_category_ids = [(4, self.parent_category.id)]
|
||||||
|
|
||||||
|
products = self.group_order._get_products_for_group_order(self.group_order.id)
|
||||||
|
|
||||||
|
# Products in parent hierarchy should NOT be there
|
||||||
|
self.assertNotIn(self.product_parent, products)
|
||||||
|
self.assertNotIn(self.product_child_A, products)
|
||||||
|
self.assertNotIn(self.product_child_B, products)
|
||||||
|
|
||||||
|
# But product in other category should still be there
|
||||||
|
self.assertIn(self.product_other, products)
|
||||||
|
|
||||||
|
def test_empty_category_blacklist_no_effect(self):
|
||||||
|
"""Test that empty excluded_category_ids doesn't affect discovery."""
|
||||||
|
# Add parent category
|
||||||
|
self.group_order.category_ids = [(4, self.parent_category.id)]
|
||||||
|
|
||||||
|
# All products should be discoverable
|
||||||
|
products = self.group_order._get_products_for_group_order(self.group_order.id)
|
||||||
|
self.assertIn(self.product_parent, products)
|
||||||
|
self.assertIn(self.product_child_A, products)
|
||||||
|
self.assertIn(self.product_grandchild_A1, products)
|
||||||
|
self.assertIn(self.product_child_B, products)
|
||||||
|
|
||||||
|
# Excluded category list is empty - should have no effect
|
||||||
|
self.assertEqual(len(self.group_order.excluded_category_ids), 0)
|
||||||
|
|
||||||
|
def test_multiple_category_exclusions(self):
|
||||||
|
"""Test excluding multiple categories at once."""
|
||||||
|
# Add parent category
|
||||||
|
self.group_order.category_ids = [(4, self.parent_category.id)]
|
||||||
|
|
||||||
|
# Exclude both child categories
|
||||||
|
self.group_order.excluded_category_ids = [
|
||||||
|
(4, self.child_category_A.id),
|
||||||
|
(4, self.child_category_B.id),
|
||||||
|
]
|
||||||
|
|
||||||
|
products = self.group_order._get_products_for_group_order(self.group_order.id)
|
||||||
|
|
||||||
|
# Products in both child categories (+ grandchild) should NOT be there
|
||||||
|
self.assertNotIn(self.product_child_A, products)
|
||||||
|
self.assertNotIn(self.product_grandchild_A1, products)
|
||||||
|
self.assertNotIn(self.product_child_B, products)
|
||||||
|
|
||||||
|
# But product in parent category should still be there
|
||||||
|
self.assertIn(self.product_parent, products)
|
||||||
|
|
||||||
|
def test_category_blacklist_combined_with_other_blacklists(self):
|
||||||
|
"""Test that category blacklist works together with product and supplier blacklists."""
|
||||||
|
# Add parent category
|
||||||
|
self.group_order.category_ids = [(4, self.parent_category.id)]
|
||||||
|
|
||||||
|
# Exclude Child A category (affects product_child_A and product_grandchild_A1)
|
||||||
|
self.group_order.excluded_category_ids = [(4, self.child_category_A.id)]
|
||||||
|
|
||||||
|
# Also exclude product_child_B directly
|
||||||
|
self.group_order.excluded_product_ids = [(4, self.product_child_B.id)]
|
||||||
|
|
||||||
|
products = self.group_order._get_products_for_group_order(self.group_order.id)
|
||||||
|
|
||||||
|
# Products excluded by category blacklist
|
||||||
|
self.assertNotIn(self.product_child_A, products)
|
||||||
|
self.assertNotIn(self.product_grandchild_A1, products)
|
||||||
|
|
||||||
|
# Product excluded by product blacklist
|
||||||
|
self.assertNotIn(self.product_child_B, products)
|
||||||
|
|
||||||
|
# Only product_parent should be available
|
||||||
|
self.assertIn(self.product_parent, products)
|
||||||
|
self.assertEqual(len(products), 1)
|
||||||
|
|
||||||
|
def test_category_blacklist_available_products_count(self):
|
||||||
|
"""Test that available_products_count reflects category blacklist."""
|
||||||
|
# Add parent category (4 products in hierarchy)
|
||||||
|
self.group_order.category_ids = [(4, self.parent_category.id)]
|
||||||
|
|
||||||
|
# Count should include all 4 products
|
||||||
|
initial_count = self.group_order.available_products_count
|
||||||
|
self.assertEqual(initial_count, 4)
|
||||||
|
|
||||||
|
# Exclude Child Category A (should exclude 2 products: child_A + grandchild_A1)
|
||||||
|
self.group_order.excluded_category_ids = [(4, self.child_category_A.id)]
|
||||||
|
|
||||||
|
# Count should decrease by 2
|
||||||
|
new_count = self.group_order.available_products_count
|
||||||
|
self.assertEqual(new_count, 2)
|
||||||
|
|
|
||||||
|
|
@ -80,10 +80,17 @@
|
||||||
<group string="Delivery">
|
<group string="Delivery">
|
||||||
<field name="delivery_notice" placeholder="Information about home delivery..." nolabel="1"/>
|
<field name="delivery_notice" placeholder="Information about home delivery..." nolabel="1"/>
|
||||||
</group>
|
</group>
|
||||||
<group string="Associations">
|
<group string="Catálogo de Productos">
|
||||||
<field name="supplier_ids" widget="many2many_tags" help="Products from these suppliers will be available"/>
|
<group string="Productos Incluidos" col="2">
|
||||||
<field name="product_ids" widget="many2many_tags" help="Directly assigned products (highest priority)"/>
|
<field name="supplier_ids" widget="many2many_tags" help="All products from these suppliers will be included"/>
|
||||||
<field name="category_ids" widget="many2many_tags" help="Products in these categories will be available"/>
|
<field name="category_ids" widget="many2many_tags" help="All products in these categories (including subcategories) will be included"/>
|
||||||
|
<field name="product_ids" widget="many2many_tags" help="Specific products to include directly"/>
|
||||||
|
</group>
|
||||||
|
<group string="Productos Excluidos" col="2">
|
||||||
|
<field name="excluded_supplier_ids" widget="many2many_tags" help="Suppliers excluded from this order. Products with these suppliers as main seller will not be available (blacklist has absolute priority)"/>
|
||||||
|
<field name="excluded_category_ids" widget="many2many_tags" help="Categories excluded from this order. Products in these categories and all their subcategories will not be available (blacklist has absolute priority)"/>
|
||||||
|
<field name="excluded_product_ids" widget="many2many_tags" help="Products explicitly excluded from this order (blacklist has absolute priority over inclusions)"/>
|
||||||
|
</group>
|
||||||
</group>
|
</group>
|
||||||
</sheet>
|
</sheet>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue