[FIX] stock_picking_batch_custom: validar collected solo en operaciones detalladas
This commit is contained in:
parent
2237cba034
commit
05a8908007
4 changed files with 63 additions and 27 deletions
|
|
@ -12,7 +12,7 @@ class StockBackorderConfirmation(models.TransientModel):
|
||||||
self.env.context.get("button_validate_picking_ids", [])
|
self.env.context.get("button_validate_picking_ids", [])
|
||||||
)
|
)
|
||||||
for batch in pickings.batch_id:
|
for batch in pickings.batch_id:
|
||||||
batch._check_all_products_collected()
|
batch._check_all_products_collected(pickings)
|
||||||
|
|
||||||
def process(self):
|
def process(self):
|
||||||
self._check_batch_summary_lines()
|
self._check_batch_summary_lines()
|
||||||
|
|
|
||||||
|
|
@ -43,15 +43,13 @@ class StockPicking(models.Model):
|
||||||
batch_pickings = self.filtered(
|
batch_pickings = self.filtered(
|
||||||
lambda picking, batch=batch: picking.batch_id == batch
|
lambda picking, batch=batch: picking.batch_id == batch
|
||||||
)
|
)
|
||||||
processed_product_ids = (
|
processed_move_lines = batch_pickings.move_line_ids.filtered(
|
||||||
batch_pickings.move_line_ids.filtered(
|
lambda line: (
|
||||||
lambda line: line.move_id.state not in ("cancel", "done")
|
line.move_id.state not in ("cancel", "done")
|
||||||
and line.product_id
|
and line.product_id
|
||||||
and line.quantity
|
and line.move_id.quantity > 0
|
||||||
)
|
)
|
||||||
.mapped("product_id")
|
|
||||||
.ids
|
|
||||||
)
|
)
|
||||||
if not processed_product_ids:
|
if not processed_move_lines:
|
||||||
continue
|
continue
|
||||||
batch._check_all_products_collected(processed_product_ids)
|
batch._check_all_products_collected(batch_pickings)
|
||||||
|
|
|
||||||
|
|
@ -140,29 +140,39 @@ class StockPickingBatch(models.Model):
|
||||||
else:
|
else:
|
||||||
batch.summary_line_ids = [fields.Command.clear()]
|
batch.summary_line_ids = [fields.Command.clear()]
|
||||||
|
|
||||||
def _check_all_products_collected(self, product_ids=None):
|
def _check_all_products_collected(self, pickings=None):
|
||||||
"""Ensure collected is checked for processed products only.
|
"""Validate only Detailed Operations collected flags.
|
||||||
|
|
||||||
The product summary remains informative until Odoo knows which products
|
Product Summary checkboxes are informative and must not block the flow.
|
||||||
are actually being validated after the backorder decision.
|
The blocking validation applies to stock.move.line.is_collected in the
|
||||||
|
detailed operations tab for lines with processed quantity.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for batch in self:
|
for batch in self:
|
||||||
not_collected_lines = batch.summary_line_ids.filtered(
|
batch_pickings = (
|
||||||
|
pickings.filtered(lambda p, batch=batch: p.batch_id == batch)
|
||||||
|
if pickings
|
||||||
|
else batch.picking_ids
|
||||||
|
)
|
||||||
|
if not batch_pickings:
|
||||||
|
continue
|
||||||
|
|
||||||
|
not_collected_move_lines = batch_pickings.move_line_ids.filtered(
|
||||||
lambda line: (
|
lambda line: (
|
||||||
line.qty_done > 0
|
line.move_id.state not in ("cancel", "done")
|
||||||
|
and line.product_id
|
||||||
|
and line.move_id.quantity > 0
|
||||||
and not line.is_collected
|
and not line.is_collected
|
||||||
and (not product_ids or line.product_id.id in product_ids)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if not not_collected_lines:
|
if not not_collected_move_lines:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
product_names = ", ".join(
|
product_names = ", ".join(
|
||||||
not_collected_lines.mapped("product_id.display_name")
|
sorted(set(not_collected_move_lines.mapped("product_id.display_name")))
|
||||||
)
|
)
|
||||||
message = batch.env._(
|
message = batch.env._(
|
||||||
"You must mark all product lines as collected before validating the batch."
|
"You must mark detailed operation lines as collected before validating the batch."
|
||||||
)
|
)
|
||||||
if product_names:
|
if product_names:
|
||||||
message += "\n" + batch.env._(
|
message += "\n" + batch.env._(
|
||||||
|
|
|
||||||
|
|
@ -344,19 +344,38 @@ class TestBatchSummary(TransactionCase):
|
||||||
)
|
)
|
||||||
self.assertAlmostEqual(line_product2.qty_demanded, 10.0)
|
self.assertAlmostEqual(line_product2.qty_demanded, 10.0)
|
||||||
|
|
||||||
def test_done_requires_all_summary_lines_collected_without_backorder(self):
|
def test_done_requires_detailed_lines_collected_without_backorder(self):
|
||||||
"""Full validation still blocks if processed products are unchecked."""
|
"""Full validation blocks when detailed operation lines are unchecked."""
|
||||||
|
|
||||||
batch = self._create_batch_with_pickings()
|
batch = self._create_batch_with_pickings()
|
||||||
batch.action_confirm()
|
batch.action_confirm()
|
||||||
batch._compute_summary_line_ids()
|
batch._compute_summary_line_ids()
|
||||||
|
|
||||||
self.assertTrue(batch.summary_line_ids)
|
detail_lines = batch.move_line_ids.filtered(lambda line: line.quantity > 0)
|
||||||
self.assertFalse(batch.summary_line_ids.mapped("is_collected")[0])
|
self.assertTrue(detail_lines)
|
||||||
|
self.assertFalse(any(detail_lines.mapped("is_collected")))
|
||||||
|
|
||||||
with self.assertRaises(UserError):
|
with self.assertRaises(UserError):
|
||||||
batch.action_done()
|
batch.action_done()
|
||||||
|
|
||||||
|
def test_done_ignores_product_summary_checkbox(self):
|
||||||
|
"""Product Summary checkbox is informative and must not block validation."""
|
||||||
|
|
||||||
|
batch = self._create_batch_with_pickings()
|
||||||
|
batch.action_confirm()
|
||||||
|
batch._compute_summary_line_ids()
|
||||||
|
|
||||||
|
# Keep Product Summary unchecked on purpose
|
||||||
|
self.assertFalse(any(batch.summary_line_ids.mapped("is_collected")))
|
||||||
|
|
||||||
|
# Detailed lines drive validation
|
||||||
|
batch.move_line_ids.filtered(lambda line: line.quantity > 0).write(
|
||||||
|
{"is_collected": True}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Should not raise
|
||||||
|
batch.action_done()
|
||||||
|
|
||||||
def test_partial_validation_waits_for_backorder_wizard_before_blocking(self):
|
def test_partial_validation_waits_for_backorder_wizard_before_blocking(self):
|
||||||
"""Partial batch validation must open the backorder wizard first."""
|
"""Partial batch validation must open the backorder wizard first."""
|
||||||
|
|
||||||
|
|
@ -373,13 +392,20 @@ class TestBatchSummary(TransactionCase):
|
||||||
self.assertIsInstance(action, dict)
|
self.assertIsInstance(action, dict)
|
||||||
self.assertEqual(action.get("res_model"), "stock.backorder.confirmation")
|
self.assertEqual(action.get("res_model"), "stock.backorder.confirmation")
|
||||||
|
|
||||||
def test_partial_validation_checks_only_processed_products(self):
|
def test_partial_validation_checks_only_processed_detailed_lines(self):
|
||||||
"""Unchecked lines with no processed quantity must remain informative."""
|
"""Only processed detailed lines must be required for collected."""
|
||||||
|
|
||||||
batch = self._create_partial_batch(extra_move=True)
|
batch = self._create_partial_batch(extra_move=True)
|
||||||
|
|
||||||
|
# Ensure product_2 remains non-processed in this scenario
|
||||||
|
move_product_2 = batch.move_ids.filtered(
|
||||||
|
lambda move: move.product_id == self.product_2
|
||||||
|
)
|
||||||
|
move_product_2.write({"quantity": 0.0})
|
||||||
|
move_product_2.move_line_ids.write({"quantity": 0.0})
|
||||||
|
|
||||||
with self.assertRaisesRegex(UserError, self.product.display_name) as err:
|
with self.assertRaisesRegex(UserError, self.product.display_name) as err:
|
||||||
batch._check_all_products_collected([self.product.id])
|
batch._check_all_products_collected(batch.picking_ids)
|
||||||
|
|
||||||
self.assertNotIn(self.product_2.display_name, str(err.exception))
|
self.assertNotIn(self.product_2.display_name, str(err.exception))
|
||||||
|
|
||||||
|
|
@ -389,7 +415,9 @@ class TestBatchSummary(TransactionCase):
|
||||||
batch = self._create_batch_with_pickings()
|
batch = self._create_batch_with_pickings()
|
||||||
batch.action_confirm()
|
batch.action_confirm()
|
||||||
batch._compute_summary_line_ids()
|
batch._compute_summary_line_ids()
|
||||||
batch.summary_line_ids.write({"is_collected": True})
|
batch.move_line_ids.filtered(lambda line: line.quantity > 0).write(
|
||||||
|
{"is_collected": True}
|
||||||
|
)
|
||||||
|
|
||||||
# Should not raise
|
# Should not raise
|
||||||
batch._check_all_products_collected()
|
batch._check_all_products_collected()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue