diff --git a/README.md b/README.md index 174e89c..f1a34aa 100644 --- a/README.md +++ b/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. ## Features -1. **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. -3. **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. +- Per-User Restricting Options: Toggle restriction flags directly on individual user accounts. +- Hide RFQ & Confirmation Buttons: Hide "Confirm Order" and "Send RFQ" buttons from the purchase order header for restricted users. +- Hide Financial Fields: Hide unit price, subtotal, and totals on purchase lists, forms, line kanbans, and footer groups for restricted users. +- Flexible and Stateless: Instant dynamically computed restrictions without storing redundant data in the database. ## 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. 3. Install the module. ## Configuration (Per-User) -1. Go to **Settings** -> **Users & Companies** -> **Users**. -2. Select a User record and click **Edit**. -3. Open the **Purchase Restrictions** tab. -4. Check the desired restrictions: - - **Hide Confirm Order & Send RFQ** - - **Hide Price, Subtotal & Total** +1. Go to Settings -> Users & Companies -> Users. +2. Select a User record and click Edit. +3. Open the Purchase Restrictions tab. +4. Check the desired restrictions (Hide Confirm Order & Send RFQ, Hide Price, Subtotal & Total). 5. Save the user record. ## Author -- **Suherdy Yacob** +Suherdy Yacob diff --git a/__manifest__.py b/__manifest__.py index 361af3b..e769b35 100644 --- a/__manifest__.py +++ b/__manifest__.py @@ -5,7 +5,7 @@ 'summary': 'Configure visibility of buttons and prices/totals on Purchase Order form per user.', 'category': 'Inventory/Purchase', 'author': 'Suherdy Yacob', - 'depends': ['purchase'], + 'depends': ['purchase', 'product'], 'data': [ 'views/res_users_views.xml', 'views/purchase_order_views.xml', diff --git a/models/__init__.py b/models/__init__.py index 8f72eb1..3ea65dd 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -1,3 +1,4 @@ # -*- coding: utf-8 -*- from . import res_users from . import purchase_order +from . import product diff --git a/models/product.py b/models/product.py new file mode 100644 index 0000000..03dafd8 --- /dev/null +++ b/models/product.py @@ -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