diff --git a/stock_picking_batch_custom/models/stock_backorder_confirmation.py b/stock_picking_batch_custom/models/stock_backorder_confirmation.py index ab246c5..2236e4f 100644 --- a/stock_picking_batch_custom/models/stock_backorder_confirmation.py +++ b/stock_picking_batch_custom/models/stock_backorder_confirmation.py @@ -12,7 +12,7 @@ class StockBackorderConfirmation(models.TransientModel): self.env.context.get("button_validate_picking_ids", []) ) for batch in pickings.batch_id: - batch._check_all_products_collected() + batch._check_all_products_collected(pickings) def process(self): self._check_batch_summary_lines() diff --git a/stock_picking_batch_custom/models/stock_picking.py b/stock_picking_batch_custom/models/stock_picking.py index f4dc0a2..2e94ffa 100644 --- a/stock_picking_batch_custom/models/stock_picking.py +++ b/stock_picking_batch_custom/models/stock_picking.py @@ -43,15 +43,13 @@ class StockPicking(models.Model): batch_pickings = self.filtered( lambda picking, batch=batch: picking.batch_id == batch ) - processed_product_ids = ( - batch_pickings.move_line_ids.filtered( - lambda line: line.move_id.state not in ("cancel", "done") + processed_move_lines = batch_pickings.move_line_ids.filtered( + lambda line: ( + line.move_id.state not in ("cancel", "done") 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 - batch._check_all_products_collected(processed_product_ids) + batch._check_all_products_collected(batch_pickings) diff --git a/stock_picking_batch_custom/models/stock_picking_batch.py b/stock_picking_batch_custom/models/stock_picking_batch.py index 918c92b..40f355f 100644 --- a/stock_picking_batch_custom/models/stock_picking_batch.py +++ b/stock_picking_batch_custom/models/stock_picking_batch.py @@ -140,29 +140,39 @@ class StockPickingBatch(models.Model): else: batch.summary_line_ids = [fields.Command.clear()] - def _check_all_products_collected(self, product_ids=None): - """Ensure collected is checked for processed products only. + def _check_all_products_collected(self, pickings=None): + """Validate only Detailed Operations collected flags. - The product summary remains informative until Odoo knows which products - are actually being validated after the backorder decision. + Product Summary checkboxes are informative and must not block the flow. + The blocking validation applies to stock.move.line.is_collected in the + detailed operations tab for lines with processed quantity. """ 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: ( - 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 product_ids or line.product_id.id in product_ids) ) ) - if not not_collected_lines: + if not not_collected_move_lines: continue 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._( - "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: message += "\n" + batch.env._( diff --git a/stock_picking_batch_custom/tests/test_batch_summary.py b/stock_picking_batch_custom/tests/test_batch_summary.py index 2d96b48..d08e40d 100644 --- a/stock_picking_batch_custom/tests/test_batch_summary.py +++ b/stock_picking_batch_custom/tests/test_batch_summary.py @@ -344,19 +344,38 @@ class TestBatchSummary(TransactionCase): ) self.assertAlmostEqual(line_product2.qty_demanded, 10.0) - def test_done_requires_all_summary_lines_collected_without_backorder(self): - """Full validation still blocks if processed products are unchecked.""" + def test_done_requires_detailed_lines_collected_without_backorder(self): + """Full validation blocks when detailed operation lines are unchecked.""" batch = self._create_batch_with_pickings() batch.action_confirm() batch._compute_summary_line_ids() - self.assertTrue(batch.summary_line_ids) - self.assertFalse(batch.summary_line_ids.mapped("is_collected")[0]) + detail_lines = batch.move_line_ids.filtered(lambda line: line.quantity > 0) + self.assertTrue(detail_lines) + self.assertFalse(any(detail_lines.mapped("is_collected"))) with self.assertRaises(UserError): 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): """Partial batch validation must open the backorder wizard first.""" @@ -373,13 +392,20 @@ class TestBatchSummary(TransactionCase): self.assertIsInstance(action, dict) self.assertEqual(action.get("res_model"), "stock.backorder.confirmation") - def test_partial_validation_checks_only_processed_products(self): - """Unchecked lines with no processed quantity must remain informative.""" + def test_partial_validation_checks_only_processed_detailed_lines(self): + """Only processed detailed lines must be required for collected.""" 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: - 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)) @@ -389,7 +415,9 @@ class TestBatchSummary(TransactionCase): batch = self._create_batch_with_pickings() batch.action_confirm() 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 batch._check_all_products_collected()