From ebb9ab7450982fb69ded45f8c70ec8be45143f83 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Tue, 9 Apr 2024 13:44:37 +0100 Subject: [PATCH] [IMP] stock_move_packaging_qty: report done packaging qtys The title sounds like a simple task, but it requires many changes: - Store done pkg qtys in `stock.move.line` records. - The qtys are not computed. Instead, the user must write them by hand. This happens because many times the packaging just indicates an approximate content, but is not always exact until reaching this point of the process, where both the done qtys (both UoM and packagings) are measured and get real values. - Added compute and inverse on `stock.move` for simple scenarios where one move = one move line. - Add all this on all reports and views. This change also opens the door to be able to get a real inventory measured in product packagings. That's not implemented in this MVP, but the idea is recorded. @moduon MT-5531 --- stock_move_packaging_qty/README.rst | 13 +++- stock_move_packaging_qty/__manifest__.py | 3 +- stock_move_packaging_qty/models/__init__.py | 1 + stock_move_packaging_qty/models/stock_move.py | 75 +++++++++++++++++-- .../models/stock_move_line.py | 49 ++++++++++++ .../readme/DESCRIPTION.rst | 2 +- stock_move_packaging_qty/readme/ROADMAP.rst | 5 ++ .../static/description/index.html | 35 ++++++--- .../tests/test_stock_move_packaging_qty.py | 26 ++++++- .../views/report_stock_picking.xml | 67 +++++++++++++++++ .../views/stock_move_line_view.xml | 64 ++++++++++++++++ .../views/stock_move_tree_view.xml | 24 ------ .../views/stock_move_view.xml | 74 ++++++++++++++++++ .../views/stock_picking_form_view.xml | 14 +++- 14 files changed, 403 insertions(+), 49 deletions(-) create mode 100644 stock_move_packaging_qty/models/stock_move_line.py create mode 100644 stock_move_packaging_qty/readme/ROADMAP.rst create mode 100644 stock_move_packaging_qty/views/stock_move_line_view.xml delete mode 100644 stock_move_packaging_qty/views/stock_move_tree_view.xml create mode 100644 stock_move_packaging_qty/views/stock_move_view.xml diff --git a/stock_move_packaging_qty/README.rst b/stock_move_packaging_qty/README.rst index d3de8f875b22..067dc3d2b484 100644 --- a/stock_move_packaging_qty/README.rst +++ b/stock_move_packaging_qty/README.rst @@ -7,7 +7,7 @@ Stock Packaging Qty !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:ac3969cedf86da72ca20a93812053d6d9054a9f5d8e0c896eab57fe2b835eefc + !! source digest: sha256:5b4005788a852d7f71c44042e6c8839be5963fd5d6ac95822b93c48c660a2730 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png @@ -28,13 +28,22 @@ Stock Packaging Qty |badge1| |badge2| |badge3| |badge4| |badge5| -Add packaging fields in the stock moves and their reports. +Add packaging fields in the stock moves, their lines and their reports. **Table of contents** .. contents:: :local: +Known issues / Roadmap +====================== + +* Maybe we should track also reserved packaging quantities? + +* Since we store done product packaging quantities in the stock move lines, we + should be able to use this information in quants to provide real + packaging-based stock data. + Bug Tracker =========== diff --git a/stock_move_packaging_qty/__manifest__.py b/stock_move_packaging_qty/__manifest__.py index 51125885d9d8..abd1072f359f 100644 --- a/stock_move_packaging_qty/__manifest__.py +++ b/stock_move_packaging_qty/__manifest__.py @@ -10,7 +10,8 @@ "depends": ["stock"], "data": [ "views/report_stock_picking.xml", - "views/stock_move_tree_view.xml", + "views/stock_move_line_view.xml", + "views/stock_move_view.xml", "views/stock_picking_form_view.xml", ], "license": "LGPL-3", diff --git a/stock_move_packaging_qty/models/__init__.py b/stock_move_packaging_qty/models/__init__.py index 6bda2d2428e0..f800274cd973 100644 --- a/stock_move_packaging_qty/models/__init__.py +++ b/stock_move_packaging_qty/models/__init__.py @@ -1 +1,2 @@ from . import stock_move +from . import stock_move_line diff --git a/stock_move_packaging_qty/models/stock_move.py b/stock_move_packaging_qty/models/stock_move.py index f42c70eb24b9..b6746f9713ec 100644 --- a/stock_move_packaging_qty/models/stock_move.py +++ b/stock_move_packaging_qty/models/stock_move.py @@ -1,17 +1,24 @@ # Copyright 2020 Camptocamp SA # Copyright 2021 ForgeFlow, S.L. -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) -from odoo import api, fields, models +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). +from odoo import _, api, exceptions, fields, models +from odoo.tools import float_compare -class StockPicking(models.Model): +class StockMove(models.Model): _inherit = "stock.move" product_packaging_qty = fields.Float( - string="Pkg. Qty.", + string="Pkgs. Demand", compute="_compute_product_packaging_qty", inverse="_inverse_product_packaging_qty", - help="Amount of packages demanded.", + help="Amount of product packagings demanded.", + ) + product_packaging_qty_done = fields.Float( + string="Pkgs. Done", + compute="_compute_product_packaging_qty_done", + inverse="_inverse_product_packaging_qty_done", + help="Amount of product packagings done.", ) @api.depends( @@ -31,6 +38,18 @@ def _compute_product_packaging_qty(self): move.product_uom_qty / move._get_single_package_uom_qty() ) + @api.depends( + "move_line_ids.product_packaging_qty_done", + "move_line_nosuggest_ids.product_packaging_qty_done", + ) + def _compute_product_packaging_qty_done(self): + """Get the sum of done packaging qtys from move lines.""" + for move in self: + lines = move._get_move_lines() + move.product_packaging_qty_done = sum( + lines.mapped("product_packaging_qty_done") + ) + @api.onchange("product_packaging_qty") def _inverse_product_packaging_qty(self): """Store the quantity in the product's UoM. @@ -43,6 +62,23 @@ def _inverse_product_packaging_qty(self): uom_factor = move._get_single_package_uom_qty() move.product_uom_qty = move.product_packaging_qty * uom_factor + def _inverse_product_packaging_qty_done(self): + """Store the done packaging dqty in the move line if there's just one.""" + for move in self: + lines = move._get_move_lines() + # Setting 0 done pkgs with no lines? Nothing to do + if not lines and not move.product_packaging_qty_done: + continue + if len(lines) != 1: + raise exceptions.UserError( + _( + "There are %d move lines involved. " + "Please set their product packaging done qty directly.", + len(lines), + ) + ) + lines.product_packaging_qty_done = move.product_packaging_qty_done + @api.onchange("product_packaging_id") def _onchange_product_packaging(self): """Add a default qty if the packaging has an invalid value.""" @@ -62,3 +98,32 @@ def _get_single_package_uom_qty(self): return self.product_packaging_id.product_uom_id._compute_quantity( self.product_packaging_id.qty, self.product_uom ) + + def _set_quantities_to_reservation(self): + """Add packaging qtys when clicking on "Set Quantities".""" + result = super()._set_quantities_to_reservation() + digits = self.env["stock.move.line"].fields_get(["qty_done"], ["digits"])[ + "qty_done" + ]["digits"][1] + for line in self.move_line_ids: + if float_compare(line.qty_done, line.reserved_uom_qty, digits): + continue + if not line.product_packaging_id: + line.product_packaging_qty_done = 0 + continue + line.product_packaging_qty_done = ( + line.product_packaging_id._check_qty( + line.qty_done, line.product_uom_id, "DOWN" + ) + / line.product_packaging_id.qty + ) + return result + + def _clear_quantities_to_zero(self): + """Clear packaging qtys when clicking on "Clear Quantities".""" + result = super()._clear_quantities_to_zero() + for line in self.move_line_ids: + if line.qty_done: + continue + line.product_packaging_qty_done = 0 + return result diff --git a/stock_move_packaging_qty/models/stock_move_line.py b/stock_move_packaging_qty/models/stock_move_line.py new file mode 100644 index 000000000000..b953ead6ae2e --- /dev/null +++ b/stock_move_packaging_qty/models/stock_move_line.py @@ -0,0 +1,49 @@ +# Copyright 2024 Moduon Team S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0) +from odoo import fields, models + + +class StockMoveLine(models.Model): + _inherit = "stock.move.line" + + product_packaging_id = fields.Many2one( + related="move_id.product_packaging_id", readonly=True + ) + product_packaging_qty_done = fields.Float( + string="Done Pkg. Qty.", + help="Product packaging quantity done.", + ) + + def _get_aggregated_properties(self, move_line=False, move=False): + """Aggregate by product packaging too.""" + result = super()._get_aggregated_properties(move_line, move) + pkg = result["move"].product_packaging_id + result["product_packaging"] = pkg + result["line_key"] += f"_{pkg.id}" + return result + + def _get_aggregated_product_quantities(self, **kwargs): + """Aggregate by product packaging too.""" + result = super()._get_aggregated_product_quantities(**kwargs) + # Know all involved move lines, following upstream criteria + all_lines = self.browse() + processed_moves = all_lines.move_id + if kwargs.get("except_package"): + all_lines |= self - self.filtered("result_package_id") + if not kwargs.get("strict"): + moves = (self.picking_id | self.picking_id.backorder_ids).move_ids + all_lines |= moves.move_line_ids | moves.move_line_nosuggest_ids + # Aggregate product packaging quantities + for move_line in all_lines: + props = self._get_aggregated_properties(move_line) + try: + agg = result[props["line_key"]] + except KeyError: + continue # Missing aggregation; nothing to do + agg.setdefault("product_packaging_qty", 0.0) + agg.setdefault("product_packaging_qty_done", 0.0) + agg["product_packaging_qty_done"] += move_line.product_packaging_qty_done + if move_line.move_id not in processed_moves: + agg["product_packaging_qty"] += move_line.move_id.product_packaging_qty + processed_moves |= move_line.move_id + return result diff --git a/stock_move_packaging_qty/readme/DESCRIPTION.rst b/stock_move_packaging_qty/readme/DESCRIPTION.rst index 0b10411187a6..bdbb7d88fa24 100644 --- a/stock_move_packaging_qty/readme/DESCRIPTION.rst +++ b/stock_move_packaging_qty/readme/DESCRIPTION.rst @@ -1 +1 @@ -Add packaging fields in the stock moves and their reports. +Add packaging fields in the stock moves, their lines and their reports. diff --git a/stock_move_packaging_qty/readme/ROADMAP.rst b/stock_move_packaging_qty/readme/ROADMAP.rst new file mode 100644 index 000000000000..a3737366bb94 --- /dev/null +++ b/stock_move_packaging_qty/readme/ROADMAP.rst @@ -0,0 +1,5 @@ +* Maybe we should track also reserved packaging quantities? + +* Since we store done product packaging quantities in the stock move lines, we + should be able to use this information in quants to provide real + packaging-based stock data. diff --git a/stock_move_packaging_qty/static/description/index.html b/stock_move_packaging_qty/static/description/index.html index ad679f59a018..831109373039 100644 --- a/stock_move_packaging_qty/static/description/index.html +++ b/stock_move_packaging_qty/static/description/index.html @@ -1,3 +1,4 @@ + @@ -366,24 +367,34 @@

