[IMP] stock_picking_batch_collect: collected checks on transfers, ordered quantity, delivery slips report
- The collected checks also apply to transfers outside a batch, opt-in per
operation type ("Require Collected Lines"), with the batch restrictions of
the settings. A per-product toggle in the transfer's Operations tab collects
all its lines.
- Read-only ordered quantity from the sale line on every batch and transfer
view, since the weighed demand overwrites it. Adds a dependency on
sale_stock; a migration fills it in the summary of open batches.
- "Delivery Slips" report on the batch: all its delivery slips in one PDF.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
76dc355c6c
commit
9c81cb986d
24 changed files with 630 additions and 39 deletions
|
|
@ -1,2 +1,3 @@
|
|||
from . import test_batch_summary # noqa: F401
|
||||
from . import test_collected_picked # noqa: F401
|
||||
from . import test_picking_collected # noqa: F401
|
||||
|
|
|
|||
137
stock_picking_batch_collect/tests/test_picking_collected.py
Normal file
137
stock_picking_batch_collect/tests/test_picking_collected.py
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
# Copyright 2026 Criptomart
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import Command
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests import TransactionCase
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
||||
@tagged("-at_install", "post_install")
|
||||
class TestPickingCollected(TransactionCase):
|
||||
"""Collected checks and ordered quantities on transfers outside a batch."""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.stock_location = cls.env.ref("stock.stock_location_stock")
|
||||
cls.picking_type_out = cls.env.ref("stock.picking_type_out")
|
||||
cls.picking_type_out.collected_check_required = True
|
||||
cls.env.company.write(
|
||||
{
|
||||
"batch_detailed_restriction_enabled": True,
|
||||
"batch_detailed_restriction_scope": "processed",
|
||||
"batch_summary_restriction_enabled": False,
|
||||
}
|
||||
)
|
||||
kg = cls.env.ref("uom.product_uom_kgm")
|
||||
cls.product = cls.env["product.product"].create(
|
||||
{
|
||||
"name": "Collected Pears",
|
||||
"is_storable": True,
|
||||
"uom_id": kg.id,
|
||||
"uom_po_id": kg.id,
|
||||
}
|
||||
)
|
||||
cls.env["stock.quant"]._update_available_quantity(
|
||||
cls.product, cls.stock_location, 50.0
|
||||
)
|
||||
cls.partner = cls.env["res.partner"].create({"name": "Pear Lover"})
|
||||
|
||||
def _create_sale_picking(self, qty=2.0):
|
||||
order = self.env["sale.order"].create(
|
||||
{
|
||||
"partner_id": self.partner.id,
|
||||
"order_line": [
|
||||
Command.create(
|
||||
{"product_id": self.product.id, "product_uom_qty": qty}
|
||||
)
|
||||
],
|
||||
}
|
||||
)
|
||||
order.action_confirm()
|
||||
picking = order.picking_ids
|
||||
picking.action_assign()
|
||||
return picking
|
||||
|
||||
def test_loose_picking_blocks_until_collected(self):
|
||||
picking = self._create_sale_picking()
|
||||
|
||||
with self.assertRaisesRegex(UserError, self.product.display_name):
|
||||
picking.button_validate()
|
||||
|
||||
picking.move_ids.is_collected = True
|
||||
picking.button_validate()
|
||||
|
||||
self.assertEqual(picking.state, "done")
|
||||
|
||||
def test_picking_type_without_check_is_not_blocked(self):
|
||||
self.picking_type_out.collected_check_required = False
|
||||
picking = self._create_sale_picking()
|
||||
|
||||
picking.button_validate()
|
||||
|
||||
self.assertEqual(picking.state, "done")
|
||||
|
||||
def test_batch_picking_follows_the_batch_restriction(self):
|
||||
"""The batch summary flag is not required by the loose transfer check."""
|
||||
self.env.company.batch_summary_restriction_enabled = True
|
||||
picking = self._create_sale_picking()
|
||||
batch = self.env["stock.picking.batch"].create(
|
||||
{"picking_ids": [Command.set(picking.ids)]}
|
||||
)
|
||||
batch.action_confirm()
|
||||
picking.move_ids.is_collected = True
|
||||
|
||||
with self.assertRaisesRegex(UserError, "product summary"):
|
||||
batch.action_done()
|
||||
|
||||
def test_collecting_the_move_collects_its_lines(self):
|
||||
picking = self._create_sale_picking()
|
||||
move = picking.move_ids
|
||||
self.assertFalse(move.is_collected)
|
||||
|
||||
move.is_collected = True
|
||||
|
||||
self.assertTrue(all(move.move_line_ids.mapped("is_collected")))
|
||||
self.assertTrue(move.picked)
|
||||
|
||||
move.move_line_ids.is_collected = False
|
||||
|
||||
self.assertFalse(move.is_collected)
|
||||
|
||||
def test_ordered_quantity_survives_weighing(self):
|
||||
picking = self._create_sale_picking(2.0)
|
||||
move = picking.move_ids
|
||||
|
||||
move.move_line_ids.write({"quantity": 1.5, "picked": True})
|
||||
|
||||
self.assertEqual(move.product_uom_qty, 1.5)
|
||||
self.assertEqual(move.sale_ordered_qty, 2.0)
|
||||
self.assertEqual(move.move_line_ids.sale_ordered_qty, 2.0)
|
||||
|
||||
def test_batch_summary_ordered_quantity(self):
|
||||
pickings = self._create_sale_picking(2.0) | self._create_sale_picking(3.0)
|
||||
batch = self.env["stock.picking.batch"].create(
|
||||
{"picking_ids": [Command.set(pickings.ids)]}
|
||||
)
|
||||
pickings[0].move_ids.move_line_ids.write({"quantity": 1.5, "picked": True})
|
||||
|
||||
batch._compute_summary_line_ids()
|
||||
|
||||
line = batch.summary_line_ids
|
||||
self.assertEqual(line.qty_ordered, 5.0)
|
||||
self.assertEqual(line.qty_demanded, 4.5)
|
||||
|
||||
def test_report_prints_every_delivery_slip(self):
|
||||
pickings = self._create_sale_picking(2.0) | self._create_sale_picking(3.0)
|
||||
batch = self.env["stock.picking.batch"].create(
|
||||
{"picking_ids": [Command.set(pickings.ids)]}
|
||||
)
|
||||
|
||||
html, _report_format = self.env["ir.actions.report"]._render_qweb_html(
|
||||
"stock_picking_batch_collect.report_batch_deliveryslip", batch.ids
|
||||
)
|
||||
|
||||
for picking in pickings:
|
||||
self.assertIn(picking.name, html.decode())
|
||||
Loading…
Add table
Add a link
Reference in a new issue