Skip to content

Commit

Permalink
[FIX] stock: assign SN on move Lines
Browse files Browse the repository at this point in the history
Before this commit, we can't use the Generate and Assign Serial Numbers
on already existing move line. It always creates new one, even if the
picking type use "Pre-fill Detailed Operations".
Now, it will edit existing lines, except if we ask to generate more SN
than we have unassigned move lines, or if the picking don't use the
"Pre-fill Detailed Operations" option, in which cases it will still
create new move line with the generated SN.

Also, before this commit, paste a list of serial numbers to assign them
on multiple move lines in once work only with creation of a new move
line for each serial number.
Now, it's also work with existing move lines.

Finally, some code shared the same logic in `stock.move` in the move
line onchange method and in the `_generate_serial_numbers` method.
Moves this code in a new method, `_generate_serial_move_line_commands`,
and call it in the two previous methods.

task-2150561

closes odoo#42316

Signed-off-by: Simon Lejeune (sle) <[email protected]>
  • Loading branch information
fw-bot authored and sle-odoo committed Dec 23, 2019
1 parent 6ddf7a5 commit e72ae1a
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 57 deletions.
127 changes: 89 additions & 38 deletions addons/stock/models/stock_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,15 +182,14 @@ def onchange_product(self):
if self.product_id:
self.description_picking = self.product_id._get_description(self.picking_type_id)

@api.depends('has_tracking', 'picking_type_id.use_create_lots', 'picking_type_id.use_existing_lots', 'picking_type_id.show_reserved', 'picking_type_id.show_operations')
@api.depends('has_tracking', 'picking_type_id.use_create_lots', 'picking_type_id.use_existing_lots', 'state')
def _compute_display_assign_serial(self):
for move in self:
move.display_assign_serial = (
move.has_tracking == 'serial' and
move.state in ('partially_available', 'assigned', 'confirmed') and
move.picking_type_id.use_create_lots and
not move.picking_type_id.use_existing_lots and
not move.picking_type_id.show_reserved
not move.picking_type_id.use_existing_lots
)

@api.depends('picking_id.is_locked')
Expand Down Expand Up @@ -641,23 +640,14 @@ def _generate_serial_numbers(self, next_serial_count=False):
suffix = splitted[1]
initial_number = int(initial_number)

move_lines_commands = []
location_dest = self.location_dest_id._get_putaway_strategy(self.product_id) or self.location_dest_id
lot_names = []
for i in range(0, next_serial_count):
lot_name = '%s%s%s' % (
lot_names.append('%s%s%s' % (
prefix,
str(initial_number + i).zfill(padding),
suffix
)
move_lines_commands.append((0, 0, {
'lot_name': lot_name,
'qty_done': 1,
'product_id': self.product_id.id,
'product_uom_id': self.product_id.uom_id.id,
'location_id': self.location_id.id,
'location_dest_id': location_dest.id,
'picking_id': self.picking_id.id,
}))
))
move_lines_commands = self._generate_serial_move_line_commands(lot_names)
self.write({'move_line_ids': move_lines_commands})
return True

Expand Down Expand Up @@ -816,31 +806,34 @@ def onchange_product_id(self):
self.name = product.partner_ref
self.product_uom = product.uom_id.id

@api.onchange('move_line_nosuggest_ids')
def onchange_move_line_nosuggest_ids(self):
@api.onchange('move_line_ids', 'move_line_nosuggest_ids')
def onchange_move_line_ids(self):
if not self.picking_type_id.use_create_lots:
# This onchange manages the creation of multiple lot name. We don't
# need that if the picking type disallows the creation of new lots.
return

breaking_char = '\n'
move_lines_to_create = []
for move_line in self.move_line_nosuggest_ids:
if self.picking_type_id.show_reserved:
move_lines = self.move_line_ids
else:
move_lines = self.move_line_nosuggest_ids

