[IMP] mrp,stock: improve duplicate SN warnings
Add and improve onchange warnings when a duplicate SN is used in following cases: inventory, picking (any type), manufacturing, scrap, and "Update Quantity" (i.e. directly edit quants from product form). Improvement includes: - include location where the existing SN is - auto-correct source location when appropriate (e.g. trying to scrap a SN in the wrong location) The goal of this is to prevent but not restrict duplicate SNs so users can have some flexibility (especially if a dupe SN occurs because of an error such as doing pick-pack-ship out of order.) closes odoo/odoo#61287 Task: 1924758 Signed-off-by: Arnold Moyaux <amoyaux@users.noreply.github.com>
This commit is contained in:
@@ -671,19 +671,12 @@ class MrpProduction(models.Model):
|
||||
|
||||
@api.onchange('lot_producing_id')
|
||||
def _onchange_lot_producing(self):
|
||||
if self.product_id.tracking == 'serial':
|
||||
if self.env['stock.move.line'].search_count([
|
||||
('company_id', '=', self.company_id.id),
|
||||
('product_id', '=', self.product_id.id),
|
||||
('lot_id', '=', self.lot_producing_id.id),
|
||||
('state', '!=', 'cancel')
|
||||
]):
|
||||
return {
|
||||
'warning': {
|
||||
'title': _('Warning'),
|
||||
'message': _('Existing Serial number (%s). Please correct the serial numbers encoded.') % self.lot_producing_id.name
|
||||
}
|
||||
}
|
||||
if self.product_id.tracking == 'serial' and self.lot_producing_id:
|
||||
message, dummy = self.env['stock.quant']._check_serial_number(self.product_id,
|
||||
self.lot_producing_id,
|
||||
self.company_id)
|
||||
if message:
|
||||
return {'warning': {'title': _('Warning'), 'message': message}}
|
||||
|
||||
@api.onchange('bom_id')
|
||||
def _onchange_workorder_ids(self):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo import _, api, fields, models
|
||||
|
||||
|
||||
class StockScrap(models.Model):
|
||||
@@ -34,3 +34,19 @@ class StockScrap(models.Model):
|
||||
else:
|
||||
vals.update({'raw_material_production_id': self.production_id.id})
|
||||
return vals
|
||||
|
||||
@api.onchange('lot_id')
|
||||
def _onchange_serial_number(self):
|
||||
if self.product_id.tracking == 'serial' and self.lot_id:
|
||||
if self.production_id:
|
||||
message, recommended_location = self.env['stock.quant']._check_serial_number(self.product_id,
|
||||
self.lot_id,
|
||||
self.company_id,
|
||||
self.location_id,
|
||||
self.production_id.location_dest_id)
|
||||
if message:
|
||||
if recommended_location:
|
||||
self.location_id = recommended_location
|
||||
return {'warning': {'title': _('Warning'), 'message': message}}
|
||||
else:
|
||||
return super()._onchange_serial_number()
|
||||
|
||||
@@ -1523,3 +1523,45 @@ class TestMrpOrder(TestMrpCommon):
|
||||
# of a conversion 187.5ml = 0.188L
|
||||
# thus creating an extra line with 'product_uom_qty': 0.5
|
||||
self.assertEqual(len(mo_product_final_form.move_raw_ids.move_line_ids), 1, 'One move line should exist for the MO.')
|
||||
|
||||
def test_mo_sn_warning(self):
|
||||
""" Checks that when a MO where the final product is tracked by serial, a warning pops up if
|
||||
the `lot_producting_id` has previously been used already (i.e. dupe SN). Also checks if a
|
||||
scrap linked to a MO has its sn warning correctly pop up.
|
||||
"""
|
||||
self.stock_location = self.env.ref('stock.stock_location_stock')
|
||||
mo, _, p_final, _, _ = self.generate_mo(tracking_final='serial', qty_base_1=1, qty_final=1)
|
||||
self.assertEqual(len(mo), 1, 'MO should have been created')
|
||||
|
||||
sn1 = self.env['stock.production.lot'].create({
|
||||
'name': 'serial1',
|
||||
'product_id': p_final.id,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
|
||||
self.env['stock.quant']._update_available_quantity(p_final, self.stock_location, 1, lot_id=sn1)
|
||||
mo.lot_producing_id = sn1
|
||||
|
||||
warning = False
|
||||
warning = mo._onchange_lot_producing()
|
||||
self.assertTrue(warning, 'Reuse of existing serial number not detected')
|
||||
self.assertEqual(list(warning.keys())[0], 'warning', 'Warning message was not returned')
|
||||
|
||||
mo.action_generate_serial()
|
||||
sn2 = mo.lot_producing_id
|
||||
mo.button_mark_done()
|
||||
|
||||
# scrap linked to MO but with wrong SN location
|
||||
scrap = self.env['stock.scrap'].create({
|
||||
'product_id': p_final.id,
|
||||
'product_uom_id': self.uom_unit.id,
|
||||
'production_id': mo.id,
|
||||
'location_id': self.stock_location_14.id,
|
||||
'lot_id': sn2.id
|
||||
})
|
||||
|
||||
warning = False
|
||||
warning = scrap._onchange_serial_number()
|
||||
self.assertTrue(warning, 'Use of wrong serial number location not detected')
|
||||
self.assertEqual(list(warning.keys())[0], 'warning', 'Warning message was not returned')
|
||||
self.assertEqual(scrap.location_id, mo.location_dest_id, 'Location was not auto-corrected')
|
||||
|
||||
@@ -590,6 +590,37 @@ class InventoryLine(models.Model):
|
||||
self.product_qty = theoretical_qty
|
||||
self.theoretical_qty = theoretical_qty
|
||||
|
||||
@api.onchange('product_qty', 'prod_lot_id')
|
||||
def _onchange_serial_number(self):
|
||||
if self.prod_lot_id and self.product_id.tracking == 'serial' and not float_is_zero(self.product_qty, self.product_id.uom_id.rounding):
|
||||
dupe_sn_lines = self.env['stock.inventory.line']
|
||||
message = _('Is this expected? For example this can happen if pick-pack-ship was not completed in the right order. In this case the issue will be solved '
|
||||
'automatically once all steps are completed. Otherwise, you can adjust quantities in these locations.')
|
||||
# first check current inventory for non-zero dupe SNs
|
||||
if self.inventory_id:
|
||||
dupe_sn_lines |= self.inventory_id.line_ids.filtered(
|
||||
lambda l: l.product_id == self.product_id
|
||||
and l.prod_lot_id == self.prod_lot_id
|
||||
and l.location_id.id is not self.location_id.id
|
||||
and not float_is_zero(l.product_qty, l.product_id.uom_id.rounding))
|
||||
if len(dupe_sn_lines) > 0:
|
||||
message = _('There is already one or more inventory adjustment line(s) counting the Serial Number (%s) at the location(s): %s.\n\n',
|
||||
self.prod_lot_id.name, ', '.join(dupe_sn_lines.location_id.mapped('display_name'))) + message
|
||||
return {'warning': {'title': _('Warning'), 'message': message}}
|
||||
# then check locations not in inventory for dupe SNs
|
||||
if self.inventory_id and self.inventory_id.location_ids:
|
||||
quants = self.env['stock.quant'].search([('product_id', '=', self.product_id.id),
|
||||
('lot_id', '=', self.prod_lot_id.id),
|
||||
('quantity', '!=', 0),
|
||||
('location_id', 'not in', self.inventory_id.location_ids.ids),
|
||||
'|', ('location_id.usage', '=', 'customer'),
|
||||
'&', ('company_id', '=', self.company_id.id),
|
||||
('location_id.usage', 'in', ('internal', 'transit'))])
|
||||
if quants:
|
||||
message = _('The Serial Number (%s) is already used in these location(s): %s.\n\n',
|
||||
self.prod_lot_id.name, ', '.join(quants.location_id.mapped('display_name'))) + message
|
||||
return {'warning': {'title': _('Warning'), 'message': message}}
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
""" Override to handle the case we create inventory line without
|
||||
|
||||
@@ -896,16 +896,19 @@ class StockMove(models.Model):
|
||||
quantity_done = sum(ml.product_uom_id._compute_quantity(ml.qty_done, self.product_uom) for ml in self.move_line_ids.filtered(lambda ml: not ml.lot_id and ml.lot_name))
|
||||
quantity_done += self.product_id.uom_id._compute_quantity(len(self.lot_ids), self.product_uom)
|
||||
self.update({'quantity_done': quantity_done})
|
||||
used_lots = self.env['stock.move.line'].search([
|
||||
('company_id', '=', self.company_id.id),
|
||||
('product_id', '=', self.product_id.id),
|
||||
('lot_id', 'in', self.lot_ids.ids),
|
||||
('move_id', '!=', self._origin.id),
|
||||
('state', '!=', 'cancel')
|
||||
])
|
||||
if used_lots:
|
||||
|
||||
quants = self.env['stock.quant'].search([('product_id', '=', self.product_id.id),
|
||||
('lot_id', 'in', self.lot_ids.ids),
|
||||
('quantity', '!=', 0),
|
||||
'|', ('location_id.usage', '=', 'customer'),
|
||||
'&', ('company_id', '=', self.company_id.id),
|
||||
('location_id.usage', 'in', ('internal', 'transit'))])
|
||||
if quants:
|
||||
sn_to_location = ""
|
||||
for quant in quants:
|
||||
sn_to_location += _("\n(%s) exists in location %s", quant.lot_id.display_name, quant.location_id.display_name)
|
||||
return {
|
||||
'warning': {'title': _('Warning'), 'message': _('Existing Serial numbers (%s). Please correct the serial numbers encoded.') % ','.join(used_lots.lot_id.mapped('display_name'))}
|
||||
'warning': {'title': _('Warning'), 'message': _('Existing Serial numbers. Please correct the serial numbers encoded:') + sn_to_location}
|
||||
}
|
||||
|
||||
@api.onchange('move_line_ids', 'move_line_nosuggest_ids', 'picking_type_id')
|
||||
|
||||
@@ -133,6 +133,7 @@ class StockMoveLine(models.Model):
|
||||
help him. This includes:
|
||||
- automatically switch `qty_done` to 1.0
|
||||
- warn if he has already encoded `lot_name` in another move line
|
||||
- warn (and update if appropriate) if the SN is in a different source location than selected
|
||||
"""
|
||||
res = {}
|
||||
if self.product_id.tracking == 'serial':
|
||||
@@ -147,17 +148,25 @@ class StockMoveLine(models.Model):
|
||||
if counter.get(self.lot_name) and counter[self.lot_name] > 1:
|
||||
message = _('You cannot use the same serial number twice. Please correct the serial numbers encoded.')
|
||||
elif not self.lot_id:
|
||||
counter = self.env['stock.production.lot'].search_count([
|
||||
('company_id', '=', self.company_id.id),
|
||||
('product_id', '=', self.product_id.id),
|
||||
('name', '=', self.lot_name),
|
||||
])
|
||||
if counter > 0:
|
||||
message = _('Existing Serial number (%s). Please correct the serial number encoded.') % self.lot_name
|
||||
lots = self.env['stock.production.lot'].search([('product_id', '=', self.product_id.id),
|
||||
('name', '=', self.lot_name),
|
||||
('company_id', '=', self.company_id.id)])
|
||||
quants = lots.quant_ids.filtered(lambda q: q.quantity != 0 and q.location_id.usage in ['customer', 'internal', 'transit'])
|
||||
if quants:
|
||||
message = _('Serial number (%s) already exists in location(s): %s. Please correct the serial number encoded.', self.lot_name, ', '.join(quants.location_id.mapped('display_name')))
|
||||
elif self.lot_id:
|
||||
counter = Counter([line.lot_id.id for line in move_lines_to_check])
|
||||
if counter.get(self.lot_id.id) and counter[self.lot_id.id] > 1:
|
||||
message = _('You cannot use the same serial number twice. Please correct the serial numbers encoded.')
|
||||
else:
|
||||
# check if in correct source location
|
||||
message, recommended_location = self.env['stock.quant']._check_serial_number(self.product_id,
|
||||
self.lot_id,
|
||||
self.company_id,
|
||||
self.location_id,
|
||||
self.picking_id.location_id)
|
||||
if recommended_location:
|
||||
self.location_id = recommended_location
|
||||
if message:
|
||||
res['warning'] = {'title': _('Warning'), 'message': message}
|
||||
return res
|
||||
|
||||
@@ -369,6 +369,15 @@ class StockQuant(models.Model):
|
||||
}
|
||||
return {'warning': warning}
|
||||
|
||||
@api.onchange('lot_id')
|
||||
def _onchange_serial_number(self):
|
||||
if self.lot_id and self.product_id.tracking == 'serial':
|
||||
message, dummy = self.env['stock.quant']._check_serial_number(self.product_id,
|
||||
self.lot_id,
|
||||
self.company_id)
|
||||
if message:
|
||||
return {'warning': {'title': _('Warning'), 'message': message}}
|
||||
|
||||
@api.model
|
||||
def _update_available_quantity(self, product_id, location_id, quantity, lot_id=None, package_id=None, owner_id=None, in_date=None):
|
||||
""" Increase or decrease `reserved_quantity` of a set of quants for a given set of
|
||||
@@ -643,6 +652,68 @@ class StockQuant(models.Model):
|
||||
})
|
||||
return action
|
||||
|
||||
@api.model
|
||||
def _check_serial_number(self, product_id, lot_id, company_id, source_location_id=None, ref_doc_location_id=None):
|
||||
""" Checks for duplicate serial numbers (SN) when assigning a SN (i.e. no source_location_id)
|
||||
and checks for potential incorrect location selection of a SN when using a SN (i.e.
|
||||
source_location_id). Returns warning message of all locations the SN is located at and
|
||||
(optionally) a recommended source location of the SN (when using SN from incorrect location).
|
||||
This function is designed to be used by onchange functions across differing situations including,
|
||||
but not limited to scrap, incoming picking SN encoding, and outgoing picking SN selection.
|
||||
|
||||
:param product_id: `product.product` product to check SN for
|
||||
:param lot_id: `stock.production.lot` SN to check
|
||||
:param company_id: `res.company` company to check against (i.e. we ignore duplicate SNs across
|
||||
different companies)
|
||||
:param source_location_id: `stock.location` optional source location if using the SN rather
|
||||
than assigning it
|
||||
:param ref_doc_location_id: `stock.location` optional reference document location for
|
||||
determining recommended location. This is param expected to only be used when a
|
||||
`source_location_id` is provided.
|
||||
:return: tuple(message, recommended_location) If not None, message is a string expected to be
|
||||
used in warning message dict and recommended_location is a `location_id`
|
||||
"""
|
||||
message = None
|
||||
recommended_location = None
|
||||
if product_id.tracking == 'serial':
|
||||
quants = self.env['stock.quant'].search([('product_id', '=', product_id.id),
|
||||
('lot_id', '=', lot_id.id),
|
||||
('quantity', '!=', 0),
|
||||
'|', ('location_id.usage', '=', 'customer'),
|
||||
'&', ('company_id', '=', company_id.id),
|
||||
('location_id.usage', 'in', ('internal', 'transit'))])
|
||||
sn_locations = quants.mapped('location_id')
|
||||
if quants:
|
||||
if not source_location_id:
|
||||
# trying to assign an already existing SN
|
||||
message = _('The Serial Number (%s) is already used in these location(s): %s.\n\n'
|
||||
'Is this expected? For example this can occur if a delivery operation is validated '
|
||||
'before its corresponding receipt operation is validated. In this case the issue will be solved '
|
||||
'automatically once all steps are completed. Otherwise, the serial numbershould be corrected to '
|
||||
'prevent inconsistent data.',
|
||||
lot_id.name, ', '.join(sn_locations.mapped('display_name')))
|
||||
|
||||
elif source_location_id and source_location_id not in sn_locations:
|
||||
# using an existing SN in the wrong location
|
||||
recommended_location = self.env['stock.location']
|
||||
if ref_doc_location_id:
|
||||
for location in sn_locations:
|
||||
if ref_doc_location_id.parent_path in location.parent_path:
|
||||
recommended_location = location
|
||||
break
|
||||
else:
|
||||
for location in sn_locations:
|
||||
if location.usage != 'customer':
|
||||
recommended_location = location
|
||||
break
|
||||
if recommended_location:
|
||||
message = _('Serial number (%s) is not located in %s, but is located in location(s): %s. Source location for this move will be changed to %s',
|
||||
lot_id.name, source_location_id.display_name, ', '.join(sn_locations.mapped('display_name')), recommended_location.display_name)
|
||||
else:
|
||||
message = _('Serial number (%s) is not located in %s, but is located in location(s): %s. Please correct this to prevent inconsistent data.',
|
||||
lot_id.name, source_location_id.display_name, ', '.join(sn_locations.mapped('display_name')))
|
||||
return message, recommended_location
|
||||
|
||||
|
||||
class QuantPackage(models.Model):
|
||||
""" Packages containing quants and/or other packages """
|
||||
|
||||
@@ -96,6 +96,19 @@ class StockScrap(models.Model):
|
||||
self.location_id = False
|
||||
self.scrap_location_id = False
|
||||
|
||||
@api.onchange('lot_id')
|
||||
def _onchange_serial_number(self):
|
||||
if self.product_id.tracking == 'serial' and self.lot_id:
|
||||
message, recommended_location = self.env['stock.quant']._check_serial_number(self.product_id,
|
||||
self.lot_id,
|
||||
self.company_id,
|
||||
self.location_id,
|
||||
self.picking_id.location_dest_id)
|
||||
if message:
|
||||
if recommended_location:
|
||||
self.location_id = recommended_location
|
||||
return {'warning': {'title': _('Warning'), 'message': message}}
|
||||
|
||||
@api.ondelete(at_uninstall=False)
|
||||
def _unlink_except_done(self):
|
||||
if 'done' in self.mapped('state'):
|
||||
|
||||
@@ -936,3 +936,53 @@ class TestInventory(TransactionCase):
|
||||
Inventory._run_conflict_inventory_tasks()
|
||||
neg_qty_inventories = Inventory.search([('is_conflict_inventory', '=', True)])
|
||||
self.assertEqual(len(neg_qty_inventories), 0)
|
||||
|
||||
def test_inventory_sn_warning(self):
|
||||
""" Check that warnings pop up when duplicate SNs added.
|
||||
Two cases covered:
|
||||
- Check for dupes within the inventory (i.e. inventory adjustment lines)
|
||||
- Check for dupes not in inventory (i.e. existing quants w/ same SN name)
|
||||
"""
|
||||
# check duplicated SN within inventory adjustment
|
||||
lot1 = self.env['stock.production.lot'].create({
|
||||
'name': 'sn1',
|
||||
'product_id': self.product2.id,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
self.env['stock.quant']._update_available_quantity(self.product2, self.pack_location, 1, lot_id=lot1)
|
||||
|
||||
inventory = self.env['stock.inventory'].create({
|
||||
'name': 'Serial Onchange Check',
|
||||
'location_ids': [self.stock_location.id, self.pack_location.id],
|
||||
'product_ids': [(4, self.product2.id)],
|
||||
})
|
||||
|
||||
inventory.action_start()
|
||||
self.assertEqual(len(inventory.line_ids), 1)
|
||||
|
||||
inventory_line = self.env['stock.inventory.line'].create({
|
||||
'inventory_id': inventory.id,
|
||||
'location_id': self.stock_location.id,
|
||||
'product_id': self.product2.id,
|
||||
'prod_lot_id': lot1.id,
|
||||
'product_qty': 1
|
||||
})
|
||||
warning = False
|
||||
warning = inventory_line._onchange_serial_number()
|
||||
self.assertTrue(warning, 'Multiple lines with same serial number not detected')
|
||||
self.assertEqual(list(warning.keys())[0], 'warning', 'Warning message was not returned')
|
||||
|
||||
# check duplicate SN outside of inventory adjustment
|
||||
lot2 = self.env['stock.production.lot'].create({
|
||||
'name': 'sn2',
|
||||
'product_id': self.product2.id,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
self.env['stock.quant']._update_available_quantity(self.product2, self.customer_location, 1, lot_id=lot2)
|
||||
inventory_line.write({
|
||||
'prod_lot_id': lot2.id,
|
||||
})
|
||||
warning = False
|
||||
warning = inventory_line._onchange_serial_number()
|
||||
self.assertTrue(warning, 'Reuse of existing serial number not detected')
|
||||
self.assertEqual(list(warning.keys())[0], 'warning', 'Warning message was not returned')
|
||||
|
||||
@@ -3920,6 +3920,41 @@ class StockMove(TransactionCase):
|
||||
insufficient_qty_wizard.action_done()
|
||||
self.assertEqual(self.env['stock.quant']._gather(self.product, self.stock_location).quantity, -11)
|
||||
|
||||
def test_scrap_7_sn_warning(self):
|
||||
""" Check serial numbers are correctly double checked """
|
||||
|
||||
child_loc1 = self.env['stock.location'].create({
|
||||
'name': "child_location1",
|
||||
'usage': 'internal',
|
||||
'location_id': self.stock_location.id
|
||||
})
|
||||
child_loc2 = self.env['stock.location'].create({
|
||||
'name': "child_location2",
|
||||
'usage': 'internal',
|
||||
'location_id': self.stock_location.id
|
||||
})
|
||||
|
||||
lot1 = self.env['stock.production.lot'].create({
|
||||
'name': 'serial1',
|
||||
'product_id': self.product_serial.id,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
|
||||
self.env['stock.quant']._update_available_quantity(self.product_serial, child_loc1, 1, lot1)
|
||||
|
||||
scrap = self.env['stock.scrap'].create({
|
||||
'product_id': self.product_serial.id,
|
||||
'product_uom_id': self.uom_unit.id,
|
||||
'location_id': child_loc2.id,
|
||||
'lot_id': lot1.id
|
||||
})
|
||||
|
||||
warning = False
|
||||
warning = scrap._onchange_serial_number()
|
||||
self.assertTrue(warning, 'Use of wrong serial number location not detected')
|
||||
self.assertEqual(list(warning.keys())[0], 'warning', 'Warning message was not returned')
|
||||
self.assertEqual(scrap.location_id, child_loc1, 'Location was not auto-corrected')
|
||||
|
||||
def test_in_date_1(self):
|
||||
""" Check that moving a tracked quant keeps the incoming date.
|
||||
"""
|
||||
@@ -4489,3 +4524,60 @@ class StockMove(TransactionCase):
|
||||
line1_result_package = picking.move_line_ids[0].result_package_id
|
||||
line2_result_package = picking.move_line_ids[1].result_package_id
|
||||
self.assertNotEqual(line1_result_package, line2_result_package, "Product and Product1 should be in a different package.")
|
||||
|
||||
def test_move_sn_warning(self):
|
||||
""" Check that warnings pop up when duplicate SNs added or when SN isn't in
|
||||
expected location.
|
||||
Two cases covered:
|
||||
- Check for dupes when assigning serial number to a stock move
|
||||
- Check for dupes when assigning serial number to a stock move line
|
||||
"""
|
||||
|
||||
lot1 = self.env['stock.production.lot'].create({
|
||||
'name': 'serial1',
|
||||
'product_id': self.product_serial.id,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
|
||||
self.env['stock.quant']._update_available_quantity(self.product_serial, self.pack_location, 1, lot1)
|
||||
|
||||
move = self.env['stock.move'].create({
|
||||
'name': 'test sn',
|
||||
'location_id': self.supplier_location.id,
|
||||
'location_dest_id': self.stock_location.id,
|
||||
'product_id': self.product_serial.id,
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
|
||||
move_line = self.env['stock.move.line'].create({
|
||||
'move_id': move.id,
|
||||
'product_id': move.product_id.id,
|
||||
'qty_done': 1,
|
||||
'product_uom_id': move.product_uom.id,
|
||||
'location_id': move.location_id.id,
|
||||
'location_dest_id': move.location_dest_id.id,
|
||||
'lot_name': lot1.name,
|
||||
})
|
||||
|
||||
warning = False
|
||||
warning = move_line._onchange_serial_number()
|
||||
self.assertTrue(warning, 'Reuse of existing serial number (name) not detected')
|
||||
self.assertEqual(list(warning.keys())[0], 'warning', 'Warning message was not returned')
|
||||
|
||||
move_line.write({
|
||||
'lot_name': False,
|
||||
'lot_id': lot1.id
|
||||
})
|
||||
|
||||
warning = False
|
||||
warning = move_line._onchange_serial_number()
|
||||
self.assertTrue(warning, 'Reuse of existing serial number (record) not detected')
|
||||
self.assertEqual(list(warning.keys())[0], 'warning', 'Warning message was not returned')
|
||||
self.assertEqual(move_line.location_id, self.pack_location, 'Location was not auto-corrected')
|
||||
|
||||
move.lot_ids = lot1
|
||||
warning = False
|
||||
warning = move._onchange_lot_ids()
|
||||
self.assertTrue(warning, 'Reuse of existing serial number (record) not detected')
|
||||
self.assertEqual(list(warning.keys())[0], 'warning', 'Warning message was not returned')
|
||||
|
||||
@@ -220,3 +220,33 @@ class TestEditableQuant(TransactionCase):
|
||||
# Try to write on quant with permission
|
||||
quant.with_user(user_admin).write({'inventory_quantity': 8})
|
||||
self.assertEqual(quant.quantity, 8)
|
||||
|
||||
def test_sn_warning(self):
|
||||
""" Checks that a warning is given when reusing an existing SN
|
||||
in inventory mode.
|
||||
"""
|
||||
|
||||
sn1 = self.env['stock.production.lot'].create({
|
||||
'name': 'serial1',
|
||||
'product_id': self.product_tracked_sn.id,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
|
||||
self.Quant.create({
|
||||
'product_id': self.product_tracked_sn.id,
|
||||
'location_id': self.room1.id,
|
||||
'inventory_quantity': 1,
|
||||
'lot_id': sn1.id
|
||||
})
|
||||
|
||||
dupe_sn = self.Quant.create({
|
||||
'product_id': self.product_tracked_sn.id,
|
||||
'location_id': self.room2.id,
|
||||
'inventory_quantity': 1,
|
||||
'lot_id': sn1.id
|
||||
})
|
||||
|
||||
warning = False
|
||||
warning = dupe_sn._onchange_serial_number()
|
||||
self.assertTrue(warning, 'Reuse of existing serial number not detected')
|
||||
self.assertEqual(list(warning.keys())[0], 'warning', 'Warning message was not returned')
|
||||
|
||||
Reference in New Issue
Block a user