[REF] product_origin_char: simplify to template-based origin

- Move origin_text field from product.supplierinfo to product.template
- Add related field in product.product for variant access
- Remove dependency on product_main_seller
- Update views to show field near category (editable)
- Rewrite tests for new architecture
- Update all documentation (README, readme/ fragments)
- Bump version to 18.0.2.0.0
This commit is contained in:
snt 2026-03-06 17:43:20 +01:00
parent e2ced75ecd
commit 5efe57dc19
15 changed files with 180 additions and 402 deletions

View file

@ -19,8 +19,7 @@ Product Origin Text
|badge1| |badge2| |badge1| |badge2|
This module replaces the structured country/state origin fields from ``product_origin`` This module adds a simple free-text ``origin_text`` field to products.
with a flexible free-text field that can be defined per supplier.
**Problem:** **Problem:**
@ -38,21 +37,18 @@ These free-form descriptions don't fit into structured country/state fields.
**Solution:** **Solution:**
This module adds a translatable ``origin_text`` field to ``product.supplierinfo`` that: This module adds a simple ``origin_text`` field to ``product.template`` that:
* Allows free-form text to describe product origin * Allows free-form text to describe product origin
* Is stored per supplier (different suppliers may have different origin info) * Is accessible from product variants via related field
* Is translatable (can be described differently in each language) * Works with any product workflow
* Is automatically displayed on the product based on the main vendor
(from ``product_main_seller`` module)
**Features:** **Features:**
* Free-text ``Origin`` field in supplier info (Purchase tab of product) * Free-text ``Origin`` field in product form
* Computed ``Origin`` field in product form (shows main vendor's origin) * Field visible in product list views (optional column)
* Field is visible in product list view (optional column) * Variants share the same origin as their template
* Full translation support for multiple languages * Editable from both template and variant views
* Compatible with existing supplier management workflows
**Table of contents** **Table of contents**
@ -65,7 +61,6 @@ Installation
This module depends on: This module depends on:
* ``product`` - Base product management * ``product`` - Base product management
* ``product_main_seller`` - To determine the main vendor for products
To install: To install:
@ -77,8 +72,8 @@ Configuration
No configuration is needed. The module works automatically after installation. No configuration is needed. The module works automatically after installation.
The origin text displayed on a product is based on the **main vendor** as determined The origin field appears in the product form view (next to category) and can be
by ``product_main_seller`` (the first supplier in the vendors list). enabled as an optional column in list views.
Usage Usage
===== =====
@ -86,29 +81,21 @@ Usage
**To add origin information to a product:** **To add origin information to a product:**
#. Go to a product form #. Go to a product form
#. Open the **Purchase** tab #. Find the **Origin** field (near the product category)
#. In the **Vendors** section, select a supplier line #. Fill in the origin with free-form text
#. Fill in the **Origin** field with free-form text describing the origin (e.g., "Valencia, Spain", "Local producer - Basque Country")
(e.g., "Valencia, Huerta de..., Spain")
#. Save the product #. Save the product
**To view origin information:** **To view origin information in lists:**
* The **Origin** field will be automatically displayed on the product form * In product list views, enable the optional "Origin" column
(below the **Main Vendor** field in the Purchase tab) * The origin is shown for both product templates and variants
* The origin shown is from the **main vendor** (first supplier in the list)
* You can also add the Origin column to product list views
**To change the displayed origin:** **Product variants:**
* Reorder the vendors list to change which is the main vendor * All variants of a product template share the same origin
* The origin text will automatically update to show the new main vendor's origin * Updating the origin on any variant updates the template
* This ensures consistency across all variants
**Multi-language support:**
* The origin text is translatable
* Switch to another language and edit the supplier info to provide a translation
* Each language can have a different description of the origin
Bug Tracker Bug Tracker
=========== ===========

View file

@ -4,111 +4,88 @@
### Architecture ### Architecture
This module extends the product origin information system to use free-text fields instead of structured country/state fields. This module adds a simple free-text origin field to products.
**Models:** **Models:**
1. **product.supplierinfo** - Base model with the `origin_text` field 1. **product.template** - Base model with the `origin_text` field
- Field: `origin_text` (Char, translate=True) - Field: `origin_text` (Char)
- This is where the actual data is stored - This is where the actual data is stored
- Each supplier can have a different origin text for the same product - Editable directly on the product form
2. **product.template** - Computed field 2. **product.product** - Related field for variant access
- Field: `origin_text` (Char, computed, store=False) - Field: `origin_text` (Char, related to `product_tmpl_id.origin_text`)
- Computes from main vendor's supplierinfo - `readonly=False` allows editing from variant views
- Depends on: `main_seller_id`, `variant_seller_ids.origin_text` - Changes propagate to the template
3. **product.product** - Computed field (variant level) ### Why This Architecture?
- Field: `origin_text` (Char, computed, store=False)
- Computes from main vendor's supplierinfo
- Depends on: `product_tmpl_id.main_seller_id`, `seller_ids.origin_text`
### Why Computed Fields Instead of Related? The `origin_text` field is stored in `product.template` because:
The `origin_text` field in `product.template` and `product.product` cannot be a simple `related` field because: - Origin typically applies to all variants of a product
- Simplifies data management (one source of truth)
- Avoids duplication across variants
- Standard Odoo pattern for product-level attributes
- `main_seller_id` is a `res.partner` record The related field in `product.product` with `readonly=False` allows:
- `origin_text` is stored in `product.supplierinfo` records
- We need to find the supplierinfo record where `partner_id == main_seller_id`
This requires a computed field with custom logic to filter and find the correct supplierinfo record. - Seamless access from variant views
- Editing from either template or variant forms
- Automatic synchronization between template and variants
### Translation Support ### Previous Architecture (Deprecated)
The `origin_text` field in `product.supplierinfo` uses `translate=True`, which means: The previous version stored `origin_text` in `product.supplierinfo` with computed
fields in template/product. This was deprecated because:
- Each language can have a different value - Supplierinfo records don't always exist when products are created
- Translations are stored in Odoo's translation system - Added unnecessary complexity with computed fields
- When switching languages, the field shows the translated value - Required `product_main_seller` dependency
- If no translation exists, it falls back to the default language value - Origin semantically belongs to the product, not the supplier relationship
### Store Strategy
The computed fields in product template and product use `store=False` because:
- The value should always reflect the current main vendor
- If the main vendor changes or its origin text is updated, the product's origin should update automatically
- No need to store redundant data
- Reduces database size and update complexity
### Dependencies ### Dependencies
- **product** - Core product module - **product** - Core product module (only dependency)
- **product_main_seller** - Provides `main_seller_id` computed field for products
### Relationship to product_origin
This module is a replacement/alternative to the OCA `product_origin` module:
- `product_origin` provides structured fields (country_id, state_id)
- `product_origin_char` provides free-text field (origin_text)
- Both cannot be installed simultaneously without potential conflicts
- This module was created because suppliers use creative, non-standardized origin descriptions
### Performance Considerations ### Performance Considerations
- Computed fields with `store=False` are calculated on-the-fly - Simple stored field with no computation overhead
- Performance is acceptable because the computation is simple (filter + get first) - Related field access is efficient in Odoo ORM
- If performance becomes an issue, consider: - No additional queries needed for variant access
- Adding `store=True` with proper dependencies
- Adding database indexes on frequently searched fields
- Caching strategies
### Testing Strategy ### Testing Strategy
Tests cover: Tests cover:
1. **Basic field storage** - Create supplierinfo with origin_text 1. **Basic field storage** - Set origin on template
2. **Computed field** - Verify product shows main vendor's origin 2. **Related field access** - Verify variant sees template origin
3. **Main vendor change** - Verify origin updates when main vendor changes 3. **Write from variant** - Verify changes propagate to template
4. **Translation** - Verify field is translatable (multilingual support) 4. **Empty cases** - Products without origin
5. **Empty cases** - Product without vendors, vendor without origin 5. **Multiple products** - Independent origins
6. **Variants** - All variants share same origin
### Future Improvements
Potential enhancements:
- Add migration script from `product_origin` to convert country/state to text
- Add bulk update wizard to set origin for multiple products
- Add origin text to purchase order lines
- Add search/group by origin in product lists
- Add validation rules (max length, format checks)
## Code Quality ## Code Quality
- Follows OCA guidelines - Follows OCA guidelines
- Black formatted (line length 88) - Black formatted (line length 88)
- isort sorted imports - isort for imports
- Passes flake8 and pylint checks - Minimal dependencies (only `product`)
- Full test coverage
- Documented with docstrings ## Migration from v1.x
- Translatable strings handled correctly (no `_()` in field definitions)
If upgrading from the previous supplier-based architecture:
1. Data in `product.supplierinfo.origin_text` will need manual migration
2. Run migration script to copy main supplier origin to product template
3. Remove dependency on `product_main_seller` if no longer needed
## Version History ## Version History
- **18.0.1.0.0** (2026-02-25) - Initial release - **18.0.2.0.0** (2026-03-06) - Simplified architecture
- Field directly on product.template
- Related field in product.product
- Removed dependency on product_main_seller
- **18.0.1.0.0** (2026-02-25) - Initial release (deprecated)
- Free-text origin field per supplier - Free-text origin field per supplier
- Automatic display based on main vendor - Automatic display based on main vendor
- Multi-language support
- Full test coverage
- OCA documentation structure

View file

@ -3,19 +3,17 @@
{ # noqa: B018 { # noqa: B018
"name": "Product Origin Text", "name": "Product Origin Text",
"version": "18.0.1.0.0", "version": "18.0.2.0.0",
"category": "Product", "category": "Product",
"summary": "Free text origin field per supplier", "summary": "Free text origin field for products",
"author": "Odoo Community Association (OCA), Criptomart", "author": "Odoo Community Association (OCA), Criptomart",
"maintainers": ["Criptomart"], "maintainers": ["Criptomart"],
"license": "AGPL-3", "license": "AGPL-3",
"website": "https://git.criptomart.net/criptomart/addons-cm", "website": "https://git.criptomart.net/criptomart/addons-cm",
"depends": [ "depends": [
"product", "product",
"product_main_seller",
], ],
"data": [ "data": [
"views/product_supplierinfo_views.xml",
"views/product_template_views.xml", "views/product_template_views.xml",
], ],
} }