for move_line in move_lines:
# Look if the `lot_name` contains multiple values.
if breaking_char in (move_line.lot_name or ''):
splitted_lines = move_line.lot_name.split(breaking_char)
splitted_lines = list(filter(lambda line: line, splitted_lines))
move_line.lot_name = splitted_lines[0]
# For each SN line, set move ine data...
for line in splitted_lines[1:]:
move_line_data = {
'lot_name': line,
'qty_done': 1,
'product_id': move_line.product_id.id,
'product_uom_id': move_line.product_id.uom_id.id,
'location_id': move_line.location_id.id,
'location_dest_id': move_line.location_dest_id.id,
'owner_id': move_line.owner_id or False,
'package_id': move_line.package_id or False,
}
move_lines_to_create += [(0, 0, move_line_data)]
# ... then create these move lines.
self.update({'move_line_nosuggest_ids': move_lines_to_create})
split_lines = move_line.lot_name.split(breaking_char)
split_lines = list(filter(None, split_lines))
move_line.lot_name = split_lines[0]
move_lines_commands = self._generate_serial_move_line_commands(
split_lines[1:],
origin_move_line=move_line,
)
if self.picking_type_id.show_reserved:
self.update({'move_line_ids': move_lines_commands})
else:
self.update({'move_line_nosuggest_ids': move_lines_commands})
break

@api.onchange('product_uom')
def onchange_product_uom(self):
Expand Down Expand Up @@ -904,6 +897,64 @@ def _assign_picking(self):
def _assign_picking_post_process(self, new=False):
pass

def _generate_serial_move_line_commands(self, lot_names, origin_move_line=None):
"""Return a list of commands to update the move lines (write on
existing ones or create new ones).
Called when user want to create and assign multiple serial numbers in
one time (using the button/wizard or copy-paste a list in the field).
:param lot_names: A list containing all serial number to assign.
:type lot_names: list
:param origin_move_line: A move line to duplicate the value from, default to None
:type origin_move_line: record of :class:`stock.move.line`
:return: A list of commands to create/update :class:`stock.move.line`
:rtype: list
"""
self.ensure_one()

# Select the right move lines depending of the picking type configuration.
move_lines = self.env['stock.move.line']
if self.picking_type_id.show_reserved:
move_lines = self.move_line_ids.filtered(lambda ml: not ml.lot_id and not ml.lot_name)
else:
move_lines = self.move_line_nosuggest_ids.filtered(lambda ml: not ml.lot_id and not ml.lot_name)

if origin_move_line:
location_dest = origin_move_line.location_dest_id
else:
location_dest = self.location_dest_id._get_putaway_strategy(self.product_id)
move_line_vals = {
'location_dest_id': location_dest.id or self.location_dest_id.id,
'location_id': self.location_id.id,
'product_id': self.product_id.id,
'product_uom_id': self.product_id.uom_id.id,
'qty_done': 1,
}
if origin_move_line:
# `owner_id` and `package_id` are taken only in the case we create
# new move lines from an existing move line. Also, updates the
# `qty_done` because it could be usefull for products tracked by lot.
move_line_vals.update({
'owner_id': origin_move_line.owner_id.id,
'package_id': origin_move_line.package_id.id,
'qty_done': origin_move_line.qty_done or 1,
})

move_lines_commands = []
for lot_name in lot_names:
# We write the lot name on an existing move line (if we have still one)...
if move_lines:
move_lines_commands.append((1, move_lines[0].id, {
'lot_name': lot_name,
'qty_done': 1,
}))
move_lines = move_lines[1:]
# ... or create a new move line with the serial name.
else:
move_line_cmd = dict(move_line_vals, lot_name=lot_name)
move_lines_commands.append((0, 0, move_line_cmd))
return move_lines_commands

def _get_new_picking_values(self):
""" return create values for new picking that will be linked with group
of moves in self.
Expand Down
16 changes: 13 additions & 3 deletions addons/stock/tests/test_generate_serial_numbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@ def setUpClass(cls):
})
cls.uom_unit = cls.env.ref('uom.product_uom_unit')

warehouse = cls.env['stock.warehouse'].create({
cls.warehouse = cls.env['stock.warehouse'].create({
'name': 'Base Warehouse',
'reception_steps': 'one_step',
'delivery_steps': 'ship_only',
'code': 'BWH'
})
cls.location = cls.env['stock.location'].create({
'name': 'Room A',
'location_id': warehouse.lot_stock_id.id,
'location_id': cls.warehouse.lot_stock_id.id,
})
cls.location_dest = cls.env['stock.location'].create({
'name': 'Room B',
'location_id': warehouse.lot_stock_id.id,
'location_id': cls.warehouse.lot_stock_id.id,
})

cls.Wizard = cls.env['stock.assign.serial']
Expand Down Expand Up @@ -275,7 +275,12 @@ def test_set_multiple_lot_name_01(self):
has five new move lines with the right `lot_name`.
"""
nbre_of_lines = 10
picking_type = self.env['stock.picking.type'].search([
('use_create_lots', '=', True),
('warehouse_id', '=', self.warehouse.id)
])
move = self.get_new_move(nbre_of_lines)
move.picking_type_id = picking_type
# We must begin with a move with 10 move lines.
self.assertEqual(len(move.move_line_ids), nbre_of_lines)

