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