first commit
This commit is contained in:
commit
6b517d3100
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# Odoo
|
||||
*.pot
|
||||
*.po
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# System
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
32
README.md
Normal file
32
README.md
Normal file
@ -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.
|
||||
1
__init__.py
Normal file
1
__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from . import wizard
|
||||
19
__manifest__.py
Normal file
19
__manifest__.py
Normal file
@ -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',
|
||||
}
|
||||
2
security/ir.model.access.csv
Normal file
2
security/ir.model.access.csv
Normal file
@ -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
|
||||
|
1
wizard/__init__.py
Normal file
1
wizard/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from . import change_uom
|
||||
44
wizard/change_uom.py
Normal file
44
wizard/change_uom.py
Normal file
@ -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'}
|
||||
32
wizard/change_uom_views.xml
Normal file
32
wizard/change_uom_views.xml
Normal file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_product_uom_change_wizard_form" model="ir.ui.view">
|
||||
<field name="name">product.uom.change.wizard.form</field>
|
||||
<field name="model">product.uom.change.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Change Product UOM">
|
||||
<group>
|
||||
<field name="product_tmpl_id"/>
|
||||
<field name="current_uom_id"/>
|
||||
<field name="uom_category_id" invisible="1"/>
|
||||
<field name="new_uom_id" domain="[('category_id', '=', uom_category_id), ('id', '!=', current_uom_id)]"/>
|
||||
<field name="new_uom_po_id" domain="[('category_id', '=', uom_category_id)]"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="action_change_uom" string="Change UOM" type="object" class="btn-primary" data-hotkey="q"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="z"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_product_uom_change_wizard" model="ir.actions.act_window">
|
||||
<field name="name">Change UOM</field>
|
||||
<field name="res_model">product.uom.change.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
<field name="binding_model_id" ref="product.model_product_template"/>
|
||||
<field name="binding_view_types">form</field>
|
||||
<field name="groups_id" eval="[(4, ref('base.group_system'))]"/>
|
||||
</record>
|
||||
</odoo>
|
||||
Loading…
Reference in New Issue
Block a user