Stock Packaging Qty

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:ac3969cedf86da72ca20a93812053d6d9054a9f5d8e0c896eab57fe2b835eefc +!! source digest: sha256:5b4005788a852d7f71c44042e6c8839be5963fd5d6ac95822b93c48c660a2730 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: LGPL-3 OCA/stock-logistics-warehouse Translate me on Weblate Try me on Runboat

-

Add packaging fields in the stock moves and their reports.

+

Add packaging fields in the stock moves, their lines and their reports.

Table of contents

+
+

Known issues / Roadmap

+ +
-

Bug Tracker

+

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 @@ -391,15 +402,15 @@

Bug Tracker

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

-

Credits

+

Credits

-

Authors

+

Authors

  • ForgeFlow
-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association

OCA, or the Odoo Community Association, is a nonprofit organization whose diff --git a/stock_move_packaging_qty/tests/test_stock_move_packaging_qty.py b/stock_move_packaging_qty/tests/test_stock_move_packaging_qty.py index ca6ccff1e913..cc62c83c2df7 100644 --- a/stock_move_packaging_qty/tests/test_stock_move_packaging_qty.py +++ b/stock_move_packaging_qty/tests/test_stock_move_packaging_qty.py @@ -12,7 +12,14 @@ def setUpClass(cls): cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) cls.env.user.groups_id |= cls.env.ref("product.group_stock_packaging") cls.partner = cls.env.ref("base.res_partner_12") - cls.product = cls.env.ref("product.product_product_9") + cls.product = cls.env["product.product"].create( + { + "name": "Test product", + "type": "consu", + "uom_id": cls.env.ref("uom.product_uom_unit").id, + "uom_po_id": cls.env.ref("uom.product_uom_unit").id, + } + ) cls.packaging = cls.env["product.packaging"].create( {"name": "Test packaging", "product_id": cls.product.id, "qty": 5.0} ) @@ -59,3 +66,20 @@ def test_product_uom_qty_change(self): move_f.product_packaging_id = self.packaging self.assertEqual(move_f.product_uom_qty, 5) self.assertEqual(move_f.product_packaging_qty, 1) + picking = picking_f.save() + self.assertEqual(picking.state, "draft") + picking.action_assign() + picking.action_set_quantities_to_reservation() + self.assertRecordValues( + picking.move_ids_without_package, + [ + { + "product_id": self.product.id, + "product_packaging_id": self.packaging.id, + "product_packaging_qty_done": 1, + "product_packaging_qty": 1, + "product_uom_qty": 5, + } + ], + ) + picking.button_validate() diff --git a/stock_move_packaging_qty/views/report_stock_picking.xml b/stock_move_packaging_qty/views/report_stock_picking.xml index f9183a7e2fb7..149299516747 100644 --- a/stock_move_packaging_qty/views/report_stock_picking.xml +++ b/stock_move_packaging_qty/views/report_stock_picking.xml @@ -40,5 +40,72 @@