View file

@ -1,6 +1,5 @@
# Copyright 2026 Criptomart # Copyright 2026 Criptomart
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import product_supplierinfo # noqa: F401
from . import product_template # noqa: F401 from . import product_template # noqa: F401
from . import product_product # noqa: F401 from . import product_product # noqa: F401

View file

@ -1,7 +1,6 @@
# Copyright 2026 Criptomart # Copyright 2026 Criptomart
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api
from odoo import fields from odoo import fields
from odoo import models from odoo import models
@ -10,21 +9,6 @@ class ProductProduct(models.Model):
_inherit = "product.product" _inherit = "product.product"
origin_text = fields.Char( origin_text = fields.Char(
string="Origin", related="product_tmpl_id.origin_text",
compute="_compute_origin_text", readonly=False,
store=False,
help="Origin text from main vendor's supplierinfo",
) )
@api.depends("product_tmpl_id.main_seller_id", "seller_ids.origin_text")
def _compute_origin_text(self):
for product in self:
if product.product_tmpl_id.main_seller_id:
# Find the supplierinfo record for the main seller
main_seller = product.product_tmpl_id.main_seller_id
seller = product.seller_ids.filtered(
lambda s, ms=main_seller: s.partner_id == ms
)[:1]
product.origin_text = seller.origin_text if seller else False
else:
product.origin_text = False

