Skip to content

Commit 97c9970

Browse files
committed
[ADD] product_category_global_info: add required attribute in global info page
- Added global info and required attribute options in product category. - Updated the product category form to include these new options. - Ensured sale order retrieves details for visible categories. - Created a new page in the sale order to display these details clearly.
1 parent 4c650f3 commit 97c9970

9 files changed

+232
-0
lines changed
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from . import models
2+
from . import tests
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
'name': "Product Category Global Info",
3+
'version': '1.0',
4+
'summary': 'Extends product category with global info fields and a new tab grouping attributes by category.',
5+
'description': """
6+
This module extends the product category form by:
7+
- Adding a boolean field "Show on Global Info"
8+
- Adding a many2many field to map required attributes
9+
It also adds a new tab called "Global Info" that displays category and attribute information grouped by product category.
10+
""",
11+
'author':'Odoo S.A.',
12+
'category': 'Productivity',
13+
'depends': ['stock', 'sale_management'],
14+
'data': [
15+
'views/product_category_global_info.xml',
16+
'views/sale_order_view_inherit.xml',
17+
],
18+
'license': 'AGPL-3'
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from . import product_category
2+
from . import sale_order_line
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from odoo import fields, models
2+
3+
4+
class ProductCategory(models.Model):
5+
_inherit = 'product.category'
6+
7+
show_on_global_info = fields.Boolean(
8+
string="Show on Global info",
9+
help="If checked, this category will appear in the 'Global Info' tab of Sale Orders.",
10+
default=False,
11+
store=True
12+
)
13+
14+
required_attribute_ids = fields.Many2many(
15+
comodel_name="product.attribute",
16+
string="Required Attribute",
17+
help="Attributes that must be specified for products in this category."
18+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from odoo import Command, api, fields, models
2+
3+
4+
class SaleOrderLine(models.Model):
5+
_inherit = 'sale.order.line'
6+
7+
product_category_id = fields.Many2one(
8+
comodel_name='product.category',
9+
string="Product Category",
10+
compute="_compute_product_category",
11+
store=True
12+
)
13+
14+
attribute_ids = fields.Many2many(
15+
comodel_name='product.attribute',
16+
string="Attributes"
17+
)
18+
19+
attribute_value_ids = fields.Many2many(
20+
comodel_name='product.attribute.value',
21+
string="Attribute Value"
22+
)
23+
24+
@api.depends('product_id')
25+
def _compute_product_category(self):
26+
for line in self:
27+
if line.product_id and line.product_id.categ_id.show_on_global_info:
28+
line.product_category_id = line.product_id.categ_id
29+
else:
30+
line.product_category_id = False
31+
32+
@api.onchange('product_category_id')
33+
def _onchange_product_category(self):
34+
if self.product_category_id:
35+
required_attributes = self.product_category_id.required_attribute_ids
36+
default_attribute_values = []
37+
default_attributes = []
38+
39+
for attribute in required_attributes:
40+
selected_value = self.product_template_attribute_value_ids.filtered(lambda v: v.attribute_id == attribute)
41+
default_attributes.append(attribute.id)
42+
default_attribute_values.append(selected_value.product_attribute_value_id.id)
43+
self.attribute_ids = Command.set([default_attributes])
44+
self.attribute_value_ids = Command.set([default_attribute_values])
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import test_required_attributes
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
from odoo.tests.common import TransactionCase
2+
from odoo.tests import tagged
3+
from odoo import Command
4+
5+
@tagged("post_install", "-at_install")
6+
class TestRequiredAttributes(TransactionCase):
7+
8+
@classmethod
9+
def setUpClass(cls):
10+
super(TestRequiredAttributes, cls).setUpClass()
11+
12+
cls.test_required_attribute = cls.env["product.attribute"].create({
13+
"name": "Required Attribute"
14+
})
15+
cls.test_required_values = cls.env["product.attribute.value"].create([
16+
{
17+
"name": "Required Attribute 1",
18+
"attribute_id": cls.test_required_attribute.id
19+
},
20+
{
21+
"name": "Required Attribute 2",
22+
"attribute_id": cls.test_required_attribute.id
23+
},
24+
{
25+
"name": "Required Attribute 3",
26+
"attribute_id": cls.test_required_attribute.id
27+
}
28+
])
29+
30+
cls.test_attribute = cls.env["product.attribute"].create({
31+
"name": "Normal Attribute"
32+
})
33+
cls.test_attribute_values = cls.env["product.attribute.value"].create([
34+
{
35+
"name": "Attribute 1",
36+
"attribute_id": cls.test_attribute.id
37+
},
38+
{
39+
"name": "Attribute 2",
40+
"attribute_id": cls.test_attribute.id
41+
},
42+
{
43+
"name": "Attribute 3",
44+
"attribute_id": cls.test_attribute.id
45+
}
46+
])
47+
48+
cls.category = cls.env['product.category'].create([{
49+
'name': 'Test Category',
50+
'show_on_global_info': True,
51+
'required_attribute_ids': [Command.set([cls.test_required_attribute.id])]
52+
}])
53+
54+
cls.product = cls.env["product.product"].create({
55+
"name": "Test Product",
56+
"categ_id": cls.category.id,
57+
"attribute_line_ids": [Command.create({
58+
"attribute_id": cls.test_required_attribute.id,
59+
"value_ids": [Command.set(cls.test_required_values.ids)]
60+
}), Command.create({
61+
"attribute_id": cls.test_attribute.id,
62+
"value_ids": [Command.set(cls.test_attribute_values.ids)]
63+
})]
64+
})
65+
66+
67+
cls.sale_order = cls.env["sale.order"].create({"partner_id": cls.env.ref("base.res_partner_1").id})
68+
cls.sale_order_line = cls.env["sale.order.line"].create({
69+
"order_id": cls.sale_order.id,
70+
"product_id": cls.product.id
71+
})
72+
73+
def test_category_fields(self):
74+
self.assertTrue(self.category.show_on_global_info, "Category should have 'show_on_global_info' set to True")
75+
self.assertEqual(len(self.category.required_attribute_ids), 1, "Category should have one required attributes (test_required_attribute)")
76+
77+
def test_product_attributes(self):
78+
product_attributes = self.product.attribute_line_ids.mapped("attribute_id.name")
79+
self.assertIn("Required Attribute", product_attributes, "Product should have 'Required Attribute' in attribute")
80+
self.assertIn("Normal Attribute", product_attributes, "Product should have 'Normal Attribute' in attribute")
81+
82+
def test_sale_order_line_global_info(self):
83+
if self.sale_order_line.product_id.categ_id.show_on_global_info:
84+
displayed_attributes = self.sale_order_line.product_id.categ_id.required_attribute_ids.mapped("name")
85+
self.assertIn("Required Attribute", displayed_attributes, "Global info should show 'Required Attribute'")
86+
self.assertNotIn("Normal Attribute", displayed_attributes, "Global info should NOT show 'Normal Attribute'")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<odoo>
2+
<record id="view_product_category_form_inherit" model="ir.ui.view">
3+
<field name="name">product.category.form.inherit</field>
4+
<field name="model">product.category</field>
5+
<field name="inherit_id" ref="stock.product_category_form_view_inherit"/>
6+
<field name="arch" type="xml">
7+
<xpath expr="//field[@name='removal_strategy_id']" position="after">
8+
<field name="show_on_global_info"/>
9+
<field name="required_attribute_ids" widget="many2many_tags"/>
10+
</xpath>
11+
</field>
12+
</record>
13+
</odoo>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<odoo>
2+
3+
<record id="view_sale_order_line_list_global_info" model="ir.ui.view">
4+
<field name="name">sale.order.line.list.global.info</field>
5+
<field name="model">sale.order.line</field>
6+
<field name="arch" type="xml">
7+
<list default_group_by="product_category_id" create="false" delete="false">
8+
<field name="product_category_id" string="Product Category"/>
9+
<field name="attribute_ids" widget="many2many_tags" string="Attributes"/>
10+
<field name="attribute_value_ids" widget="many2many_tags" string="Attribute Values"/>
11+
</list>
12+
</field>
13+
</record>
14+
15+
<record id="view_sale_order_global_info" model="ir.ui.view">
16+
<field name="name">sale.order.global.info.form</field>
17+
<field name="model">sale.order</field>
18+
<field name="inherit_id" ref="sale.view_order_form"/>
19+
<field name="arch" type="xml">
20+
<xpath expr="//notebook" position="inside">
21+
<page string="Global Info">
22+
<field name="order_line" context="{'list_view_ref': 'product_category_global_info.view_sale_order_line_list_global_info'}"/>
23+
</page>
24+
</xpath>
25+
</field>
26+
</record>
27+
28+
<record id="view_sale_order_global_info_search" model="ir.ui.view">
29+
<field name="name">sale.order.global.info.search</field>
30+
<field name="model">sale.order.line</field>
31+
<field name="arch" type="xml">
32+
<search>
33+
<filter name="group_by_product_category" context="{'group_by': 'product_category_id'}"/>
34+
</search>
35+
</field>
36+
</record>
37+
38+
<record id="sale_order_view_global_info" model="ir.actions.act_window">
39+
<field name="name">Global Info</field>
40+
<field name="res_model">sale.order.line</field>
41+
<field name="view_id" ref="view_sale_order_line_list_global_info"/>
42+
<field name="search_view_id" ref="view_sale_order_global_info_search"/>
43+
<field name="view_mode">list</field>
44+
<field name="context">{'search_default_group_by_product_category': 1}</field>
45+
</record>
46+
47+
</odoo>

0 commit comments

Comments
 (0)