commit 6b517d3100f549679c2792a5285a9d1b8c467e09 Author: Suherdy Yacob Date: Mon Jan 12 11:38:09 2026 +0700 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..559269c --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class + +# Odoo +*.pot +*.po + +# IDE +.vscode/ +.idea/ + +# System +.DS_Store +Thumbs.db diff --git a/README.md b/README.md new file mode 100644 index 0000000..42d0c5e --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# Product UOM Change + +**Author:** Suherdy Yacob +**Version:** 18.0.1.0.0 +**License:** LGPL-3 +**Category:** Inventory + +## Description +This module allows authorized users to change the Unit of Measure (UOM) of a product even if it has existing stock moves, bypassing the standard Odoo restriction. + +It enforces that the new UOM must belong to the **same UOM Category** as the current UOM to maintain data integrity regarding physical quantities. + +> **WARNING:** This module uses raw SQL to bypass Odoo's standard validation. While it enforces category consistency, changing UOMs on products with existing stock can lead to inventory valuation and quantity inconsistencies if the conversion factors differ. Use with caution. + +## Configuration +No specific configuration is required. +Access to the "Change UOM" feature is restricted to users in the **Administration / Settings** group (`base.group_system`). + +## Usage +1. Navigate to **Inventory > Products > Products**. +2. Open the product form for the product you wish to modify. +3. Click on the **Actions** (gear icon) menu. +4. Select **Change UOM**. +5. In the wizard that appears: + * **New UOM**: Select the new Unit of Measure. The list is filtered to show only UOMs in the same category. + * **New Purchase UOM**: (Optional) Select a new UOM for purchasing, if different. +6. Click the **Change UOM** button to apply the changes. + +## Technical Details +* **Model:** `product.uom.change.wizard` +* **Access Rights:** Wizard access is granted to internal users, but the action is restricted to system administrators. +* **Logic:** Updates `product.template` directly via SQL to avoid the `write` method constraint check in `stock` module. diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..4027237 --- /dev/null +++ b/__init__.py @@ -0,0 +1 @@ +from . import wizard diff --git a/__manifest__.py b/__manifest__.py new file mode 100644 index 0000000..be1eaf8 --- /dev/null +++ b/__manifest__.py @@ -0,0 +1,19 @@ +{ + 'name': 'Product UOM Change', + 'version': '18.0.1.0.0', + 'summary': 'Allow changing Product UOM even if stock moves exist', + 'description': """ + This module adds a wizard to allow changing the Unit of Measure (UOM) of a product + even if it has existing stock moves, provided the new UOM is in the same category. + + WARNING: This bypasses Odoo's standard validation. Use with caution. + """, + 'category': 'Inventory', + 'author': 'Suherdy Yacob', + 'depends': ['stock', 'product'], + 'data': [ + 'security/ir.model.access.csv', + 'wizard/change_uom_views.xml', + ], + 'license': 'LGPL-3', +} diff --git a/security/ir.model.access.csv b/security/ir.model.access.csv new file mode 100644 index 0000000..3ee4b9c --- /dev/null +++ b/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_product_uom_change_wizard,product.uom.change.wizard,model_product_uom_change_wizard,base.group_user,1,1,1,1 diff --git a/wizard/__init__.py b/wizard/__init__.py new file mode 100644 index 0000000..83090c5 --- /dev/null +++ b/wizard/__init__.py @@ -0,0 +1 @@ +from . import change_uom diff --git a/wizard/change_uom.py b/wizard/change_uom.py new file mode 100644 index 0000000..37bedf3 --- /dev/null +++ b/wizard/change_uom.py @@ -0,0 +1,44 @@ +from odoo import api, fields, models, _ +from odoo.exceptions import UserError + +class ProductUomChangeWizard(models.TransientModel): + _name = 'product.uom.change.wizard' + _description = 'Change Product UOM Wizard' + + product_tmpl_id = fields.Many2one('product.template', string='Product', required=True, readonly=True) + current_uom_id = fields.Many2one('uom.uom', related='product_tmpl_id.uom_id', string='Current UOM', readonly=True) + uom_category_id = fields.Many2one('uom.category', related='product_tmpl_id.uom_id.category_id', readonly=True) + new_uom_id = fields.Many2one('uom.uom', string='New UOM', required=True) + new_uom_po_id = fields.Many2one('uom.uom', string='New Purchase UOM') + + @api.model + def default_get(self, fields): + res = super(ProductUomChangeWizard, self).default_get(fields) + if self._context.get('active_model') == 'product.template' and self._context.get('active_id'): + res['product_tmpl_id'] = self._context['active_id'] + elif self._context.get('active_model') == 'product.product' and self._context.get('active_id'): + product = self.env['product.product'].browse(self._context['active_id']) + res['product_tmpl_id'] = product.product_tmpl_id.id + return res + + def action_change_uom(self): + self.ensure_one() + if self.new_uom_id.category_id != self.product_tmpl_id.uom_id.category_id: + raise UserError(_("New UOM must be in the same category as the current UOM.")) + + # Update UOM via SQL to bypass the constraint check in product.template write + self.env.cr.execute("UPDATE product_template SET uom_id = %s WHERE id = %s", (self.new_uom_id.id, self.product_tmpl_id.id)) + + # Also update Purchase UOM if specified + if self.new_uom_po_id: + if self.new_uom_po_id.category_id != self.product_tmpl_id.uom_po_id.category_id: + raise UserError(_("New Purchase UOM must be in the same category as the current Purchase UOM.")) + self.env.cr.execute("UPDATE product_template SET uom_po_id = %s WHERE id = %s", (self.new_uom_po_id.id, self.product_tmpl_id.id)) + + # Invalidate cache to ensure the new value is read + self.product_tmpl_id.invalidate_recordset(['uom_id', 'uom_po_id']) + + # Log note in chatter + self.product_tmpl_id.message_post(body=f"UOM forcefully changed from {self.current_uom_id.name} to {self.new_uom_id.name} via Wizard.") + + return {'type': 'ir.actions.act_window_close'} diff --git a/wizard/change_uom_views.xml b/wizard/change_uom_views.xml new file mode 100644 index 0000000..95b9bc0 --- /dev/null +++ b/wizard/change_uom_views.xml @@ -0,0 +1,32 @@ + + + + product.uom.change.wizard.form + product.uom.change.wizard + +
+ + + + + + + +
+
+
+
+
+ + + Change UOM + product.uom.change.wizard + form + new + + form + + +