View file

@ -1,15 +0,0 @@
# Copyright 2026 Criptomart
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields
from odoo import models
class ProductSupplierinfo(models.Model):
_inherit = "product.supplierinfo"
origin_text = fields.Char(
string="Origin",
translate=True,
help="Free text to describe product origin (country, region, producer, etc.)",
)

View file

@ -1,7 +1,6 @@
# Copyright 2026 Criptomart # Copyright 2026 Criptomart
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api
from odoo import fields from odoo import fields
from odoo import models from odoo import models
@ -11,20 +10,5 @@ class ProductTemplate(models.Model):
origin_text = fields.Char( origin_text = fields.Char(
string="Origin", string="Origin",
compute="_compute_origin_text", help="Origin of the product.",
store=False,
help="Origin text from main vendor's supplierinfo",
) )
@api.depends("main_seller_id", "variant_seller_ids.origin_text")
def _compute_origin_text(self):
for template in self:
if template.main_seller_id:
# Find the supplierinfo record for the main seller
main_seller = template.main_seller_id
seller = template.variant_seller_ids.filtered(
lambda s, ms=main_seller: s.partner_id == ms
)[:1]
template.origin_text = seller.origin_text if seller else False
else:
template.origin_text = False

View file

@ -1,4 +1,4 @@
No configuration is needed. The module works automatically after installation. No configuration is needed. The module works automatically after installation.
The origin text displayed on a product is based on the **main vendor** as determined The origin field appears in the product form view (next to category) and can be
by ``product_main_seller`` (the first supplier in the vendors list). enabled as an optional column in list views.

