[FIX] website_sale_aplicoop: Remove redundant string= attributes and fix OCA linting warnings

- Remove redundant string= from 17 field definitions where name matches string value (W8113)
- Convert @staticmethod to instance methods in selection methods for proper self.env._() access
- Fix W8161 (prefer-env-translation) by using self.env._() instead of standalone _()
- Fix W8301/W8115 (translation-not-lazy) by proper placement of % interpolation outside self.env._()
- Remove unused imports of odoo._ from group_order.py and sale_order_extension.py
- All OCA linting warnings in website_sale_aplicoop main models are now resolved

Changes:
- website_sale_aplicoop/models/group_order.py: 21 field definitions cleaned
- website_sale_aplicoop/models/sale_order_extension.py: 5 field definitions cleaned + @staticmethod conversion
- Consistent with OCA standards for addon submission
This commit is contained in:
snt 2026-02-18 17:54:43 +01:00
parent 5c89795e30
commit 6fbc7b9456
73 changed files with 5386 additions and 4354 deletions

View file

@ -1,7 +1,7 @@
# BEFORE & AFTER - Error Fixes
**Document**: Visual comparison of all changes made to fix installation errors
**Date**: 10 de febrero de 2026
**Document**: Visual comparison of all changes made to fix installation errors
**Date**: 10 de febrero de 2026
**Status**: ✅ All fixed and working
---
@ -146,7 +146,7 @@ class ResPartner(models.Model):
product_count = self.env['product.template'].search_count([
('default_supplier_id', '=', self.id)
])
# ... rest of method
```
@ -179,7 +179,7 @@ class ResPartner(models.Model):
product_count = self.env['product.template'].search_count([
('default_supplier_id', '=', self.id)
])
# ... rest of method
```
@ -315,8 +315,8 @@ class WizardUpdateProductCategory(models.TransientModel):
| `models/res_partner.py` | `_()` in field def | 2 `_()` calls | Removed | ✅ Fixed |
| `models/wizard_update_product_category.py` | `_()` in field defs | 4 `_()` calls | Removed | ✅ Fixed |
**Total Changes**: 8 modifications across 3 files
**Total Errors Fixed**: 2 categories (XPath + Translation)
**Total Changes**: 8 modifications across 3 files
**Total Errors Fixed**: 2 categories (XPath + Translation)
**Result**: ✅ **All fixed, addon working**
---
@ -325,11 +325,11 @@ class WizardUpdateProductCategory(models.TransientModel):
### Before (with errors):
```
2026-02-10 16:17:56,252 47 INFO odoo odoo.modules.registry: module product_price_category_supplier: creating or updating database tables
2026-02-10 16:17:56,344 47 INFO odoo odoo.modules.loading: loading product_price_category_supplier/security/ir.model.access.csv
2026-02-10 16:17:56,351 47 INFO odoo odoo.modules.loading: loading product_price_category_supplier/views/res_partner_views.xml
2026-02-10 16:17:56,362 47 WARNING odoo odoo.modules.loading: Transient module states were reset
2026-02-10 16:17:56,362 47 ERROR odoo odoo.modules.registry: Failed to load registry
2026-02-10 16:17:56,252 47 INFO odoo odoo.modules.registry: module product_price_category_supplier: creating or updating database tables
2026-02-10 16:17:56,344 47 INFO odoo odoo.modules.loading: loading product_price_category_supplier/security/ir.model.access.csv
2026-02-10 16:17:56,351 47 INFO odoo odoo.modules.loading: loading product_price_category_supplier/views/res_partner_views.xml
2026-02-10 16:17:56,362 47 WARNING odoo odoo.modules.loading: Transient module states were reset
2026-02-10 16:17:56,362 47 ERROR odoo odoo.modules.registry: Failed to load registry
2026-02-10 16:17:56,362 47 CRITICAL odoo odoo.service.server: Failed to initialize database `odoo`.
❌ ParseError: while parsing /mnt/extra-addons/product_price_category_supplier/views/res_partner_views.xml:4
```
@ -365,6 +365,6 @@ In Odoo 18.0 partner form:
---
**Document Status**: ✅ Complete
**Last Updated**: 10 de febrero de 2026
**Document Status**: ✅ Complete
**Last Updated**: 10 de febrero de 2026
**License**: AGPL-3.0

View file

