From 9302ca8c3d022d5ec3b03c0470ad6b98d6a3c8fc Mon Sep 17 00:00:00 2001 From: Morita Shinnosuke Date: Fri, 12 Jun 2026 08:06:43 +0000 Subject: [PATCH 1/3] [ADD] sale_order_secondary_unit_convert --- sale_order_secondary_unit_convert/README.rst | 57 +++ sale_order_secondary_unit_convert/__init__.py | 1 + .../__manifest__.py | 17 + .../models/__init__.py | 1 + .../models/sale_order_line.py | 55 +++ .../pyproject.toml | 3 + .../readme/DESCRIPTION.md | 2 + .../static/description/index.html | 410 ++++++++++++++++++ .../views/sale_order_views.xml | 31 ++ 9 files changed, 577 insertions(+) create mode 100644 sale_order_secondary_unit_convert/README.rst create mode 100644 sale_order_secondary_unit_convert/__init__.py create mode 100644 sale_order_secondary_unit_convert/__manifest__.py create mode 100644 sale_order_secondary_unit_convert/models/__init__.py create mode 100644 sale_order_secondary_unit_convert/models/sale_order_line.py create mode 100644 sale_order_secondary_unit_convert/pyproject.toml create mode 100644 sale_order_secondary_unit_convert/readme/DESCRIPTION.md create mode 100644 sale_order_secondary_unit_convert/static/description/index.html create mode 100644 sale_order_secondary_unit_convert/views/sale_order_views.xml diff --git a/sale_order_secondary_unit_convert/README.rst b/sale_order_secondary_unit_convert/README.rst new file mode 100644 index 00000000..bf1a36e6 --- /dev/null +++ b/sale_order_secondary_unit_convert/README.rst @@ -0,0 +1,57 @@ +================================= +Sale Order Secondary Unit Convert +================================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:77e2e5ee13ea91067966507a56acdf7acf6515f5dc6cb3596b8a195ffbf8bab6 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-qrtl%2Fhls--custom-lightgray.png?logo=github + :target: https://github.com/qrtl/hls-custom/tree/18.0/sale_order_secondary_unit_convert + :alt: qrtl/hls-custom + +|badge1| |badge2| |badge3| + +This module lets users enter sale order line quantities in a convenient +input secondary unit while the line keeps being displayed in another +secondary unit. + +**Table of contents** + +.. contents:: + :local: + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Quartile + +Maintainers +----------- + +This module is part of the `qrtl/hls-custom `_ project on GitHub. + +You are welcome to contribute. diff --git a/sale_order_secondary_unit_convert/__init__.py b/sale_order_secondary_unit_convert/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/sale_order_secondary_unit_convert/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/sale_order_secondary_unit_convert/__manifest__.py b/sale_order_secondary_unit_convert/__manifest__.py new file mode 100644 index 00000000..082784ff --- /dev/null +++ b/sale_order_secondary_unit_convert/__manifest__.py @@ -0,0 +1,17 @@ +# Copyright 2026 Quartile (https://www.quartile.co) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "Sale Order Secondary Unit Convert", + "summary": "Enter sale order line quantities in a secondary input unit" + " while displaying another secondary unit.", + "version": "18.0.1.0.0", + "author": "Quartile", + "website": "https://www.quartile.co", + "category": "Sale", + "license": "AGPL-3", + "depends": ["sale_order_secondary_unit"], + "data": [ + "views/sale_order_views.xml", + ], + "installable": True, +} diff --git a/sale_order_secondary_unit_convert/models/__init__.py b/sale_order_secondary_unit_convert/models/__init__.py new file mode 100644 index 00000000..8eb9d1d4 --- /dev/null +++ b/sale_order_secondary_unit_convert/models/__init__.py @@ -0,0 +1 @@ +from . import sale_order_line diff --git a/sale_order_secondary_unit_convert/models/sale_order_line.py b/sale_order_secondary_unit_convert/models/sale_order_line.py new file mode 100644 index 00000000..d7a200f9 --- /dev/null +++ b/sale_order_secondary_unit_convert/models/sale_order_line.py @@ -0,0 +1,55 @@ +# Copyright 2026 Quartile (https://www.quartile.co) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models +from odoo.tools.float_utils import float_round + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + input_secondary_uom_id = fields.Many2one( + comodel_name="product.secondary.unit", + string="Input Unit", + ) + input_secondary_qty = fields.Float( + string="Input Qty", + digits="Product Unit of Measure", + compute="_compute_input_secondary_qty", + store=True, + readonly=False, + ) + + @api.depends( + "product_id", "product_uom_qty", "product_uom", "input_secondary_uom_id" + ) + def _compute_input_secondary_qty(self): + for line in self: + uom = line.input_secondary_uom_id + if not uom or uom.dependency_type == "independent": + line.input_secondary_qty = 0.0 + continue + line.input_secondary_qty = float_round( + line._secondary_base_qty() / (uom.factor or 1.0), + precision_rounding=uom.uom_id.rounding, + ) + + def _secondary_base_qty(self): + self.ensure_one() + qty = self.product_uom_qty + uom_line = self.product_uom + uom_product = self.product_id.uom_id + if uom_line and uom_product and uom_line != uom_product: + qty = uom_line._compute_quantity(qty, uom_product) + return qty + + @api.onchange("input_secondary_qty") + def _onchange_input_secondary(self): + uom = self.input_secondary_uom_id + if not uom or uom.dependency_type == "independent" or not self.product_id: + return + base_qty = self.input_secondary_qty * (uom.factor or 1.0) + self.product_uom_qty = self.product_id.uom_id._compute_quantity( + base_qty, self.product_uom + ) + self.env.remove_to_compute(self._fields["input_secondary_qty"], self) diff --git a/sale_order_secondary_unit_convert/pyproject.toml b/sale_order_secondary_unit_convert/pyproject.toml new file mode 100644 index 00000000..4231d0cc --- /dev/null +++ b/sale_order_secondary_unit_convert/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/sale_order_secondary_unit_convert/readme/DESCRIPTION.md b/sale_order_secondary_unit_convert/readme/DESCRIPTION.md new file mode 100644 index 00000000..410ee6e6 --- /dev/null +++ b/sale_order_secondary_unit_convert/readme/DESCRIPTION.md @@ -0,0 +1,2 @@ +This module lets users enter sale order line quantities in a convenient input +secondary unit while the line keeps being displayed in another secondary unit. diff --git a/sale_order_secondary_unit_convert/static/description/index.html b/sale_order_secondary_unit_convert/static/description/index.html new file mode 100644 index 00000000..b5297141 --- /dev/null +++ b/sale_order_secondary_unit_convert/static/description/index.html @@ -0,0 +1,410 @@ + + + + + +Sale Order Secondary Unit Convert + + + +
+

