From dee4dc62136512dde20b7b86f6cf1b62617aebb5 Mon Sep 17 00:00:00 2001 From: mekav-odoo Date: Tue, 17 Feb 2026 18:41:28 +0530 Subject: [PATCH] [ADD] pos: implement second UoM - Add pos_second_uom_id to product template. - Implement inherited from NumberPopup to handle quantity entry in secondary units. - Patch ControlButtons to include a second UoM button. - Add conversion logic to calculate the main UoM quantity based on absolute factors. --- second_uom/__init__.py | 1 + second_uom/__manifest__.py | 19 +++++++++++ second_uom/models/__init__.py | 2 ++ second_uom/models/product_product.py | 11 +++++++ second_uom/models/product_template.py | 30 +++++++++++++++++ .../components/uom_popup/second_uom_popup.js | 14 ++++++++ .../components/uom_popup/second_uom_popup.xml | 15 +++++++++ .../control_buttons/control_buttons.js | 32 +++++++++++++++++++ .../control_buttons/control_buttons.xml | 12 +++++++ second_uom/views/product_views.xml | 13 ++++++++ 10 files changed, 149 insertions(+) create mode 100644 second_uom/__init__.py create mode 100644 second_uom/__manifest__.py create mode 100644 second_uom/models/__init__.py create mode 100644 second_uom/models/product_product.py create mode 100644 second_uom/models/product_template.py create mode 100644 second_uom/static/src/app/components/uom_popup/second_uom_popup.js create mode 100644 second_uom/static/src/app/components/uom_popup/second_uom_popup.xml create mode 100644 second_uom/static/src/app/screens/product_screen/control_buttons/control_buttons.js create mode 100644 second_uom/static/src/app/screens/product_screen/control_buttons/control_buttons.xml create mode 100644 second_uom/views/product_views.xml diff --git a/second_uom/__init__.py b/second_uom/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/second_uom/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/second_uom/__manifest__.py b/second_uom/__manifest__.py new file mode 100644 index 00000000000..030c1469e22 --- /dev/null +++ b/second_uom/__manifest__.py @@ -0,0 +1,19 @@ +{ + 'name': 'second_uom', + 'description': "Add Button for second uom", + 'author': "meet kavathiya", + 'website': 'https://www.odoo.com/', + 'category': '', + 'version': '0.1', + 'application': True, + 'installable': True, + 'depends': ['point_of_sale'], + 'data': ['views/product_views.xml'], + 'assets': { + 'point_of_sale._assets_pos': [ + 'second_uom/static/src/app/**/*.js', + 'second_uom/static/src/app/**/*.xml', + ], + }, + 'license': 'LGPL-3', +} diff --git a/second_uom/models/__init__.py b/second_uom/models/__init__.py new file mode 100644 index 00000000000..049669dd0fe --- /dev/null +++ b/second_uom/models/__init__.py @@ -0,0 +1,2 @@ +from . import product_template +from . import product_product diff --git a/second_uom/models/product_product.py b/second_uom/models/product_product.py new file mode 100644 index 00000000000..f1874b40557 --- /dev/null +++ b/second_uom/models/product_product.py @@ -0,0 +1,11 @@ +from odoo import api, models + + +class InheritedProductProduct(models.Model): + _inherit = 'product.product' + + @api.model + def _load_pos_data_fields(self, config): + params = super()._load_pos_data_fields(config) + params.append('pos_second_uom_id') + return params diff --git a/second_uom/models/product_template.py b/second_uom/models/product_template.py new file mode 100644 index 00000000000..6e189f27f1d --- /dev/null +++ b/second_uom/models/product_template.py @@ -0,0 +1,30 @@ +from odoo import api, fields, models + + +class InheritedProductTemplate(models.Model): + _inherit = 'product.template' + + pos_second_uom_id = fields.Many2one( + 'uom.uom', + string="POS Second UoM", + help="Secondary unit of measure for quick quantity conversion in the POS popup.", + ) + uom_root_id = fields.Many2one( + 'uom.uom', string="UoM Root", compute='_compute_uom_root_id' + ) + + @api.depends('uom_id') + def _compute_uom_root_id(self): + for record in self: + if record.uom_id and record.uom_id.parent_path: + root_id = int(record.uom_id.parent_path.split('/')[0]) + record.uom_root_id = root_id + else: + record.uom_root_id = False + + @api.onchange('uom_id') + def _onchange_uom_id_clear_second(self): + if self.pos_second_uom_id and not self.uom_id._has_common_reference( + self.pos_second_uom_id + ): + self.pos_second_uom_id = False diff --git a/second_uom/static/src/app/components/uom_popup/second_uom_popup.js b/second_uom/static/src/app/components/uom_popup/second_uom_popup.js new file mode 100644 index 00000000000..43164cf6f90 --- /dev/null +++ b/second_uom/static/src/app/components/uom_popup/second_uom_popup.js @@ -0,0 +1,14 @@ +import { NumberPopup } from "@point_of_sale/app/components/popups/number_popup/number_popup"; + +export class SecondUomPopup extends NumberPopup { + static template = "second_uom.SecondUomPopup"; + static props = { + ...NumberPopup.props, + uomName: { type: String }, + }; + + confirm() { + this.props.getPayload(this.state.buffer); + this.props.close(); + } +} diff --git a/second_uom/static/src/app/components/uom_popup/second_uom_popup.xml b/second_uom/static/src/app/components/uom_popup/second_uom_popup.xml new file mode 100644 index 00000000000..17685b3236d --- /dev/null +++ b/second_uom/static/src/app/components/uom_popup/second_uom_popup.xml @@ -0,0 +1,15 @@ + + + + +
+ Unit: +
+
+ + + () + + +
+
diff --git a/second_uom/static/src/app/screens/product_screen/control_buttons/control_buttons.js b/second_uom/static/src/app/screens/product_screen/control_buttons/control_buttons.js new file mode 100644 index 00000000000..8ebc3808a0b --- /dev/null +++ b/second_uom/static/src/app/screens/product_screen/control_buttons/control_buttons.js @@ -0,0 +1,32 @@ +import { ControlButtons } from "@point_of_sale/app/screens/product_screen/control_buttons/control_buttons"; +import { SecondUomPopup } from "@second_uom/app/components/uom_popup/second_uom_popup"; +import { patch } from "@web/core/utils/patch"; +import { makeAwaitable } from "@point_of_sale/app/utils/make_awaitable_dialog"; + +patch(ControlButtons.prototype, { + displaySecondUomBtn() { + const selectedLine = this.currentOrder?.getSelectedOrderline(); + return selectedLine && selectedLine.product_id.pos_second_uom_id; + }, + + async clickSecondUom() { + const selectedLine = this.currentOrder.getSelectedOrderline(); + if (!selectedLine) return; + + const secondUom = selectedLine.product_id.pos_second_uom_id; + const mainUom = selectedLine.product_id.uom_id; + + const result = await makeAwaitable(this.dialog, SecondUomPopup, { + title: "Secondary Unit Entry", + uomName: secondUom.name, + startingValue: 0, + }); + + if (result !== null && result !== undefined) { + const enteredQty = parseFloat(result); + if (isNaN(enteredQty)) return; + const newMainQty = (enteredQty * secondUom.factor) / mainUom.factor; + selectedLine.setQuantity(newMainQty); + } + } +}); diff --git a/second_uom/static/src/app/screens/product_screen/control_buttons/control_buttons.xml b/second_uom/static/src/app/screens/product_screen/control_buttons/control_buttons.xml new file mode 100644 index 00000000000..748b08d4d69 --- /dev/null +++ b/second_uom/static/src/app/screens/product_screen/control_buttons/control_buttons.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/second_uom/views/product_views.xml b/second_uom/views/product_views.xml new file mode 100644 index 00000000000..06df256796e --- /dev/null +++ b/second_uom/views/product_views.xml @@ -0,0 +1,13 @@ + + + + product.template.form.inherit.second.uom + product.template + + + + + + + +