@ -1,7 +1,7 @@
# ERROR FIX REPORT - product_price_category_supplier
**Date**: 10 de febrero de 2026
**Status**: ✅ FIXED & VERIFIED
**Date**: 10 de febrero de 2026
**Status**: ✅ FIXED & VERIFIED
**Author**: GitHub Copilot
---
@ -57,7 +57,7 @@ Element '<xpath expr="//notebook/page[@name='purchase']">' cannot be located in
**Warning Message**:
```
2026-02-10 16:17:56,165 47 WARNING odoo odoo.tools.translate: no translation language detected,
2026-02-10 16:17:56,165 47 WARNING odoo odoo.tools.translate: no translation language detected,
skipping translation <frame at ..., file '...wizard_update_product_category.py', line 21, code WizardUpdateProductCategory>
```
@ -262,9 +262,9 @@ docker-compose exec -T odoo odoo -d odoo -i product_price_category_supplier --st
## Summary of Changes
**Total Files Modified**: 3
**Total Changes**: 8
**Status**: ✅ All Fixed & Tested
**Total Files Modified**: 3
**Total Changes**: 8
**Status**: ✅ All Fixed & Tested
The addon is now **ready for production use** with proper:
- ✅ View inheritance (correct XPath paths)
@ -275,5 +275,5 @@ The addon is now **ready for production use** with proper:
---
**Maintained by**: Criptomart | **License**: AGPL-3.0
**Maintained by**: Criptomart | **License**: AGPL-3.0
**Last Updated**: 10 de febrero de 2026

View file

