diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py
index 21ce6234d3e..49bcbe0799b 100644
--- a/addons/mrp/models/stock_move.py
+++ b/addons/mrp/models/stock_move.py
@@ -351,7 +351,7 @@ class StockMove(models.Model):
moves_ids_to_unlink = OrderedSet()
phantom_moves_vals_list = []
for move in self:
- if not move.picking_type_id or (move.production_id and move.production_id.product_id == move.product_id):
+ if (not move.picking_type_id and not self.env.context.get('is_scrap')) or (move.production_id and move.production_id.product_id == move.product_id):
moves_ids_to_return.add(move.id)
continue
bom = self.env['mrp.bom'].sudo()._bom_find(move.product_id, company_id=move.company_id.id, bom_type='phantom')[move.product_id]
@@ -364,7 +364,7 @@ class StockMove(models.Model):
factor = move.product_uom._compute_quantity(move.product_uom_qty, bom.product_uom_id) / bom.product_qty
boms, lines = bom.sudo().explode(move.product_id, factor, picking_type=bom.picking_type_id)
for bom_line, line_data in lines:
- if move.picking_id.immediate_transfer or float_is_zero(move.product_uom_qty, precision_rounding=move.product_uom.rounding):
+ if move.picking_id.immediate_transfer or float_is_zero(move.product_uom_qty, precision_rounding=move.product_uom.rounding) or self.env.context.get('is_scrap'):
phantom_moves_vals_list += move._generate_move_phantom(bom_line, 0, line_data['qty'])
else:
phantom_moves_vals_list += move._generate_move_phantom(bom_line, line_data['qty'], 0)
diff --git a/addons/mrp/models/stock_scrap.py b/addons/mrp/models/stock_scrap.py
index 20ea8329c6a..b8f4184c8de 100644
--- a/addons/mrp/models/stock_scrap.py
+++ b/addons/mrp/models/stock_scrap.py
@@ -3,7 +3,6 @@
from odoo import _, api, fields, models
-
class StockScrap(models.Model):
_inherit = 'stock.scrap'
@@ -14,6 +13,12 @@ class StockScrap(models.Model):
'mrp.workorder', 'Work Order',
states={'done': [('readonly', True)]},
check_company=True) # Not to restrict or prefer quants, but informative
+ product_is_kit = fields.Boolean(related='product_id.is_kits')
+ product_template = fields.Many2one(related='product_id.product_tmpl_id')
+ bom_id = fields.Many2one(
+ 'mrp.bom', 'Kit',
+ domain="[('type', '=', 'phantom'), '|', ('product_id', '=', product_id), '&', ('product_id', '=', False), ('product_tmpl_id', '=', product_template)]",
+ states={'done': [('readonly', True)]}, check_company=True)
@api.onchange('workorder_id')
def _onchange_workorder_id(self):
@@ -50,3 +55,19 @@ class StockScrap(models.Model):
return {'warning': {'title': _('Warning'), 'message': message}}
else:
return super()._onchange_serial_number()
+
+ @api.depends('move_ids', 'move_ids.move_line_ids.qty_done', 'product_id')
+ def _compute_scrap_qty(self):
+ self.scrap_qty = 1
+ for scrap in self:
+ if not scrap.bom_id:
+ return super(StockScrap, scrap)._compute_scrap_qty()
+ if scrap.move_ids:
+ filters = {
+ 'incoming_moves': lambda m: True,
+ 'outgoing_moves': lambda m: False
+ }
+ scrap.scrap_qty = scrap.move_ids._compute_kit_quantities(scrap.product_id, scrap.scrap_qty, scrap.bom_id, filters)
+
+ def _should_check_available_qty(self):
+ return super()._should_check_available_qty() or self.product_is_kit
diff --git a/addons/mrp/tests/test_stock.py b/addons/mrp/tests/test_stock.py
index f15ae8e219f..27f6a4c0c32 100644
--- a/addons/mrp/tests/test_stock.py
+++ b/addons/mrp/tests/test_stock.py
@@ -146,7 +146,7 @@ class TestWarehouseMrp(common.TestMrpCommon):
# Scrap Product Wood with lot.
scrap_id = self.env['stock.scrap'].with_context(active_model='mrp.production', active_id=production_3.id).create({'product_id': self.product_2.id, 'scrap_qty': 1.0, 'product_uom_id': self.product_2.uom_id.id, 'location_id': location_id, 'lot_id': lot_product_2.id, 'production_id': production_3.id})
scrap_id.do_scrap()
- scrap_move = scrap_id.move_id
+ scrap_move = scrap_id.move_ids[0]
self.assertTrue(scrap_move.raw_material_production_id)
self.assertTrue(scrap_move.scrapped)
diff --git a/addons/mrp/views/stock_scrap_views.xml b/addons/mrp/views/stock_scrap_views.xml
index 11fcd7bd077..e1b51b20ab8 100644
--- a/addons/mrp/views/stock_scrap_views.xml
+++ b/addons/mrp/views/stock_scrap_views.xml
@@ -8,6 +8,9 @@
+
+
+
@@ -19,6 +22,9 @@
+
+
+
diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py
index 703f6bd965e..f99ef69c8cc 100644
--- a/addons/stock/models/stock_move.py
+++ b/addons/stock/models/stock_move.py
@@ -127,7 +127,7 @@ class StockMove(models.Model):
"this second option should be chosen.")
scrapped = fields.Boolean(
'Scrapped', related='location_dest_id.scrap_location', readonly=True, store=True)
- scrap_ids = fields.One2many('stock.scrap', 'move_id')
+ scrap_id = fields.Many2one('stock.scrap', 'Scrap operation', readonly=True, check_company=True)
group_id = fields.Many2one('procurement.group', 'Procurement Group', default=_default_group_id, index=True)
rule_id = fields.Many2one(
'stock.rule', 'Stock Rule', ondelete='restrict', help='The stock rule that created this stock move',
@@ -1881,7 +1881,7 @@ Please change the quantity done or the rounding precision of your unit of measur
# We don't want to create back order for scrap moves
# Replace by a kwarg in master
if self.env.context.get('is_scrap'):
- return moves_todo
+ return moves
if picking and not cancel_backorder:
backorder = picking._create_backorder()
diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py
index 8c33239c9f6..967ab43e396 100644
--- a/addons/stock/models/stock_picking.py
+++ b/addons/stock/models/stock_picking.py
@@ -947,7 +947,7 @@ class Picking(models.Model):
move_ids_without_package |= move
else:
move_ids_without_package |= move
- return move_ids_without_package.filtered(lambda move: not move.scrap_ids)
+ return move_ids_without_package.filtered(lambda move: not move.scrap_id)
def _check_move_lines_map_quant_package(self, package):
return package._check_move_lines_map_quant(self.move_line_ids.filtered(lambda ml: ml.package_id == package), 'reserved_qty')
diff --git a/addons/stock/models/stock_scrap.py b/addons/stock/models/stock_scrap.py
index c2719d63f14..1034cfe6db2 100644
--- a/addons/stock/models/stock_scrap.py
+++ b/addons/stock/models/stock_scrap.py
@@ -34,7 +34,7 @@ class StockScrap(models.Model):
'stock.quant.package', 'Package',
states={'done': [('readonly', True)]}, check_company=True)
owner_id = fields.Many2one('res.partner', 'Owner', states={'done': [('readonly', True)]}, check_company=True)
- move_id = fields.Many2one('stock.move', 'Scrap Move', readonly=True, check_company=True, copy=False)
+ move_ids = fields.One2many('stock.move', 'scrap_id')
picking_id = fields.Many2one('stock.picking', 'Picking', states={'done': [('readonly', True)]}, check_company=True)
location_id = fields.Many2one(
'stock.location', 'Source Location',
@@ -46,7 +46,7 @@ class StockScrap(models.Model):
domain="[('scrap_location', '=', True), ('company_id', 'in', [company_id, False])]", check_company=True, readonly=False)
scrap_qty = fields.Float(
'Quantity', required=True, states={'done': [('readonly', True)]}, digits='Product Unit of Measure',
- compute='_compute_scrap_qty', precompute=True, readonly=False, store=True)
+ compute='_compute_scrap_qty', default=0.0, readonly=False, store=True)
state = fields.Selection([
('draft', 'Draft'),
('done', 'Done')],
@@ -83,12 +83,12 @@ class StockScrap(models.Model):
for scrap in self:
scrap.scrap_location_id = locations_per_company[scrap.company_id.id]
- @api.depends('move_id', 'move_id.move_line_ids.qty_done', 'product_id')
+ @api.depends('move_ids', 'move_ids.move_line_ids.qty_done', 'product_id')
def _compute_scrap_qty(self):
self.scrap_qty = 1
for scrap in self:
- if scrap.move_id:
- scrap.scrap_qty = scrap.move_id.quantity_done
+ if scrap.move_ids:
+ scrap.scrap_qty = scrap.move_ids[0].quantity_done
@api.onchange('lot_id')
def _onchange_serial_number(self):
@@ -120,6 +120,7 @@ class StockScrap(models.Model):
'product_uom_qty': self.scrap_qty,
'location_id': self.location_id.id,
'scrapped': True,
+ 'scrap_id': self.id,
'location_dest_id': self.scrap_location_id.id,
'move_line_ids': [(0, 0, {'product_id': self.product_id.id,
'product_uom_id': self.product_uom_id.id,
@@ -140,7 +141,7 @@ class StockScrap(models.Model):
move = self.env['stock.move'].create(scrap._prepare_move_values())
# master: replace context by cancel_backorder
move.with_context(is_scrap=True)._action_done()
- scrap.write({'move_id': move.id, 'state': 'done'})
+ scrap.write({'state': 'done'})
scrap.date_done = fields.Datetime.now()
return True
@@ -151,25 +152,32 @@ class StockScrap(models.Model):
def action_get_stock_move_lines(self):
action = self.env['ir.actions.act_window']._for_xml_id('stock.stock_move_line_action')
- action['domain'] = [('move_id', '=', self.move_id.id)]
+ action['domain'] = [('move_id', 'in', self.move_ids.ids)]
return action
+ def _should_check_available_qty(self):
+ return self.product_id.type == 'product'
+
+ def check_available_qty(self):
+ if not self._should_check_available_qty():
+ return True
+
+ precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
+ available_qty = self.with_context(
+ location=self.location_id.id,
+ lot_id=self.lot_id.id,
+ package_id=self.package_id.id,
+ owner_id=self.owner_id.id
+ ).product_id.qty_available
+ scrap_qty = self.product_uom_id._compute_quantity(self.scrap_qty, self.product_id.uom_id)
+ return float_compare(available_qty, scrap_qty, precision_digits=precision) >= 0
+
def action_validate(self):
self.ensure_one()
if float_is_zero(self.scrap_qty,
precision_rounding=self.product_uom_id.rounding):
raise UserError(_('You can only enter positive quantities.'))
- if self.product_id.type != 'product':
- return self.do_scrap()
- precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
- available_qty = sum(self.env['stock.quant']._gather(self.product_id,
- self.location_id,
- self.lot_id,
- self.package_id,
- self.owner_id,
- strict=True).mapped('quantity'))
- scrap_qty = self.product_uom_id._compute_quantity(self.scrap_qty, self.product_id.uom_id)
- if float_compare(available_qty, scrap_qty, precision_digits=precision) >= 0:
+ if self.check_available_qty():
return self.do_scrap()
else:
ctx = dict(self.env.context)
@@ -177,7 +185,7 @@ class StockScrap(models.Model):
'default_product_id': self.product_id.id,
'default_location_id': self.location_id.id,
'default_scrap_id': self.id,
- 'default_quantity': scrap_qty,
+ 'default_quantity': self.product_uom_id._compute_quantity(self.scrap_qty, self.product_id.uom_id),
'default_product_uom_name': self.product_id.uom_name
})
return {
diff --git a/addons/stock/report/stock_traceability.py b/addons/stock/report/stock_traceability.py
index eb1aba84c8b..15d99f77076 100644
--- a/addons/stock/report/stock_traceability.py
+++ b/addons/stock/report/stock_traceability.py
@@ -94,10 +94,10 @@ class MrpStockReport(models.TransientModel):
res_model = 'stock.move'
res_id = move_line.move_id.id
ref = 'Inventory Adjustment'
- elif move_line.move_id.scrapped and move_line.move_id.scrap_ids:
+ elif move_line.move_id.scrapped and move_line.move_id.scrap_id:
res_model = 'stock.scrap'
- res_id = move_line.move_id.scrap_ids[0].id
- ref = move_line.move_id.scrap_ids[0].name
+ res_id = move_line.move_id.scrap_id.id
+ ref = move_line.move_id.scrap_id.name
return res_model, res_id, ref
@api.model
diff --git a/addons/stock/tests/test_move.py b/addons/stock/tests/test_move.py
index 1184dfb7e17..72983743f1d 100644
--- a/addons/stock/tests/test_move.py
+++ b/addons/stock/tests/test_move.py
@@ -4727,7 +4727,7 @@ class StockMove(TransactionCase):
scrap = scrap_form.save()
scrap.do_scrap()
self.assertEqual(scrap.state, 'done')
- move = scrap.move_id
+ move = scrap.move_ids[0]
self.assertEqual(move.state, 'done')
self.assertEqual(move.quantity_done, 1)
self.assertEqual(move.scrapped, True)
@@ -4746,7 +4746,7 @@ class StockMove(TransactionCase):
scrap.do_scrap()
self.assertTrue(scrap.name.startswith('SP/'), 'Sequence should be Changed after do_scrap')
self.assertEqual(scrap.state, 'done')
- move = scrap.move_id
+ move = scrap.move_ids[0]
self.assertEqual(move.state, 'done')
self.assertEqual(move.quantity_done, 1)
self.assertEqual(move.scrapped, True)
@@ -4818,7 +4818,7 @@ class StockMove(TransactionCase):
self.assertEqual(len(picking.move_ids), 2)
scrapped_move = picking.move_ids.filtered(lambda m: m.state == 'done')
self.assertTrue(scrapped_move, 'No scrapped move created.')
- self.assertEqual(scrapped_move.scrap_ids.ids, [scrap.id], 'Wrong scrap linked to the move.')
+ self.assertEqual(scrapped_move.scrap_id.id, scrap.id, 'Wrong scrap linked to the move.')
self.assertEqual(scrap.scrap_qty, 5, 'Scrap quantity has been modified and is not correct anymore.')
scrapped_move.quantity_done = 8
diff --git a/addons/stock/tests/test_stock_flow.py b/addons/stock/tests/test_stock_flow.py
index 56f9a5793cc..c466c4725f6 100644
--- a/addons/stock/tests/test_stock_flow.py
+++ b/addons/stock/tests/test_stock_flow.py
@@ -2381,7 +2381,7 @@ class TestStockFlow(TestStockCommon):
self.assertEqual(picking.state, 'cancel')
self.assertEqual(move.state, 'cancel')
- self.assertEqual(scrap.move_id.state, 'done')
+ self.assertEqual(scrap.move_ids[0].state, 'done')
def test_receive_tracked_product(self):
self.productA.tracking = 'serial'
diff --git a/addons/stock/views/stock_scrap_views.xml b/addons/stock/views/stock_scrap_views.xml
index 97f862422c5..e90fde8b570 100644
--- a/addons/stock/views/stock_scrap_views.xml
+++ b/addons/stock/views/stock_scrap_views.xml
@@ -38,12 +38,12 @@
-
+