[FIX] mrp: handle unbuild/scrap/unscrap and production with same serial

When a product has been unbuilt and also (mistakenly) scrapped, it's
impossible to reuse the serial number in production again, even if the
product is unscrapped.

This fixes the sanity check in a similar way to the _check_sn_uniqueness
function, not just checking for removed stock move lines, but also unremoved
stock move lines. A test case is also added to check this scenario.

This is a similar fix to a previous commit we did
(4f07b260807053586ac6c01cf92ac3d5e37b1041) where we bumped into a
problem with the duplicate serial sanity check.

closes odoo/odoo#142574

X-original-commit: 1761b95d9fda5d5355f35effd1f345a25ded1197
Signed-off-by: William Henrotin (whe) <whe@odoo.com>
This commit is contained in:
Simon Wydooghe
2023-11-20 15:51:23 +00:00
committed by William Henrotin
parent 29a6abfba1
commit d00d6bcb08
2 changed files with 82 additions and 2 deletions
+9 -2
View File
@@ -2560,10 +2560,17 @@ class MrpProduction(models.Model):
removed = self.env['stock.move.line'].search_count([
('lot_id', '=', lot.id),
('state', '=', 'done'),
('location_dest_id.scrap_location', '=', True)
('location_id.scrap_location', '=', False),
('location_dest_id.scrap_location', '=', True),
])
unremoved = self.env['stock.move.line'].search_count([
('lot_id', '=', lot.id),
('state', '=', 'done'),
('location_id.scrap_location', '=', True),
('location_dest_id.scrap_location', '=', False),
])
# Either removed or unbuild
if not ((duplicates_unbuild or removed) and duplicates - duplicates_unbuild - removed == 0):
if not ((duplicates_unbuild or removed) and duplicates - duplicates_unbuild - removed + unremoved == 0):
return True
# Check presence of same sn in current production
duplicates = co_prod_move_lines.filtered(lambda ml: ml.quantity and ml.lot_id == lot)
+73
View File
@@ -1391,6 +1391,79 @@ class TestMrpOrder(TestMrpCommon):
subassembly_mo2.lot_producing_id = subassembly_sn
subassembly_mo2.button_mark_done()
def test_product_produce_duplicate_6(self):
"""Produce a product for the second time with the same serial
after having unbuilt, scrapped and unscrapped the product"""
product = self.env["product.product"].create(
{
"name": "Product",
"type": "product",
"tracking": "serial",
}
)
sn = self.env["stock.lot"].create(
{
"name": "SN",
"product_id": product.id,
"company_id": self.env.company.id,
}
)
mo1_form = Form(self.env["mrp.production"])
mo1_form.product_id = product
mo1 = mo1_form.save()
mo1.action_confirm()
with Form(mo1) as mo:
mo.qty_producing = 1
mo1.lot_producing_id = sn
mo1.button_mark_done()
ub_form = Form(self.env["mrp.unbuild"])
ub_form.mo_id = mo1
ub = ub_form.save()
ub.action_unbuild()
scrap = self.env['stock.scrap'].create({
'product_id': product.id,
'product_uom_id': product.uom_id.id,
'lot_id': sn.id,
})
scrap.do_scrap()
unscrap_picking = self.env['stock.picking'].create({
'picking_type_id': self.env.ref('stock.picking_type_internal').id,
'location_id': scrap.scrap_location_id.id,
'location_dest_id': scrap.location_id.id,
})
unscrap_move = self.env['stock.move'].create({
'name': 'unscrap',
'location_id': scrap.scrap_location_id.id,
'location_dest_id': scrap.location_id.id,
'product_id': product.id,
'product_uom': product.uom_id.id,
'picking_id': unscrap_picking.id,
})
unscrap_picking.action_confirm()
self.env['stock.move.line'].create({
'move_id': unscrap_move.id,
'product_id': unscrap_move.product_id.id,
'lot_id': sn.id,
'quantity': 1,
'product_uom_id': unscrap_move.product_uom.id,
'picking_id': unscrap_move.picking_id.id,
})
unscrap_picking.button_validate()
mo2_form = Form(self.env["mrp.production"])
mo2_form.product_id = product
mo2 = mo2_form.save()
mo2.action_confirm()
with Form(mo2) as mo:
mo.qty_producing = 1
mo2.lot_producing_id = sn
mo2.button_mark_done()
def test_product_produce_12(self):
""" Checks that, the production is robust against deletion of finished move."""