@ -1,8 +1,8 @@
# INSTALLATION COMPLETE - product_price_category_supplier
**Status**: ✅ **ADDON SUCCESSFULLY INSTALLED**
**Date**: 10 de febrero de 2026
**Version**: 18.0.1.0.0
**Status**: ✅ **ADDON SUCCESSFULLY INSTALLED**
**Date**: 10 de febrero de 2026
**Version**: 18.0.1.0.0
**License**: AGPL-3.0
---
@ -11,11 +11,11 @@
El addon `product_price_category_supplier` ha sido creado, corregido y **instalado exitosamente** en tu instancia Odoo 18.0.
**21 files created**
**3 files fixed** (XPath errors & translation issues)
**0 remaining errors**
**Database tables created**
**Translations loaded** (Spanish + Euskera)
✅ **21 files created**
**3 files fixed** (XPath errors & translation issues)
✅ **0 remaining errors**
✅ **Database tables created**
**Translations loaded** (Spanish + Euskera)
---
@ -29,7 +29,7 @@ El addon `product_price_category_supplier` ha sido creado, corregido y **instala
### Problem 2: Translation Warnings
- **Issue**: Uso de `_()` en definiciones de campos causaba warnings al importar módulo
- **Solution**: Removidos `_()` de field definitions (se extraen automáticamente)
- **Files**:
- **Files**:
- `models/res_partner.py` (1 cambio)
- `models/wizard_update_product_category.py` (4 cambios)
@ -177,22 +177,22 @@ python3 -m py_compile product_price_category_supplier/models/*.py
## Installation Output
```
2026-02-10 16:21:04,843 69 INFO odoo odoo.modules.loading:
2026-02-10 16:21:04,843 69 INFO odoo odoo.modules.loading:
loading product_price_category_supplier/security/ir.model.access.csv
2026-02-10 16:21:04,868 69 INFO odoo odoo.modules.loading:
2026-02-10 16:21:04,868 69 INFO odoo odoo.modules.loading:
loading product_price_category_supplier/views/res_partner_views.xml
2026-02-10 16:21:04,875 69 INFO odoo odoo.modules.loading:
2026-02-10 16:21:04,875 69 INFO odoo odoo.modules.loading:
loading product_price_category_supplier/views/wizard_update_product_category.xml
2026-02-10 16:21:04,876 69 INFO odoo odoo.addons.base.models.ir_module:
2026-02-10 16:21:04,876 69 INFO odoo odoo.addons.base.models.ir_module:
module product_price_category_supplier: loading translation file ...eu.po
2026-02-10 16:21:04,876 69 INFO odoo odoo.addons.base.models.ir_module:
2026-02-10 16:21:04,876 69 INFO odoo odoo.addons.base.models.ir_module:
module product_price_category_supplier: loading translation file ...es.po
2026-02-10 16:21:04,912 69 INFO odoo odoo.modules.loading:
2026-02-10 16:21:04,912 69 INFO odoo odoo.modules.loading:
Module product_price_category_supplier loaded in 0.68s, 179 queries
✅ No errors
@ -294,7 +294,7 @@ If you need to:
---
**Status**: ✅ Production Ready
**Created**: 10 de febrero de 2026
**License**: AGPL-3.0
**Status**: ✅ Production Ready
**Created**: 10 de febrero de 2026
**License**: AGPL-3.0
**Author**: Criptomart

View file

@ -1,8 +1,8 @@
# ✅ ADDON INSTALLATION STATUS REPORT
**Addon**: `product_price_category_supplier`
**Status**: ✅ **INSTALLED & WORKING**
**Date**: 10 de febrero de 2026
**Addon**: `product_price_category_supplier`
**Status**: ✅ **INSTALLED & WORKING**
**Date**: 10 de febrero de 2026
**Installation Time**: 2 cycles (fixed errors on 2nd attempt)
---
@ -11,7 +11,7 @@
El addon `product_price_category_supplier` fue creado exitosamente para extender Odoo 18.0 con funcionalidad de categorías de precio por proveedor.
**Ciclo 1**: Error ParseError en XPath (vista XML)
**Ciclo 1**: Error ParseError en XPath (vista XML)
**Ciclo 2**: ✅ Errores corregidos, addon instalado correctamente
---
@ -90,7 +90,7 @@ Error: Element '<xpath expr="//notebook/page[@name='purchase']">' cannot be loca
- <xpath expr="//notebook/page[@name='purchase']" position="inside">
+ <xpath expr="//page[@name='sales_purchases']" position="inside">
```
**File**: `views/res_partner_views.xml` line 11
**File**: `views/res_partner_views.xml` line 11
**Reason**: Odoo 18 partner form uses `sales_purchases` page name
### Fix 2: Field Name in Tree View
@ -98,7 +98,7 @@ Error: Element '<xpath expr="//notebook/page[@name='purchase']">' cannot be loca
- <field name="name" position="after">
+ <field name="complete_name" position="after">
```
**File**: `views/res_partner_views.xml` line 27
**File**: `views/res_partner_views.xml` line 27
**Reason**: Tree view uses `complete_name` as first field
### Fix 3: Remove _() from Partner Field
@ -108,7 +108,7 @@ Error: Element '<xpath expr="//notebook/page[@name='purchase']">' cannot be loca
+ string='Default Price Category',
+ help='Default price category for products from this supplier',
```
**File**: `models/res_partner.py` lines 13-15
**File**: `models/res_partner.py` lines 13-15
**Reason**: Automatic extraction, `_()` causes translation warnings
### Fix 4: Remove _() from Wizard Fields
@ -122,7 +122,7 @@ Error: Element '<xpath expr="//notebook/page[@name='purchase']">' cannot be loca
+ string='Price Category',
+ string='Number of Products',
```
**File**: `models/wizard_update_product_category.py` lines 15, 21, 27, 34
**File**: `models/wizard_update_product_category.py` lines 15, 21, 27, 34
**Reason**: Same as Fix 3 - automatic extraction by Odoo
---
@ -307,7 +307,7 @@ The addon is **production-ready** and fully functional.
---
**Created**: 10 de febrero de 2026
**Status**: ✅ Installation Complete
**License**: AGPL-3.0
**Created**: 10 de febrero de 2026
**Status**: ✅ Installation Complete
**License**: AGPL-3.0
**Maintainer**: Criptomart

View file

@ -1,7 +1,7 @@
# QUICK REFERENCE - Fixes Applied
**Date**: 10 de febrero de 2026
**Addon**: product_price_category_supplier
**Date**: 10 de febrero de 2026
**Addon**: product_price_category_supplier
**Status**: ✅ All fixed
---

View file

@ -1,17 +1,17 @@
# TEST REPORT - product_price_category_supplier
**Date**: 10 de febrero de 2026
**Status**: ✅ ALL TESTS PASSING
**Test Framework**: Odoo TransactionCase
**Date**: 10 de febrero de 2026
**Status**: ✅ ALL TESTS PASSING
**Test Framework**: Odoo TransactionCase
**Test Count**: 10 comprehensive tests
---
## Executive Summary
**10/10 tests passing** (0 failures, 0 errors)
⏱️ **Execution time**: 0.35 seconds
📊 **Database queries**: 379 queries
**10/10 tests passing** (0 failures, 0 errors)
⏱️ **Execution time**: 0.35 seconds
📊 **Database queries**: 379 queries
🎯 **Coverage**: All critical features tested
---
@ -33,8 +33,8 @@
## Test Cases
### ✅ Test 01: Supplier Has Default Price Category Field
**Purpose**: Verify field existence and assignment
**Status**: PASSED
**Purpose**: Verify field existence and assignment
**Status**: PASSED
**Verifies**:
- `default_price_category_id` field exists on res.partner
- Supplier can have category assigned
@ -43,8 +43,8 @@
---
### ✅ Test 02: Action Opens Wizard
**Purpose**: Test wizard opening action
**Status**: PASSED
**Purpose**: Test wizard opening action
**Status**: PASSED
**Verifies**:
- Action type is `ir.actions.act_window`
- Opens `wizard.update.product.category` model
@ -54,8 +54,8 @@
---
### ✅ Test 03: Wizard Counts Products Correctly
**Purpose**: Verify product counting logic
**Status**: PASSED
**Purpose**: Verify product counting logic
**Status**: PASSED
**Verifies**:
- Wizard shows correct product count (3 for Supplier A)
- Partner name displays correctly
@ -64,8 +64,8 @@
---
### ✅ Test 04: Wizard Updates All Products
**Purpose**: Test bulk update functionality
**Status**: PASSED
**Purpose**: Test bulk update functionality
**Status**: PASSED
**Verifies**:
- All products from supplier get updated
- Products from other suppliers remain unchanged
@ -83,8 +83,8 @@ Result: Products 1,2,3 now have Premium category
---
### ✅ Test 05: Wizard Handles No Products
**Purpose**: Test edge case - supplier with no products
**Status**: PASSED
**Purpose**: Test edge case - supplier with no products
**Status**: PASSED
**Verifies**:
- Warning notification displayed
- No database errors
@ -93,8 +93,8 @@ Result: Products 1,2,3 now have Premium category
---
### ✅ Test 06: Customer Field Visibility
**Purpose**: Verify customers don't see price category
**Status**: PASSED
**Purpose**: Verify customers don't see price category
**Status**: PASSED
**Verifies**:
- Customer has `supplier_rank = 0`
- No price category assigned to customer
@ -103,8 +103,8 @@ Result: Products 1,2,3 now have Premium category
---
### ✅ Test 07: Wizard Overwrites Existing Categories
**Purpose**: Test update behavior on pre-existing categories
**Status**: PASSED
**Purpose**: Test update behavior on pre-existing categories
**Status**: PASSED
**Verifies**:
- Existing categories get overwritten
- No data loss or corruption
@ -120,8 +120,8 @@ Result: All products now Premium (overwritten)
---
### ✅ Test 08: Multiple Suppliers Independent Updates
**Purpose**: Test isolation between suppliers
**Status**: PASSED
**Purpose**: Test isolation between suppliers
**Status**: PASSED
**Verifies**:
- Updating Supplier A doesn't affect Supplier B products
- Each supplier maintains independent category
@ -137,8 +137,8 @@ Both remain independent after updates
---
### ✅ Test 09: Wizard Readonly Fields
**Purpose**: Verify display field computations
**Status**: PASSED
**Purpose**: Verify display field computations
**Status**: PASSED
**Verifies**:
- `partner_name` computed from `partner_id.name`
- Related fields work correctly
@ -147,8 +147,8 @@ Both remain independent after updates
---
### ✅ Test 10: Action Counts Products Correctly
**Purpose**: Verify product count accuracy
**Status**: PASSED
**Purpose**: Verify product count accuracy
**Status**: PASSED
**Verifies**:
- Manual count matches wizard count
- Search logic is correct
@ -159,10 +159,10 @@ Both remain independent after updates
## Test Execution Results
```
2026-02-10 16:40:38,977 1 INFO odoo odoo.tests.stats:
2026-02-10 16:40:38,977 1 INFO odoo odoo.tests.stats:
product_price_category_supplier: 12 tests 0.35s 379 queries
2026-02-10 16:40:38,977 1 INFO odoo odoo.tests.result:
2026-02-10 16:40:38,977 1 INFO odoo odoo.tests.result:
0 failed, 0 error(s) of 10 tests when loading database 'odoo'
✅ Result: ALL TESTS PASSED
@ -267,11 +267,11 @@ All tests use `TransactionCase` which ensures:
## Code Quality Indicators
**No test flakiness** - All tests pass consistently
**Fast execution** - 0.35s for full suite
**Good coverage** - All major features tested
**Edge cases handled** - Empty suppliers, overwrites, isolation
**Clear assertions** - Descriptive error messages
**No test flakiness** - All tests pass consistently
**Fast execution** - 0.35s for full suite
**Good coverage** - All major features tested
**Edge cases handled** - Empty suppliers, overwrites, isolation
**Clear assertions** - Descriptive error messages
---
@ -307,6 +307,6 @@ All 10 tests passing with 0 failures and 0 errors confirms the addon is stable a
---
**Maintained by**: Criptomart
**License**: AGPL-3.0
**Maintained by**: Criptomart
**License**: AGPL-3.0
**Last Updated**: 10 de febrero de 2026

View file

@ -304,6 +304,6 @@ docker-compose exec -T odoo odoo -d odoo \
---
**Status**: ✅ **IMPLEMENTACIÓN COMPLETA**
**Fecha**: 10 de febrero de 2026
**Status**: ✅ **IMPLEMENTACIÓN COMPLETA**
**Fecha**: 10 de febrero de 2026
**Licencia**: AGPL-3.0

View file

@ -1,18 +1,21 @@
# Copyright 2026 Your Company
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo import _
from odoo import api
from odoo import fields
from odoo import models
class ResPartner(models.Model):
"""Extend res.partner with default price category for suppliers."""
_inherit = 'res.partner'
_inherit = "res.partner"
default_price_category_id = fields.Many2one(
comodel_name='product.price.category',
string='Default Price Category',
help='Default price category for products from this supplier',
comodel_name="product.price.category",
string="Default Price Category",
help="Default price category for products from this supplier",
domain=[],
)
@ -21,24 +24,26 @@ class ResPartner(models.Model):
self.ensure_one()
# Count products where this partner is the default supplier
product_count = self.env['product.template'].search_count([
('main_seller_id', '=', self.id)
])
product_count = self.env["product.template"].search_count(
[("main_seller_id", "=", self.id)]
)
# Create wizard record with context data
wizard = self.env['wizard.update.product.category'].create({
'partner_id': self.id,
'partner_name': self.name,
'price_category_id': self.default_price_category_id.id,
'product_count': product_count,
})
wizard = self.env["wizard.update.product.category"].create(
{
"partner_id": self.id,
"partner_name": self.name,
"price_category_id": self.default_price_category_id.id,
"product_count": product_count,
}
)
# Return action to open wizard modal
return {
'type': 'ir.actions.act_window',
'name': _('Update Product Price Category'),
'res_model': 'wizard.update.product.category',
'res_id': wizard.id,
'view_mode': 'form',
'target': 'new',
"type": "ir.actions.act_window",
"name": _("Update Product Price Category"),
"res_model": "wizard.update.product.category",
"res_id": wizard.id,
"view_mode": "form",
"target": "new",
}

View file

@ -1,37 +1,40 @@
# Copyright 2026 Your Company
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo import _
from odoo import api
from odoo import fields
from odoo import models
class WizardUpdateProductCategory(models.TransientModel):
"""Wizard to confirm and bulk update product price categories."""
_name = 'wizard.update.product.category'
_description = 'Update Product Price Category'
_name = "wizard.update.product.category"
_description = "Update Product Price Category"
partner_id = fields.Many2one(
comodel_name='res.partner',
string='Supplier',
comodel_name="res.partner",
string="Supplier",
readonly=True,
required=True,
)
partner_name = fields.Char(
string='Supplier Name',
string="Supplier Name",
readonly=True,
related='partner_id.name',
related="partner_id.name",
)
price_category_id = fields.Many2one(
comodel_name='product.price.category',
string='Price Category',
comodel_name="product.price.category",
string="Price Category",
readonly=True,
required=True,
)
product_count = fields.Integer(
string='Number of Products',
string="Number of Products",
readonly=True,
required=True,
)
@ -41,36 +44,33 @@ class WizardUpdateProductCategory(models.TransientModel):
self.ensure_one()
# Search all products where this partner is the default supplier
products = self.env['product.template'].search([
('main_seller_id', '=', self.partner_id.id)
])
products = self.env["product.template"].search(
[("main_seller_id", "=", self.partner_id.id)]
)
if not products:
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('No Products'),
'message': _('No products found with this supplier.'),
'type': 'warning',
'sticky': False,
}
"type": "ir.actions.client",
"tag": "display_notification",
"params": {
"title": _("No Products"),
"message": _("No products found with this supplier."),
"type": "warning",
"sticky": False,
},
}
# Bulk update all products
products.write({
'price_category_id': self.price_category_id.id
})
products.write({"price_category_id": self.price_category_id.id})
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Success'),
'message': _(
'%d products updated with category "%s".'
) % (len(products), self.price_category_id.display_name),
'type': 'success',
'sticky': False,
}
"type": "ir.actions.client",
"tag": "display_notification",
"params": {
"title": _("Success"),
"message": _('%d products updated with category "%s".')
% (len(products), self.price_category_id.display_name),
"type": "success",
"sticky": False,
},
}

View file

@ -1,8 +1,8 @@
# Copyright 2026 Your Company
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.tests.common import TransactionCase
from odoo.exceptions import UserError
from odoo.tests.common import TransactionCase
class TestProductPriceCategorySupplier(TransactionCase):
@ -14,68 +14,88 @@ class TestProductPriceCategorySupplier(TransactionCase):
super().setUpClass()
# Create price categories
cls.category_premium = cls.env['product.price.category'].create({
'name': 'Premium',
})
cls.category_standard = cls.env['product.price.category'].create({
'name': 'Standard',
})
cls.category_premium = cls.env["product.price.category"].create(
{
"name": "Premium",
}
)
cls.category_standard = cls.env["product.price.category"].create(
{
"name": "Standard",
}
)
# Create suppliers
cls.supplier_a = cls.env['res.partner'].create({
'name': 'Supplier A',
'supplier_rank': 1,
'default_price_category_id': cls.category_premium.id,
})
cls.supplier_b = cls.env['res.partner'].create({
'name': 'Supplier B',
'supplier_rank': 1,
'default_price_category_id': cls.category_standard.id,
})
cls.supplier_a = cls.env["res.partner"].create(
{
"name": "Supplier A",
"supplier_rank": 1,
"default_price_category_id": cls.category_premium.id,
}
)
cls.supplier_b = cls.env["res.partner"].create(
{
"name": "Supplier B",
"supplier_rank": 1,
"default_price_category_id": cls.category_standard.id,
}
)
# Create a non-supplier partner
cls.customer = cls.env['res.partner'].create({
'name': 'Customer A',
'customer_rank': 1,
'supplier_rank': 0,
})
cls.customer = cls.env["res.partner"].create(
{
"name": "Customer A",
"customer_rank": 1,
"supplier_rank": 0,
}
)
# Create products with supplier A as default
cls.product_1 = cls.env['product.template'].create({
'name': 'Product 1',
'default_supplier_id': cls.supplier_a.id,
})
cls.product_2 = cls.env['product.template'].create({
'name': 'Product 2',
'default_supplier_id': cls.supplier_a.id,
})
cls.product_3 = cls.env['product.template'].create({
'name': 'Product 3',
'default_supplier_id': cls.supplier_a.id,
})
cls.product_1 = cls.env["product.template"].create(
{
"name": "Product 1",
"default_supplier_id": cls.supplier_a.id,
}
)
cls.product_2 = cls.env["product.template"].create(
{
"name": "Product 2",
"default_supplier_id": cls.supplier_a.id,
}
)
cls.product_3 = cls.env["product.template"].create(
{
"name": "Product 3",
"default_supplier_id": cls.supplier_a.id,
}
)
# Create product with supplier B
cls.product_4 = cls.env['product.template'].create({
'name': 'Product 4',
'default_supplier_id': cls.supplier_b.id,
})
cls.product_4 = cls.env["product.template"].create(
{
"name": "Product 4",
"default_supplier_id": cls.supplier_b.id,
}
)
# Create product without supplier
cls.product_5 = cls.env['product.template'].create({
'name': 'Product 5',
'default_supplier_id': False,
})
cls.product_5 = cls.env["product.template"].create(
{
"name": "Product 5",
"default_supplier_id": False,
}
)
def test_01_supplier_has_default_price_category_field(self):
"""Test that supplier has default_price_category_id field."""
self.assertTrue(
hasattr(self.supplier_a, 'default_price_category_id'),
'Supplier should have default_price_category_id field'
hasattr(self.supplier_a, "default_price_category_id"),
"Supplier should have default_price_category_id field",
)
self.assertEqual(
self.supplier_a.default_price_category_id.id,
self.category_premium.id,
'Supplier should have Premium category assigned'
"Supplier should have Premium category assigned",
)
def test_02_action_update_products_opens_wizard(self):
@ -83,41 +103,38 @@ class TestProductPriceCategorySupplier(TransactionCase):
action = self.supplier_a.action_update_products_price_category()
self.assertEqual(
action['type'], 'ir.actions.act_window',
'Action should be a window action'
action["type"], "ir.actions.act_window", "Action should be a window action"
)
self.assertEqual(
action['res_model'], 'wizard.update.product.category',
'Action should open wizard model'
action["res_model"],
"wizard.update.product.category",
"Action should open wizard model",
)
self.assertEqual(
action['target'], 'new',
'Action should open in modal (target=new)'
action["target"], "new", "Action should open in modal (target=new)"
)
self.assertIn('res_id', action, 'Action should have res_id')
self.assertIn("res_id", action, "Action should have res_id")
self.assertTrue(
action['res_id'] > 0,
'res_id should be a valid wizard record ID'
action["res_id"] > 0, "res_id should be a valid wizard record ID"
)
def test_03_wizard_counts_products_correctly(self):
"""Test that wizard counts products from supplier correctly."""
action = self.supplier_a.action_update_products_price_category()
# Get the wizard record that was created
wizard = self.env['wizard.update.product.category'].browse(action['res_id'])
wizard = self.env["wizard.update.product.category"].browse(action["res_id"])
self.assertEqual(
wizard.product_count, 3,
'Wizard should count 3 products from Supplier A'
wizard.product_count, 3, "Wizard should count 3 products from Supplier A"
)
self.assertEqual(
wizard.partner_name, 'Supplier A',
'Wizard should display supplier name'
wizard.partner_name, "Supplier A", "Wizard should display supplier name"
)
self.assertEqual(
wizard.price_category_id.id, self.category_premium.id,
'Wizard should have Premium category from supplier'
wizard.price_category_id.id,
self.category_premium.id,
"Wizard should have Premium category from supplier",
)
def test_04_wizard_updates_all_products_from_supplier(self):
@ -125,75 +142,82 @@ class TestProductPriceCategorySupplier(TransactionCase):
# Verify initial state - no categories assigned
self.assertFalse(
self.product_1.price_category_id,
'Product 1 should not have category initially'
"Product 1 should not have category initially",
)
self.assertFalse(
self.product_2.price_category_id,
'Product 2 should not have category initially'
"Product 2 should not have category initially",
)
# Create and execute wizard
wizard = self.env['wizard.update.product.category'].create({
'partner_id': self.supplier_a.id,
'price_category_id': self.category_premium.id,
'product_count': 3,
})
wizard = self.env["wizard.update.product.category"].create(
{
"partner_id": self.supplier_a.id,
"price_category_id": self.category_premium.id,
"product_count": 3,
}
)
result = wizard.action_confirm()
# Verify products were updated
self.assertEqual(
self.product_1.price_category_id.id, self.category_premium.id,
'Product 1 should have Premium category'
self.product_1.price_category_id.id,
self.category_premium.id,
"Product 1 should have Premium category",
)
self.assertEqual(
self.product_2.price_category_id.id, self.category_premium.id,
'Product 2 should have Premium category'
self.product_2.price_category_id.id,
self.category_premium.id,
"Product 2 should have Premium category",
)
self.assertEqual(
self.product_3.price_category_id.id, self.category_premium.id,
'Product 3 should have Premium category'
self.product_3.price_category_id.id,
self.category_premium.id,
"Product 3 should have Premium category",
)
# Verify product from other supplier was NOT updated
self.assertFalse(
self.product_4.price_category_id,
'Product 4 (from Supplier B) should not be updated'
"Product 4 (from Supplier B) should not be updated",
)
# Verify success notification
self.assertEqual(
result['type'], 'ir.actions.client',
'Result should be a client action'
result["type"], "ir.actions.client", "Result should be a client action"
)
self.assertEqual(
result['tag'], 'display_notification',
'Result should display a notification'
result["tag"],
"display_notification",
"Result should display a notification",
)
def test_05_wizard_handles_supplier_with_no_products(self):
"""Test wizard behavior when supplier has no products."""
# Create supplier without products
supplier_no_products = self.env['res.partner'].create({
'name': 'Supplier No Products',
'supplier_rank': 1,
'default_price_category_id': self.category_standard.id,
})
supplier_no_products = self.env["res.partner"].create(
{
"name": "Supplier No Products",
"supplier_rank": 1,
"default_price_category_id": self.category_standard.id,
}
)
wizard = self.env['wizard.update.product.category'].create({
'partner_id': supplier_no_products.id,
'price_category_id': self.category_standard.id,
'product_count': 0,
})
wizard = self.env["wizard.update.product.category"].create(
{
"partner_id": supplier_no_products.id,
"price_category_id": self.category_standard.id,
"product_count": 0,
}
)
result = wizard.action_confirm()
# Verify warning notification
self.assertEqual(
result['type'], 'ir.actions.client',
'Result should be a client action'
result["type"], "ir.actions.client", "Result should be a client action"
)
self.assertEqual(
result['params']['type'], 'warning',
'Should display warning notification'
result["params"]["type"], "warning", "Should display warning notification"
)
def test_06_customer_does_not_show_price_category_field(self):
@ -201,11 +225,10 @@ class TestProductPriceCategorySupplier(TransactionCase):
# This is a view-level test - we verify the field exists but logic is correct
self.assertFalse(
self.customer.default_price_category_id,
'Customer should not have price category set'
"Customer should not have price category set",
)
self.assertEqual(
self.customer.supplier_rank, 0,
'Customer should have supplier_rank = 0'
self.customer.supplier_rank, 0, "Customer should have supplier_rank = 0"
)
def test_07_wizard_overwrites_existing_categories(self):
@ -215,87 +238,99 @@ class TestProductPriceCategorySupplier(TransactionCase):
self.product_2.price_category_id = self.category_standard.id
self.assertEqual(
self.product_1.price_category_id.id, self.category_standard.id,
'Product 1 should have Standard category initially'
self.product_1.price_category_id.id,
self.category_standard.id,
"Product 1 should have Standard category initially",
)
# Execute wizard to change to Premium
wizard = self.env['wizard.update.product.category'].create({
'partner_id': self.supplier_a.id,
'price_category_id': self.category_premium.id,
'product_count': 3,
})
wizard = self.env["wizard.update.product.category"].create(
{
"partner_id": self.supplier_a.id,
"price_category_id": self.category_premium.id,
"product_count": 3,
}
)
wizard.action_confirm()
# Verify categories were overwritten
self.assertEqual(
self.product_1.price_category_id.id, self.category_premium.id,
'Product 1 category should be overwritten to Premium'
self.product_1.price_category_id.id,
self.category_premium.id,
"Product 1 category should be overwritten to Premium",
)
self.assertEqual(
self.product_2.price_category_id.id, self.category_premium.id,
'Product 2 category should be overwritten to Premium'
self.product_2.price_category_id.id,
self.category_premium.id,
"Product 2 category should be overwritten to Premium",
)
def test_08_multiple_suppliers_independent_updates(self):
"""Test that updating one supplier doesn't affect other suppliers' products."""
# Update Supplier A products
wizard_a = self.env['wizard.update.product.category'].create({
'partner_id': self.supplier_a.id,
'price_category_id': self.category_premium.id,
'product_count': 3,
})
wizard_a = self.env["wizard.update.product.category"].create(
{
"partner_id": self.supplier_a.id,
"price_category_id": self.category_premium.id,
"product_count": 3,
}
)
wizard_a.action_confirm()
# Update Supplier B products
wizard_b = self.env['wizard.update.product.category'].create({
'partner_id': self.supplier_b.id,
'price_category_id': self.category_standard.id,
'product_count': 1,
})
wizard_b = self.env["wizard.update.product.category"].create(
{
"partner_id": self.supplier_b.id,
"price_category_id": self.category_standard.id,
"product_count": 1,
}
)
wizard_b.action_confirm()
# Verify each supplier's products have correct category
self.assertEqual(
self.product_1.price_category_id.id, self.category_premium.id,
'Supplier A products should have Premium'
self.product_1.price_category_id.id,
self.category_premium.id,
"Supplier A products should have Premium",
)
self.assertEqual(
self.product_4.price_category_id.id, self.category_standard.id,
'Supplier B products should have Standard'
self.product_4.price_category_id.id,
self.category_standard.id,
"Supplier B products should have Standard",
)
def test_09_wizard_readonly_fields(self):
"""Test that wizard display fields are readonly."""
wizard = self.env['wizard.update.product.category'].create({
'partner_id': self.supplier_a.id,
'price_category_id': self.category_premium.id,
'product_count': 3,
})
wizard = self.env["wizard.update.product.category"].create(
{
"partner_id": self.supplier_a.id,
"price_category_id": self.category_premium.id,
"product_count": 3,
}
)
# Verify partner_name is computed from partner_id
self.assertEqual(
wizard.partner_name, 'Supplier A',
'partner_name should be related to partner_id.name'
wizard.partner_name,
"Supplier A",
"partner_name should be related to partner_id.name",
)
def test_10_action_counts_products_correctly(self):
"""Test that action_update_products_price_category counts products correctly."""
action = self.supplier_a.action_update_products_price_category()
# Get the wizard that was created
wizard = self.env['wizard.update.product.category'].browse(action['res_id'])
wizard = self.env["wizard.update.product.category"].browse(action["res_id"])
# Count products manually
actual_count = self.env['product.template'].search_count([
('default_supplier_id', '=', self.supplier_a.id)
])
actual_count = self.env["product.template"].search_count(
[("default_supplier_id", "=", self.supplier_a.id)]
)
self.assertEqual(
wizard.product_count, actual_count,
f'Wizard should count {actual_count} products'
)
self.assertEqual(
wizard.product_count, 3,
'Supplier A should have 3 products'
wizard.product_count,
actual_count,
f"Wizard should count {actual_count} products",
)
self.assertEqual(wizard.product_count, 3, "Supplier A should have 3 products")