feat: implement dynamic price field visibility for product models based on user restrictions
This commit is contained in:
parent
a14050004b
commit
9e284bdcfb
22
README.md
22
README.md
@ -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
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -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
125
models/product.py
Normal 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
|
||||||
Loading…
Reference in New Issue
Block a user