Expand Down Expand Up @@ -307,7 +312,12 @@ def test_set_multiple_lot_name_02_empty_values(self):
been correctly set.
"""
nbre_of_lines = 5
picking_type = self.env['stock.picking.type'].search([
('use_create_lots', '=', True),
('warehouse_id', '=', self.warehouse.id)
])
move = self.get_new_move(nbre_of_lines)
move.picking_type_id = picking_type
# We must begin with a move with five move lines.
self.assertEqual(len(move.move_line_ids), nbre_of_lines)

Expand Down
21 changes: 5 additions & 16 deletions addons/stock/views/stock_move_views.xml
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@
<field name="inherit_id" ref="stock.view_stock_move_operations"/>
<field name="arch" type="xml">
<field name="move_line_ids" position="replace">
<field name="move_line_nosuggest_ids" attrs="{'readonly': [('state', 'in', ('done', 'cancel'))]}" context="{'tree_view_ref': 'stock.view_stock_move_line_operation_tree_lot_name_as_textarea','default_picking_id': picking_id, 'default_move_id': id, 'default_product_id': product_id, 'default_location_id': location_id, 'default_location_dest_id': location_dest_id, 'default_company_id': company_id}"/>
<field name="move_line_nosuggest_ids" attrs="{'readonly': [('state', 'in', ('done', 'cancel'))]}" context="{'tree_view_ref': 'stock.view_stock_move_line_operation_tree','default_picking_id': picking_id, 'default_move_id': id, 'default_product_id': product_id, 'default_location_id': location_id, 'default_location_dest_id': location_dest_id, 'default_company_id': company_id}"/>
</field>
</field>
</record>
Expand Down Expand Up @@ -257,7 +257,10 @@
'default_product_id': parent.product_id,
}"
/>
<field name="lot_name" attrs="{'readonly': ['&amp;', ('package_level_id', '!=', False), ('parent.picking_type_entire_packs', '=', True)]}" invisible="not context.get('show_lots_text')" groups="stock.group_production_lot"/>
<field name="lot_name" widget="text" groups="stock.group_production_lot"
placeholder="Write your SN/LN one by one or copy paste a list."
attrs="{'readonly': ['&amp;', ('package_level_id', '!=', False), ('parent.picking_type_entire_packs', '=', True)]}"
invisible="not context.get('show_lots_text')"/>
<field name="package_id" attrs="{'readonly': ['&amp;', ('package_level_id', '!=', False), ('parent.picking_type_entire_packs', '=', True)]}" invisible="not context.get('show_package')" groups="stock.group_tracking_lot"/>
<field name="result_package_id" attrs="{'readonly': ['&amp;', ('package_level_id', '!=', False), ('parent.picking_type_entire_packs', '=', True)]}" groups="stock.group_tracking_lot"/>
<field name="owner_id" attrs="{'readonly': ['&amp;', ('package_level_id', '!=', False), ('parent.picking_type_entire_packs', '=', True)]}" invisible="not context.get('show_owner')" groups="stock.group_tracking_owner"/>
Expand Down Expand Up @@ -300,20 +303,6 @@
</field>
</record>

<record id="view_stock_move_line_operation_tree_lot_name_as_textarea" model="ir.ui.view">
<field name="name">stock.move.line.operations.tree</field>
<field name="model">stock.move.line</field>
<field name="priority">1000</field>
<field name="mode">primary</field>
<field name="inherit_id" ref="stock.view_stock_move_line_operation_tree"/>
<field name="arch" type="xml">
<field name="lot_name" position="attributes">
<attribute name="widget">text</attribute>
<attribute name="placeholder">Write your SN/LN one by one or copy paste a list.</attribute>
</field>
</field>
</record>

<record id="view_move_form" model="ir.ui.view">
<field name="name">stock.move.form</field>
<field name="model">stock.move</field>
Expand Down

0 comments on commit e72ae1a

Please sign in to comment.