Sale Order Secondary Unit Convert

+ + +

Beta License: AGPL-3 qrtl/hls-custom

+

This module lets users enter sale order line quantities in a convenient +input secondary unit while the line keeps being displayed in another +secondary unit.

+

Table of contents

+ +
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Quartile
  • +
+
+
+

Maintainers

+

This module is part of the qrtl/hls-custom project on GitHub.

+

You are welcome to contribute.

+
+
+
+ + diff --git a/sale_order_secondary_unit_convert/views/sale_order_views.xml b/sale_order_secondary_unit_convert/views/sale_order_views.xml new file mode 100644 index 00000000..d4fd0f5e --- /dev/null +++ b/sale_order_secondary_unit_convert/views/sale_order_views.xml @@ -0,0 +1,31 @@ + + + + + sale.order.form.secondary.unit.input + sale.order + + + + + + + + + + From cab23f45f4765abc0e21979902d38145fa4d77ef Mon Sep 17 00:00:00 2001 From: Morita Shinnosuke Date: Mon, 22 Jun 2026 06:44:15 +0000 Subject: [PATCH 2/3] fixup! --- .../models/sale_order_line.py | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/sale_order_secondary_unit_convert/models/sale_order_line.py b/sale_order_secondary_unit_convert/models/sale_order_line.py index d7a200f9..821301a4 100644 --- a/sale_order_secondary_unit_convert/models/sale_order_line.py +++ b/sale_order_secondary_unit_convert/models/sale_order_line.py @@ -16,10 +16,24 @@ class SaleOrderLine(models.Model): string="Input Qty", digits="Product Unit of Measure", compute="_compute_input_secondary_qty", + inverse="_inverse_input_secondary_qty", store=True, readonly=False, ) + def _convert_qty_to_input_secondary_uom(self): + self.ensure_one() + uom = self.input_secondary_uom_id + qty = self.product_uom_qty + uom_line = self.product_uom + uom_product = self.product_id.uom_id + if uom_line and uom_product and uom_line != uom_product: + qty = uom_line._compute_quantity(qty, uom_product) + return float_round( + qty / (uom.factor or 1.0), + precision_rounding=uom.uom_id.rounding, + ) + @api.depends( "product_id", "product_uom_qty", "product_uom", "input_secondary_uom_id" ) @@ -29,27 +43,15 @@ def _compute_input_secondary_qty(self): if not uom or uom.dependency_type == "independent": line.input_secondary_qty = 0.0 continue - line.input_secondary_qty = float_round( - line._secondary_base_qty() / (uom.factor or 1.0), - precision_rounding=uom.uom_id.rounding, - ) + line.input_secondary_qty = line._convert_qty_to_input_secondary_uom() - def _secondary_base_qty(self): - self.ensure_one() - qty = self.product_uom_qty - uom_line = self.product_uom - uom_product = self.product_id.uom_id - if uom_line and uom_product and uom_line != uom_product: - qty = uom_line._compute_quantity(qty, uom_product) - return qty - - @api.onchange("input_secondary_qty") - def _onchange_input_secondary(self): - uom = self.input_secondary_uom_id - if not uom or uom.dependency_type == "independent" or not self.product_id: - return - base_qty = self.input_secondary_qty * (uom.factor or 1.0) - self.product_uom_qty = self.product_id.uom_id._compute_quantity( - base_qty, self.product_uom - ) - self.env.remove_to_compute(self._fields["input_secondary_qty"], self) + def _inverse_input_secondary_qty(self): + for line in self: + uom = line.input_secondary_uom_id + if not uom or uom.dependency_type == "independent" or not line.product_id: + continue + base_qty = line.input_secondary_qty * (uom.factor or 1.0) + line.product_uom_qty = line.product_id.uom_id._compute_quantity( + base_qty, line.product_uom + ) + line.env.remove_to_compute(line._fields["input_secondary_qty"], line) From 8efea2a6facad733cce5ff8f22b153a52d9dd5cb Mon Sep 17 00:00:00 2001 From: Morita Shinnosuke Date: Thu, 25 Jun 2026 09:41:22 +0000 Subject: [PATCH 3/3] fixup! --- sale_order_secondary_unit_convert/README.rst | 6 ++-- .../__manifest__.py | 2 +- .../models/sale_order_line.py | 33 +++++++++---------- .../readme/DESCRIPTION.md | 2 +- .../static/description/index.html | 6 ++-- .../views/sale_order_views.xml | 6 ++-- 6 files changed, 27 insertions(+), 28 deletions(-) diff --git a/sale_order_secondary_unit_convert/README.rst b/sale_order_secondary_unit_convert/README.rst index bf1a36e6..fd710824 100644 --- a/sale_order_secondary_unit_convert/README.rst +++ b/sale_order_secondary_unit_convert/README.rst @@ -22,9 +22,9 @@ Sale Order Secondary Unit Convert |badge1| |badge2| |badge3| -This module lets users enter sale order line quantities in a convenient -input secondary unit while the line keeps being displayed in another -secondary unit. +This module lets users enter sale order line quantities in an internal +secondary unit while the line keeps being displayed in another secondary +unit. **Table of contents** diff --git a/sale_order_secondary_unit_convert/__manifest__.py b/sale_order_secondary_unit_convert/__manifest__.py index 082784ff..93751599 100644 --- a/sale_order_secondary_unit_convert/__manifest__.py +++ b/sale_order_secondary_unit_convert/__manifest__.py @@ -2,7 +2,7 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). { "name": "Sale Order Secondary Unit Convert", - "summary": "Enter sale order line quantities in a secondary input unit" + "summary": "Enter sale order line quantities in an internal secondary unit" " while displaying another secondary unit.", "version": "18.0.1.0.0", "author": "Quartile", diff --git a/sale_order_secondary_unit_convert/models/sale_order_line.py b/sale_order_secondary_unit_convert/models/sale_order_line.py index 821301a4..ea5274c3 100644 --- a/sale_order_secondary_unit_convert/models/sale_order_line.py +++ b/sale_order_secondary_unit_convert/models/sale_order_line.py @@ -8,22 +8,21 @@ class SaleOrderLine(models.Model): _inherit = "sale.order.line" - input_secondary_uom_id = fields.Many2one( + internal_secondary_uom_id = fields.Many2one( comodel_name="product.secondary.unit", - string="Input Unit", + string="Internal Secondary Unit", ) - input_secondary_qty = fields.Float( - string="Input Qty", + internal_secondary_qty = fields.Float( digits="Product Unit of Measure", - compute="_compute_input_secondary_qty", - inverse="_inverse_input_secondary_qty", + compute="_compute_internal_secondary_qty", + inverse="_inverse_internal_secondary_qty", store=True, readonly=False, ) - def _convert_qty_to_input_secondary_uom(self): + def _convert_qty_to_internal_secondary_uom(self): self.ensure_one() - uom = self.input_secondary_uom_id + uom = self.internal_secondary_uom_id qty = self.product_uom_qty uom_line = self.product_uom uom_product = self.product_id.uom_id @@ -35,23 +34,23 @@ def _convert_qty_to_input_secondary_uom(self): ) @api.depends( - "product_id", "product_uom_qty", "product_uom", "input_secondary_uom_id" + "product_id", "product_uom_qty", "product_uom", "internal_secondary_uom_id" ) - def _compute_input_secondary_qty(self): + def _compute_internal_secondary_qty(self): for line in self: - uom = line.input_secondary_uom_id + uom = line.internal_secondary_uom_id if not uom or uom.dependency_type == "independent": - line.input_secondary_qty = 0.0 + line.internal_secondary_qty = 0.0 continue - line.input_secondary_qty = line._convert_qty_to_input_secondary_uom() + line.internal_secondary_qty = line._convert_qty_to_internal_secondary_uom() - def _inverse_input_secondary_qty(self): + def _inverse_internal_secondary_qty(self): for line in self: - uom = line.input_secondary_uom_id + uom = line.internal_secondary_uom_id if not uom or uom.dependency_type == "independent" or not line.product_id: continue - base_qty = line.input_secondary_qty * (uom.factor or 1.0) + base_qty = line.internal_secondary_qty * (uom.factor or 1.0) line.product_uom_qty = line.product_id.uom_id._compute_quantity( base_qty, line.product_uom ) - line.env.remove_to_compute(line._fields["input_secondary_qty"], line) + line.env.remove_to_compute(line._fields["internal_secondary_qty"], line) diff --git a/sale_order_secondary_unit_convert/readme/DESCRIPTION.md b/sale_order_secondary_unit_convert/readme/DESCRIPTION.md index 410ee6e6..6fc25427 100644 --- a/sale_order_secondary_unit_convert/readme/DESCRIPTION.md +++ b/sale_order_secondary_unit_convert/readme/DESCRIPTION.md @@ -1,2 +1,2 @@ -This module lets users enter sale order line quantities in a convenient input +This module lets users enter sale order line quantities in an internal secondary unit while the line keeps being displayed in another secondary unit. diff --git a/sale_order_secondary_unit_convert/static/description/index.html b/sale_order_secondary_unit_convert/static/description/index.html index b5297141..5e8409dc 100644 --- a/sale_order_secondary_unit_convert/static/description/index.html +++ b/sale_order_secondary_unit_convert/static/description/index.html @@ -369,9 +369,9 @@

Sale Order Secondary Unit Convert

!! source digest: sha256:77e2e5ee13ea91067966507a56acdf7acf6515f5dc6cb3596b8a195ffbf8bab6 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: AGPL-3 qrtl/hls-custom

-

This module lets users enter sale order line quantities in a convenient -input secondary unit while the line keeps being displayed in another -secondary unit.

+

This module lets users enter sale order line quantities in an internal +secondary unit while the line keeps being displayed in another secondary +unit.

Table of contents

    diff --git a/sale_order_secondary_unit_convert/views/sale_order_views.xml b/sale_order_secondary_unit_convert/views/sale_order_views.xml index d4fd0f5e..4b0bde95 100644 --- a/sale_order_secondary_unit_convert/views/sale_order_views.xml +++ b/sale_order_secondary_unit_convert/views/sale_order_views.xml @@ -3,7 +3,7 @@ License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> - sale.order.form.secondary.unit.input + sale.order.form.secondary.unit.internal sale.order @@ -13,12 +13,12 @@ >