View file

@ -1,5 +1,4 @@
This module replaces the structured country/state origin fields from ``product_origin`` This module adds a simple free-text ``origin_text`` field to products.
with a flexible free-text field that can be defined per supplier.
**Problem:** **Problem:**
@ -17,18 +16,15 @@ These free-form descriptions don't fit into structured country/state fields.
**Solution:** **Solution:**
This module adds a translatable ``origin_text`` field to ``product.supplierinfo`` that: This module adds a simple ``origin_text`` field to ``product.template`` that:
* Allows free-form text to describe product origin * Allows free-form text to describe product origin
* Is stored per supplier (different suppliers may have different origin info) * Is accessible from product variants via related field
* Is translatable (can be described differently in each language) * Works with any product workflow
* Is automatically displayed on the product based on the main vendor
(from ``product_main_seller`` module)
**Features:** **Features:**
* Free-text ``Origin`` field in supplier info (Purchase tab of product) * Free-text ``Origin`` field in product form
* Computed ``Origin`` field in product form (shows main vendor's origin) * Field visible in product list views (optional column)
* Field is visible in product list view (optional column) * Variants share the same origin as their template
* Full translation support for multiple languages * Editable from both template and variant views
* Compatible with existing supplier management workflows

View file

@ -1,7 +1,6 @@
This module depends on: This module depends on:
* ``product`` - Base product management * ``product`` - Base product management
* ``product_main_seller`` - To determine the main vendor for products
To install: To install:

View file

@ -1,26 +1,18 @@
**To add origin information to a product:** **To add origin information to a product:**
#. Go to a product form #. Go to a product form
#. Open the **Purchase** tab #. Find the **Origin** field (near the product category)
#. In the **Vendors** section, select a supplier line #. Fill in the origin with free-form text
#. Fill in the **Origin** field with free-form text describing the origin (e.g., "Valencia, Spain", "Local producer - Basque Country")
(e.g., "Valencia, Huerta de..., Spain")
#. Save the product #. Save the product
**To view origin information:** **To view origin information in lists:**
* The **Origin** field will be automatically displayed on the product form * In product list views, enable the optional "Origin" column
(below the **Main Vendor** field in the Purchase tab) * The origin is shown for both product templates and variants
* The origin shown is from the **main vendor** (first supplier in the list)
* You can also add the Origin column to product list views
**To change the displayed origin:** **Product variants:**
* Reorder the vendors list to change which is the main vendor * All variants of a product template share the same origin
* The origin text will automatically update to show the new main vendor's origin * Updating the origin on any variant updates the template
* This ensures consistency across all variants
**Multi-language support:**
* The origin text is translatable
* Switch to another language and edit the supplier info to provide a translation
* Each language can have a different description of the origin

View file

@ -1 +0,0 @@
Logo placeholder - run install_logo.sh to add CriptoMart logo

View file

@ -5,190 +5,71 @@ from odoo.tests.common import TransactionCase
class TestProductOriginChar(TransactionCase): class TestProductOriginChar(TransactionCase):
"""Test cases for product_origin_char module.
This module adds a simple origin_text field to product.template
with a related field in product.product for variant access.
"""
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
super().setUpClass() super().setUpClass()
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
# Create test suppliers # Create test product template
cls.supplier_1 = cls.env["res.partner"].create( cls.product_tmpl = cls.env["product.template"].create(
{
"name": "Supplier 1",
"is_company": True,
}
)
cls.supplier_2 = cls.env["res.partner"].create(
{
"name": "Supplier 2",
"is_company": True,
}
)
# Create test product
cls.product = cls.env["product.product"].create(
{ {
"name": "Test Product", "name": "Test Product",
"type": "consu", "type": "consu",
} }
) )
cls.product = cls.product_tmpl.product_variant_ids[0]
def test_01_origin_text_in_supplierinfo(self): def test_01_origin_text_in_template(self):
"""Test that origin_text can be set in supplierinfo""" """Test that origin_text can be set in product.template"""
supplierinfo = self.env["product.supplierinfo"].create( self.product_tmpl.origin_text = "Valencia, Spain"
{ self.assertEqual(self.product_tmpl.origin_text, "Valencia, Spain")
"partner_id": self.supplier_1.id,
"product_tmpl_id": self.product.product_tmpl_id.id,
"origin_text": "Valencia, Spain",
}
)
self.assertEqual(supplierinfo.origin_text, "Valencia, Spain")
def test_02_origin_from_main_seller(self): def test_02_origin_text_in_product_related(self):
"""Test that product shows origin from main seller""" """Test that product.product has related access to origin_text"""
# Create supplierinfo for supplier 1 (will be main seller) self.product_tmpl.origin_text = "Aragón, Spain"
self.env["product.supplierinfo"].create( # Variant should show template's origin via related field
{
"partner_id": self.supplier_1.id,
"product_tmpl_id": self.product.product_tmpl_id.id,
"sequence": 1,
"origin_text": "Valencia, Spain",
}
)
# Verify main seller is supplier 1
self.product.product_tmpl_id.invalidate_recordset()
self.assertEqual(self.product.product_tmpl_id.main_seller_id, self.supplier_1)
# Verify origin_text on product matches supplier 1's origin
self.product.invalidate_recordset()
self.assertEqual(self.product.origin_text, "Valencia, Spain")
self.assertEqual(self.product.product_tmpl_id.origin_text, "Valencia, Spain")
def test_03_origin_updates_with_main_seller_change(self):
"""Test that origin updates when main seller changes"""
# Create supplierinfo for both suppliers
supplier1_info = self.env["product.supplierinfo"].create(
{
"partner_id": self.supplier_1.id,
"product_tmpl_id": self.product.product_tmpl_id.id,
"sequence": 1,
"origin_text": "Valencia, Spain",
}
)
supplier2_info = self.env["product.supplierinfo"].create(
{
"partner_id": self.supplier_2.id,
"product_tmpl_id": self.product.product_tmpl_id.id,
"sequence": 2,
"origin_text": "Aragón, Spain",
}
)
# Initially, main seller is supplier 1
self.product.product_tmpl_id.invalidate_recordset()
self.assertEqual(self.product.product_tmpl_id.main_seller_id, self.supplier_1)
self.product.invalidate_recordset()
self.assertEqual(self.product.origin_text, "Valencia, Spain")
# Change main seller by swapping sequences
supplier1_info.sequence = 2
supplier2_info.sequence = 1
# Verify main seller is now supplier 2
self.product.product_tmpl_id.invalidate_recordset()
self.assertEqual(self.product.product_tmpl_id.main_seller_id, self.supplier_2)
# Verify origin_text updated to supplier 2's origin
self.product.invalidate_recordset()
self.assertEqual(self.product.origin_text, "Aragón, Spain") self.assertEqual(self.product.origin_text, "Aragón, Spain")
def test_04_empty_origin_without_supplier(self): def test_03_origin_text_write_from_variant(self):
"""Test that product without suppliers has no origin""" """Test that origin_text can be written from variant (readonly=False)"""
# Create product without suppliers self.product.origin_text = "Basque Country"
product_no_supplier = self.env["product.product"].create( # Should update the template
self.assertEqual(self.product_tmpl.origin_text, "Basque Country")
def test_04_empty_origin(self):
"""Test that origin_text defaults to False/empty"""
new_product = self.env["product.product"].create(
{ {
"name": "Product Without Supplier", "name": "Product Without Origin",
"type": "consu", "type": "consu",
} }
) )
self.assertFalse(new_product.origin_text)
self.assertFalse(new_product.product_tmpl_id.origin_text)
# Verify no main seller and no origin def test_05_multiple_products_independent_origins(self):
self.assertFalse(product_no_supplier.product_tmpl_id.main_seller_id) """Test that different products have independent origins"""
self.assertFalse(product_no_supplier.origin_text) product2_tmpl = self.env["product.template"].create(
def test_05_empty_origin_with_supplier_no_text(self):
"""Test that supplier without origin_text shows False"""
# Create supplierinfo without origin_text
self.env["product.supplierinfo"].create(
{
"partner_id": self.supplier_1.id,
"product_tmpl_id": self.product.product_tmpl_id.id,
"sequence": 1,
# No origin_text set
}
)
# Verify main seller exists but origin is False
self.product.product_tmpl_id.invalidate_recordset()
self.assertEqual(self.product.product_tmpl_id.main_seller_id, self.supplier_1)
self.product.invalidate_recordset()
self.assertFalse(self.product.origin_text)
def test_06_translation_support(self):
"""Test that origin_text field is translatable"""
# Create supplierinfo with origin in default language
supplierinfo = self.env["product.supplierinfo"].create(
{
"partner_id": self.supplier_1.id,
"product_tmpl_id": self.product.product_tmpl_id.id,
"origin_text": "Valencia, Spain",
}
)
# Verify field has translate=True attribute
field = self.env["product.supplierinfo"]._fields["origin_text"]
self.assertTrue(field.translate)
# Test that we can set translation (requires lang to be installed)
# This is a basic check - full translation testing would require
# installing multiple languages
self.assertEqual(supplierinfo.origin_text, "Valencia, Spain")
def test_07_multiple_products_same_supplier(self):
"""Test that different products can have different origins from same supplier"""
product2 = self.env["product.product"].create(
{ {
"name": "Test Product 2", "name": "Test Product 2",
"type": "consu", "type": "consu",
} }
) )
# Create supplierinfo for product 1 self.product_tmpl.origin_text = "Valencia, Spain"
self.env["product.supplierinfo"].create( product2_tmpl.origin_text = "Aragón, Spain"
{
"partner_id": self.supplier_1.id,
"product_tmpl_id": self.product.product_tmpl_id.id,
"origin_text": "Valencia, Spain",
}
)
# Create supplierinfo for product 2 with same supplier but different origin self.assertEqual(self.product_tmpl.origin_text, "Valencia, Spain")
self.env["product.supplierinfo"].create( self.assertEqual(product2_tmpl.origin_text, "Aragón, Spain")
{
"partner_id": self.supplier_1.id,
"product_tmpl_id": product2.product_tmpl_id.id,
"origin_text": "Aragón, Spain",
}
)
# Verify each product has its own origin def test_06_product_variants_share_origin(self):
self.product.invalidate_recordset() """Test that all variants of a template share the same origin"""
product2.invalidate_recordset()
self.assertEqual(self.product.origin_text, "Valencia, Spain")
self.assertEqual(product2.origin_text, "Aragón, Spain")
def test_08_product_variant_level(self):
"""Test that origin_text works at product variant level"""
# Create product template with variants # Create product template with variants
product_attr = self.env["product.attribute"].create({"name": "Color"}) product_attr = self.env["product.attribute"].create({"name": "Color"})
attr_value_red = self.env["product.attribute.value"].create( attr_value_red = self.env["product.attribute.value"].create(
@ -217,17 +98,49 @@ class TestProductOriginChar(TransactionCase):
} }
) )
# Create supplierinfo at template level # Set origin on template
self.env["product.supplierinfo"].create( product_tmpl.origin_text = "Test Origin"
# Verify all variants show the same origin
for variant in product_tmpl.product_variant_ids:
self.assertEqual(variant.origin_text, "Test Origin")
def test_07_origin_update_propagates_to_variants(self):
"""Test that updating template origin updates all variants"""
# Create product with variants
product_attr = self.env["product.attribute"].create({"name": "Size"})
attr_value_s = self.env["product.attribute.value"].create(
{"name": "S", "attribute_id": product_attr.id}
)
attr_value_m = self.env["product.attribute.value"].create(
{"name": "M", "attribute_id": product_attr.id}
)
product_tmpl = self.env["product.template"].create(
{ {
"partner_id": self.supplier_1.id, "name": "Sized Product",
"product_tmpl_id": product_tmpl.id, "type": "consu",
"origin_text": "Test Origin", "origin_text": "Original Origin",
"attribute_line_ids": [
(
0,
0,
{
"attribute_id": product_attr.id,
"value_ids": [(6, 0, [attr_value_s.id, attr_value_m.id])],
},
)
],
} }
) )
# Verify all variants show the same origin (from template level) # Verify initial origin
product_tmpl.invalidate_recordset()
for variant in product_tmpl.product_variant_ids: for variant in product_tmpl.product_variant_ids:
variant.invalidate_recordset() self.assertEqual(variant.origin_text, "Original Origin")
self.assertEqual(variant.origin_text, "Test Origin")
# Update origin on template
product_tmpl.origin_text = "Updated Origin"
# Verify all variants show updated origin
for variant in product_tmpl.product_variant_ids:
self.assertEqual(variant.origin_text, "Updated Origin")

