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