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 d3de8f875..067dc3d2b 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 51125885d..abd1072f3 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 6bda2d242..f800274cd 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 f42c70eb2..b6746f971 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 @@ class StockPicking(models.Model): 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 @@ class StockPicking(models.Model): 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 @@ class StockPicking(models.Model): 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 000000000..b953ead6a --- /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 0b1041118..bdbb7d88f 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 000000000..a3737366b --- /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 ad679f59a..831109373 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 @@ ul.auto-toc { !! 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 @@ If you spotted it first, help us to smash it by providing a detailed and welcome

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

-

Credits

+

Credits

-

Authors

+

Authors

  • ForgeFlow
-

Contributors

+

Contributors

-

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 ca6ccff1e..cc62c83c2 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 @@ class TestStockMovePackagingQty(TransactionCase): 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 @@ class TestStockMovePackagingQty(TransactionCase): 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 f9183a7e2..149299516 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 000000000..feb698d5b --- /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 fdee6314e..000000000 --- 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 000000000..9f285c3aa --- /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 d6fd0b4c3..c979c8747 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 @@ }" /> + + + +