View file

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2026 Criptomart
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<!-- Add origin_text field to product.supplierinfo form view -->
<record id="product_supplierinfo_form_view_origin_text" model="ir.ui.view">
<field name="name">product.supplierinfo.form.origin.text</field>
<field name="model">product.supplierinfo</field>
<field name="inherit_id" ref="product.product_supplierinfo_form_view" />
<field name="arch" type="xml">
<field name="product_code" position="after">
<field
name="origin_text"
placeholder="e.g., Valencia, Huerta de..., Spain"
/>
</field>
</field>
</record>
<!-- Add origin_text field to supplierinfo tree in product form -->
<record id="product_supplierinfo_tree_view_origin_text" model="ir.ui.view">
<field name="name">product.supplierinfo.tree.origin.text</field>
<field name="model">product.supplierinfo</field>
<field name="inherit_id" ref="product.product_supplierinfo_tree_view" />
<field name="arch" type="xml">
<field name="product_code" position="after">
<field name="origin_text" optional="show" />
</field>
</field>
</record>
</odoo>

View file

@ -8,13 +8,9 @@
<field name="model">product.template</field> <field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view" /> <field name="inherit_id" ref="product.product_template_form_view" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<group name="purchase" position="inside"> <xpath expr="//field[@name='categ_id']" position="after">
<field <field name="origin_text" />
name="origin_text" </xpath>
readonly="1"
string="Origin (from Main Vendor)"
/>
</group>
</field> </field>
</record> </record>
@ -34,9 +30,9 @@
<record id="product_template_tree_view_origin_text" model="ir.ui.view"> <record id="product_template_tree_view_origin_text" model="ir.ui.view">
<field name="name">product.template.tree.origin.text</field> <field name="name">product.template.tree.origin.text</field>
<field name="model">product.template</field> <field name="model">product.template</field>
<field name="inherit_id" ref="product_main_seller.view_product_template_tree" /> <field name="inherit_id" ref="product.product_template_tree_view" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="main_seller_id" position="after"> <field name="categ_id" position="after">
<field name="origin_text" optional="hide" /> <field name="origin_text" optional="hide" />
</field> </field>
</field> </field>