feat: implement dynamic price field visibility for product models based on user restrictions

This commit is contained in:
Suherdy Yacob 2026-05-29 20:54:14 +07:00
parent a14050004b
commit 9e284bdcfb
4 changed files with 137 additions and 13 deletions

View File

@ -3,24 +3,22 @@
Custom Odoo 19 module to dynamically manage the visibility of buttons and financial fields on the Purchase Order form on a per-user basis. Custom Odoo 19 module to dynamically manage the visibility of buttons and financial fields on the Purchase Order form on a per-user basis.
## Features ## Features
1. **Per-User Restricting Options**: Toggle restriction flags directly on individual user accounts. - Per-User Restricting Options: Toggle restriction flags directly on individual user accounts.
2. **Hide RFQ & Confirmation Buttons**: Hide "Confirm Order" and "Send RFQ" buttons from the purchase order header for restricted users. - Hide RFQ & Confirmation Buttons: Hide "Confirm Order" and "Send RFQ" buttons from the purchase order header for restricted users.
3. **Hide Financial Fields**: Hide unit price, subtotal, and totals on purchase lists, forms, line kanbans, and footer groups for restricted users. - Hide Financial Fields: Hide unit price, subtotal, and totals on purchase lists, forms, line kanbans, and footer groups for restricted users.
4. **Flexible and Stateless**: Instant dynamically computed restrictions without storing redundant data in the database. - Flexible and Stateless: Instant dynamically computed restrictions without storing redundant data in the database.
## Installation ## Installation
1. Move the `purchase_custom_visibility` directory to Odoo's custom addons path. 1. Move the purchase_custom_visibility directory to Odoo's custom addons path.
2. Upgrade/Update Odoo addons list. 2. Upgrade/Update Odoo addons list.
3. Install the module. 3. Install the module.
## Configuration (Per-User) ## Configuration (Per-User)
1. Go to **Settings** -> **Users & Companies** -> **Users**. 1. Go to Settings -> Users & Companies -> Users.
2. Select a User record and click **Edit**. 2. Select a User record and click Edit.
3. Open the **Purchase Restrictions** tab. 3. Open the Purchase Restrictions tab.
4. Check the desired restrictions: 4. Check the desired restrictions (Hide Confirm Order & Send RFQ, Hide Price, Subtotal & Total).
- **Hide Confirm Order & Send RFQ**
- **Hide Price, Subtotal & Total**
5. Save the user record. 5. Save the user record.
## Author ## Author
- **Suherdy Yacob** Suherdy Yacob

View File

@ -5,7 +5,7 @@
'summary': 'Configure visibility of buttons and prices/totals on Purchase Order form per user.', 'summary': 'Configure visibility of buttons and prices/totals on Purchase Order form per user.',
'category': 'Inventory/Purchase', 'category': 'Inventory/Purchase',
'author': 'Suherdy Yacob', 'author': 'Suherdy Yacob',
'depends': ['purchase'], 'depends': ['purchase', 'product'],
'data': [ 'data': [
'views/res_users_views.xml', 'views/res_users_views.xml',
'views/purchase_order_views.xml', 'views/purchase_order_views.xml',

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from . import res_users from . import res_users
from . import purchase_order from . import purchase_order
from . import product

125
models/product.py Normal file
View File

@ -0,0 +1,125 @@
# -*- coding: utf-8 -*-
from odoo import models, api
from lxml import etree
class ProductSupplierinfo(models.Model):
_inherit = 'product.supplierinfo'
@api.model
def get_views(self, views, options=None):
res = super().get_views(views, options)
if not self.env.user.hide_purchase_prices:
return res
for view_type, view_data in res.get('views', {}).items():
arch_str = view_data.get('arch')
if not arch_str:
continue
try:
doc = etree.fromstring(arch_str.encode('utf-8'))
except Exception:
continue
modified = False
price_fields = {'price', 'discount'}
for field in doc.xpath("//field"):
if field.get('name') in price_fields:
is_column = False
p = field.getparent()
while p is not None:
if p.tag in ('tree', 'list'):
is_column = True
break
p = p.getparent()
if is_column:
field.set('column_invisible', 'True')
else:
field.set('invisible', '1')
modified = True
if modified:
view_data['arch'] = etree.tostring(doc, encoding='utf-8').decode('utf-8')
return res
class ProductTemplate(models.Model):
_inherit = 'product.template'
@api.model
def get_views(self, views, options=None):
res = super().get_views(views, options)
if not self.env.user.hide_purchase_prices:
return res
for view_type, view_data in res.get('views', {}).items():
arch_str = view_data.get('arch')
if not arch_str:
continue
try:
doc = etree.fromstring(arch_str.encode('utf-8'))
except Exception:
continue
modified = False
for field in doc.xpath("//field[@name='standard_price']"):
is_column = False
p = field.getparent()
while p is not None:
if p.tag in ('tree', 'list'):
is_column = True
break
p = p.getparent()
if is_column:
field.set('column_invisible', 'True')
else:
field.set('invisible', '1')
modified = True
if modified:
view_data['arch'] = etree.tostring(doc, encoding='utf-8').decode('utf-8')
return res
class ProductProduct(models.Model):
_inherit = 'product.product'
@api.model
def get_views(self, views, options=None):
res = super().get_views(views, options)
if not self.env.user.hide_purchase_prices:
return res
for view_type, view_data in res.get('views', {}).items():
arch_str = view_data.get('arch')
if not arch_str:
continue
try:
doc = etree.fromstring(arch_str.encode('utf-8'))
except Exception:
continue
modified = False
for field in doc.xpath("//field[@name='standard_price']"):
is_column = False
p = field.getparent()
while p is not None:
if p.tag in ('tree', 'list'):
is_column = True
break
p = p.getparent()
if is_column:
field.set('column_invisible', 'True')
else:
field.set('invisible', '1')
modified = True
if modified:
view_data['arch'] = etree.tostring(doc, encoding='utf-8').decode('utf-8')
return res