+ + +
+ : + +
+
+ + + + + diff --git a/stock_move_packaging_qty/views/stock_move_line_view.xml b/stock_move_packaging_qty/views/stock_move_line_view.xml new file mode 100644 index 000000000000..feb698d5b71b --- /dev/null +++ b/stock_move_packaging_qty/views/stock_move_line_view.xml @@ -0,0 +1,64 @@ + + + + + Add product packaging qty done info + stock.move.line + + + + + + + + + + + Add product packaging qty done info + stock.move.line + + + + + + + + + Add product packaging qty done info + stock.move.line + + + + + + + + + diff --git a/stock_move_packaging_qty/views/stock_move_tree_view.xml b/stock_move_packaging_qty/views/stock_move_tree_view.xml deleted file mode 100644 index fdee6314e40f..000000000000 --- a/stock_move_packaging_qty/views/stock_move_tree_view.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - view.move.extra.tree - stock.move - - - - - - - - - diff --git a/stock_move_packaging_qty/views/stock_move_view.xml b/stock_move_packaging_qty/views/stock_move_view.xml new file mode 100644 index 000000000000..9f285c3aa640 --- /dev/null +++ b/stock_move_packaging_qty/views/stock_move_view.xml @@ -0,0 +1,74 @@ + + + + + view.move.extra.tree + stock.move + + + + + + + + + + + Add product packaging qty info + stock.move + + + + + + + + + diff --git a/stock_move_packaging_qty/views/stock_picking_form_view.xml b/stock_move_packaging_qty/views/stock_picking_form_view.xml index d6fd0b4c31ee..c979c8747048 100644 --- a/stock_move_packaging_qty/views/stock_picking_form_view.xml +++ b/stock_move_packaging_qty/views/stock_picking_form_view.xml @@ -13,9 +13,6 @@ name="product_packaging_qty" groups="product.group_stock_packaging" attrs="{ - 'column_invisible': [ - ('parent.immediate_transfer', '=', True), - ], 'readonly': [ '|', ('product_packaging_id', '=', False), @@ -24,6 +21,17 @@ }" /> + + + +