[ADD] product_origin_char: Free text origin field per supplier

New addon to replace structured country/state fields with flexible
free-text origin descriptions.

Features:
- Translatable origin_text field in product.supplierinfo
- Computed origin field in products based on main_seller_id
- Support for creative supplier origin descriptions
- Full OCA documentation structure
- ES/EU translations included
- 8 unit tests (all passing)

Replaces product_origin for use cases where suppliers use non-standardized
origin descriptions (e.g., 'Valencia, Spain', 'Huerta de...', etc.)

Depends on: product, product_main_seller
Author: Criptomart
Funding: Elika Bilbo
This commit is contained in:
snt 2026-02-25 18:42:54 +01:00
parent 1a8f92a01e
commit c8b83cc333
24 changed files with 1071 additions and 0 deletions

View file

@ -0,0 +1,4 @@
# Copyright 2026 Criptomart
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import test_product_origin_char # noqa: F401

View file

@ -0,0 +1,233 @@
# Copyright 2026 Criptomart
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.tests.common import TransactionCase
class TestProductOriginChar(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
# Create test suppliers
cls.supplier_1 = cls.env["res.partner"].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",
"type": "consu",
}
)
def test_01_origin_text_in_supplierinfo(self):
"""Test that origin_text can be set in supplierinfo"""
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",
}
)
self.assertEqual(supplierinfo.origin_text, "Valencia, Spain")
def test_02_origin_from_main_seller(self):
"""Test that product shows origin from main seller"""
# Create supplierinfo for supplier 1 (will be main seller)
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",
}
)
# 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")
def test_04_empty_origin_without_supplier(self):
"""Test that product without suppliers has no origin"""
# Create product without suppliers
product_no_supplier = self.env["product.product"].create(
{
"name": "Product Without Supplier",
"type": "consu",
}
)
# Verify no main seller and no origin
self.assertFalse(product_no_supplier.product_tmpl_id.main_seller_id)
self.assertFalse(product_no_supplier.origin_text)
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",
"type": "consu",
}
)
# Create supplierinfo for product 1
self.env["product.supplierinfo"].create(
{
"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.env["product.supplierinfo"].create(
{
"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
self.product.invalidate_recordset()
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
product_attr = self.env["product.attribute"].create({"name": "Color"})
attr_value_red = self.env["product.attribute.value"].create(
{"name": "Red", "attribute_id": product_attr.id}
)
attr_value_blue = self.env["product.attribute.value"].create(
{"name": "Blue", "attribute_id": product_attr.id}
)
product_tmpl = self.env["product.template"].create(
{
"name": "Product with Variants",
"type": "consu",
"attribute_line_ids": [
(
0,
0,
{
"attribute_id": product_attr.id,
"value_ids": [
(6, 0, [attr_value_red.id, attr_value_blue.id])
],
},
)
],
}
)
# Create supplierinfo at template level
self.env["product.supplierinfo"].create(
{
"partner_id": self.supplier_1.id,
"product_tmpl_id": product_tmpl.id,
"origin_text": "Test Origin",
}
)
# Verify all variants show the same origin (from template level)
product_tmpl.invalidate_recordset()
for variant in product_tmpl.product_variant_ids:
variant.invalidate_recordset()
self.assertEqual(variant.origin_text, "Test Origin")