From 2e7549f857ba019030e4147bf5c709f5ba0396ad Mon Sep 17 00:00:00 2001 From: GitHub Copilot Date: Thu, 27 Aug 2026 13:26:56 +0200 Subject: [PATCH] [IMP] stock_picking_batch_custom: keep the weighed quantity on the operator view Basket Assembly is where the reverting quantity was noticed, so it depends on stock_move_manual_quantity now and its two lists load `picked`: the web client only saves the fields present in the arch, and without it the onchange that freezes a hand-typed quantity never reaches the server. Collecting a line does the same, minus the demand. It is the operator saying the goods are in the basket, so the quantity has to survive the reservation engine even when it was never retyped -- but only a quantity typed by hand says what the demand should become, and a line collected at zero would otherwise lose its demand and be cancelled on validation. Drops views/stock_move_line_views.xml on the way. It declared a second record under the id stock_picking_batch_views.xml already uses, so it was loaded first and immediately overwritten: dead weight that would have turned into a duplicate-field view had anyone renamed it. Co-Authored-By: Claude Opus 5 --- stock_picking_batch_custom/__manifest__.py | 4 +- .../models/stock_move_line.py | 15 ++++ stock_picking_batch_custom/readme/USAGE.rst | 5 ++ stock_picking_batch_custom/tests/__init__.py | 1 + .../tests/test_collected_picked.py | 73 +++++++++++++++++++ .../views/stock_move_line_views.xml | 36 --------- .../views/stock_picking_batch_views.xml | 6 ++ 7 files changed, 103 insertions(+), 37 deletions(-) create mode 100644 stock_picking_batch_custom/tests/test_collected_picked.py delete mode 100644 stock_picking_batch_custom/views/stock_move_line_views.xml diff --git a/stock_picking_batch_custom/__manifest__.py b/stock_picking_batch_custom/__manifest__.py index 23d05c5..7ad2a39 100644 --- a/stock_picking_batch_custom/__manifest__.py +++ b/stock_picking_batch_custom/__manifest__.py @@ -10,6 +10,9 @@ "website": "https://github.com/Criptomart", "license": "AGPL-3", "depends": [ + # A quantity typed by the operator is the weighed one: it must not be + # re-reserved by the scheduler. + "stock_move_manual_quantity", "stock_picking_batch", # Ensure our related fields to sale/picking (home_delivery, pickup_slot_label) # are available by depending on the Aplicoop website_sale extension. @@ -18,7 +21,6 @@ "data": [ "security/ir.model.access.csv", "views/res_config_settings_views.xml", - "views/stock_move_line_views.xml", "views/stock_picking_batch_views.xml", ], "assets": { diff --git a/stock_picking_batch_custom/models/stock_move_line.py b/stock_picking_batch_custom/models/stock_move_line.py index bc75ed5..03f575e 100644 --- a/stock_picking_batch_custom/models/stock_move_line.py +++ b/stock_picking_batch_custom/models/stock_move_line.py @@ -47,6 +47,21 @@ class StockMoveLine(models.Model): readonly=True, ) + def write(self, vals): + res = super().write(vals) + if vals.get("is_collected"): + # Collecting a line is the operator saying the goods are in the + # basket: its quantity must survive the reservation engine even if + # it was never retyped. The demand is left alone, only a quantity + # typed by hand adjusts it. + lines = self.filtered( + lambda line: not line.picked and line.state not in ("done", "cancel") + ) + if lines: + lines.picked = True + lines._freeze_manual_quantity(align_demand=False) + return res + @api.depends("picking_id") def _compute_consumer_group_id(self): for line in self: diff --git a/stock_picking_batch_custom/readme/USAGE.rst b/stock_picking_batch_custom/readme/USAGE.rst index 89f41e8..5dcdde0 100644 --- a/stock_picking_batch_custom/readme/USAGE.rst +++ b/stock_picking_batch_custom/readme/USAGE.rst @@ -12,3 +12,8 @@ Uso hecho y pendiente) y marca el check de recogido consolidado si corresponde. 4. Ordena o agrupa por categoría en cualquiera de las vistas según convenga. + +5. La cantidad que teclea el operario (el peso real de la balanza) queda fijada: + se marca como *Picked* y ni el planificador ni la validación de otros + albaranes vuelven a modificarla. Marcar **Collected** protege igualmente la + cantidad de la línea. Ver ``stock_move_manual_quantity``. diff --git a/stock_picking_batch_custom/tests/__init__.py b/stock_picking_batch_custom/tests/__init__.py index 161ba0b..a586ed8 100644 --- a/stock_picking_batch_custom/tests/__init__.py +++ b/stock_picking_batch_custom/tests/__init__.py @@ -1 +1,2 @@ from . import test_batch_summary # noqa: F401 +from . import test_collected_picked # noqa: F401 diff --git a/stock_picking_batch_custom/tests/test_collected_picked.py b/stock_picking_batch_custom/tests/test_collected_picked.py new file mode 100644 index 0000000..c90176d --- /dev/null +++ b/stock_picking_batch_custom/tests/test_collected_picked.py @@ -0,0 +1,73 @@ +# Copyright 2026 Criptomart +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import Command +from odoo.tests import TransactionCase +from odoo.tests import tagged + + +@tagged("-at_install", "post_install") +class TestCollectedPicked(TransactionCase): + """Collecting a line protects its quantity from the reservation engine.""" + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.stock_location = cls.env.ref("stock.stock_location_stock") + cls.customer_location = cls.env.ref("stock.stock_location_customers") + cls.picking_type = cls.env.ref("stock.picking_type_out") + cls.product = cls.env["product.product"].create( + { + "name": "Collected Apples", + "is_storable": True, + "uom_id": cls.env.ref("uom.product_uom_kgm").id, + "uom_po_id": cls.env.ref("uom.product_uom_kgm").id, + } + ) + cls.env["stock.quant"]._update_available_quantity( + cls.product, cls.stock_location, 50.0 + ) + + def _create_picking(self, demand): + picking = self.env["stock.picking"].create( + { + "picking_type_id": self.picking_type.id, + "location_id": self.stock_location.id, + "location_dest_id": self.customer_location.id, + "move_ids": [ + Command.create( + { + "name": self.product.name, + "product_id": self.product.id, + "product_uom": self.product.uom_id.id, + "product_uom_qty": demand, + "location_id": self.stock_location.id, + "location_dest_id": self.customer_location.id, + } + ) + ], + } + ) + picking.action_confirm() + picking.action_assign() + return picking + + def test_collecting_a_line_marks_it_picked(self): + picking = self._create_picking(2.0) + line = picking.move_ids.move_line_ids + + line.is_collected = True + + self.assertTrue(line.picked) + self.assertTrue(picking.move_ids.picked) + + def test_collecting_a_line_keeps_its_demand(self): + """Only a hand-typed quantity lowers the demand, collecting does not.""" + picking = self._create_picking(2.0) + move = picking.move_ids + move.move_line_ids.write({"quantity": 1.5, "picked": True}) + + move.move_line_ids.is_collected = True + + self.assertEqual(move.product_uom_qty, 1.5) + self.assertTrue(move.picked) diff --git a/stock_picking_batch_custom/views/stock_move_line_views.xml b/stock_picking_batch_custom/views/stock_move_line_views.xml deleted file mode 100644 index 7a294d3..0000000 --- a/stock_picking_batch_custom/views/stock_move_line_views.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - stock.move.line.list.batch.custom - stock.move.line - - - - hide - - - hide - - - hide - - - hide - - - hide - - - - - - - - - - - - - - - diff --git a/stock_picking_batch_custom/views/stock_picking_batch_views.xml b/stock_picking_batch_custom/views/stock_picking_batch_views.xml index 5379efe..0300795 100644 --- a/stock_picking_batch_custom/views/stock_picking_batch_views.xml +++ b/stock_picking_batch_custom/views/stock_picking_batch_views.xml @@ -33,6 +33,8 @@ + + @@ -91,6 +93,10 @@ + + + + hide