From cc181711dd84df0584ea6d49d174265dade5ec9b Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Wed, 19 Jul 2017 17:18:28 +0200 Subject: [PATCH] [FIX] stock: scrap: allow scrapping consumable A previous revision[0] forbid to scrap consumables. The rationale at the time was that, since consumables do not have quant, scrapping them does not make sense. It appears that people use this feature on consumable for traceability reason, so we bring back the old behavior. A slight change was done in stock_scrap.py: if the product is a consumable, do not raise the error if the quantity to scrap is not available in stock for obvious reasons. Also, a leftover todo in the code indicate that we should unreserve the any move having reserved the scrapped product. This is actually already done, since this mechanism is in the move line `action_done` and a scrap uses a classical stock move and stock move lines. We introduce some test to prove that it is possible to scrap a stockable product and the quant are impacted, to scrap a consumable product and the stock is not impacted and to scrap something that was reseved by a move and that the move changes its state. [0] 86f08c091eea2a623ee6460562a750506cbdd585 --- addons/stock/models/stock_picking.py | 4 +- addons/stock/models/stock_scrap.py | 14 ++--- addons/stock/tests/test_move.py | 67 +++++++++++++++++++++++- addons/stock/views/stock_scrap_views.xml | 2 +- 4 files changed, 75 insertions(+), 12 deletions(-) diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py index eea788a7a15..46b937546f7 100644 --- a/addons/stock/models/stock_picking.py +++ b/addons/stock/models/stock_picking.py @@ -778,8 +778,6 @@ class Picking(models.Model): @api.multi def button_scrap(self): self.ensure_one() - # only stockable products are scrapeable - scrapeable_products = self.move_line_ids.mapped('product_id').filtered(lambda p: p.type == 'product') return { 'name': _('Scrap'), 'view_type': 'form', @@ -787,7 +785,7 @@ class Picking(models.Model): 'res_model': 'stock.scrap', 'view_id': self.env.ref('stock.stock_scrap_form_view2').id, 'type': 'ir.actions.act_window', - 'context': {'default_picking_id': self.id, 'product_ids': scrapeable_products.ids}, + 'context': {'default_picking_id': self.id, 'product_ids': self.move_line_ids.mapped('product_id').ids}, 'target': 'new', } diff --git a/addons/stock/models/stock_scrap.py b/addons/stock/models/stock_scrap.py index 4e0cad94729..839987cee2e 100644 --- a/addons/stock/models/stock_scrap.py +++ b/addons/stock/models/stock_scrap.py @@ -79,15 +79,15 @@ class StockScrap(models.Model): def do_scrap(self): for scrap in self: move = self.env['stock.move'].create(scrap._prepare_move_values()) - quantity_in_stock = self.env['stock.quant']._get_quantity( - self.product_id, self.location_id, lot_id=self.lot_id, - package_id=self.package_id, owner_id=self.owner_id, strict=True - ) - if quantity_in_stock < move.product_qty: # FIXME: float compare - raise UserError(_('You cannot scrap a move without having available stock for %s. You can correct it with an inventory adjustment.') % move.product_id.name) + if self.product_id.type == 'product': + quantity_in_stock = self.env['stock.quant']._get_quantity( + self.product_id, self.location_id, lot_id=self.lot_id, + package_id=self.package_id, owner_id=self.owner_id, strict=True + ) + if quantity_in_stock < move.product_qty: # FIXME: float compare + raise UserError(_('You cannot scrap a move without having available stock for %s. You can correct it with an inventory adjustment.') % move.product_id.name) move.action_done() scrap.write({'move_id': move.id, 'state': 'done'}) - # TODO: unreserve reserved return True def _prepare_move_values(self): diff --git a/addons/stock/tests/test_move.py b/addons/stock/tests/test_move.py index 04406847d7e..a29e2f00e65 100644 --- a/addons/stock/tests/test_move.py +++ b/addons/stock/tests/test_move.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo.tests.common import TransactionCase from odoo.exceptions import UserError +from odoo.tests.common import TransactionCase class StockMove(TransactionCase): @@ -30,6 +30,11 @@ class StockMove(TransactionCase): 'tracking': 'lot', 'categ_id': self.env.ref('product.product_category_all').id, }) + self.product4 = self.env['product.product'].create({ + 'name': 'Product A', + 'type': 'consu', + 'categ_id': self.env.ref('product.product_category_all').id, + }) def test_in_1(self): """ Receive products from a supplier. Check that a move line is created and that the @@ -2220,3 +2225,63 @@ class StockMove(TransactionCase): move1.product_id = self.product2 move1.onchange_product_id() self.assertEqual(move1.product_uom_qty, 100) + + def test_scrap_1(self): + """ Check the created stock move and the impact on quants when we scrap a + stockable product. + """ + self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1) + scrap = self.env['stock.scrap'].create({ + 'product_id': self.product1.id, + 'product_uom_id':self.product1.uom_id.id, + 'scrap_qty': 1, + }) + self.assertEqual(scrap.state, 'done') + move = scrap.move_id + self.assertEqual(move.state, 'done') + self.assertEqual(move.quantity_done, 1) + self.assertEqual(move.scrapped, True) + self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 0) + + def test_scrap_2(self): + """ Check the created stock move and the impact on quants when we scrap a + consumable product. + """ + scrap = self.env['stock.scrap'].create({ + 'product_id': self.product4.id, + 'product_uom_id':self.product4.uom_id.id, + 'scrap_qty': 1, + }) + self.assertEqual(scrap.state, 'done') + move = scrap.move_id + self.assertEqual(move.state, 'done') + self.assertEqual(move.quantity_done, 1) + self.assertEqual(move.scrapped, True) + self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product4, self.stock_location), 0) + + def test_scrap_3(self): + """ Scrap the product of a reserved move line. Check that the move line is + correctly deleted and that the associated stock move is not assigned anymore. + """ + self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1) + move1 = self.env['stock.move'].create({ + 'name': 'test_set_quantity_done_1', + 'location_id': self.stock_location.id, + 'location_dest_id': self.customer_location.id, + 'product_id': self.product1.id, + 'product_uom': self.uom_unit.id, + 'product_uom_qty': 1.0, + }) + move1.action_confirm() + move1.action_assign() + self.assertEqual(move1.state, 'assigned') + self.assertEqual(len(move1.move_line_ids), 1) + + scrap = self.env['stock.scrap'].create({ + 'product_id': self.product1.id, + 'product_uom_id':self.product1.uom_id.id, + 'scrap_qty': 1, + }) + + self.assertEqual(move1.state, 'confirmed') + self.assertEqual(len(move1.move_line_ids), 0) diff --git a/addons/stock/views/stock_scrap_views.xml b/addons/stock/views/stock_scrap_views.xml index 12e74eca51f..fbd2684d80a 100644 --- a/addons/stock/views/stock_scrap_views.xml +++ b/addons/stock/views/stock_scrap_views.xml @@ -43,7 +43,7 @@ - +