[FIX] mrp: delivery slip mixing kits and non kits

Current behaviour:
When you generate a Delivery Slip for a list
of kit and non kit products with no backorder,
kit and non kit products get mixed.
ie: there are kit products in the section
"products not associated with a kit"

Steps to reproduce:
1. Create 4 products (K1,P1,P2,C1,C2)
2. Create a Bills of Materials for K1
3. Set Type as Kit
4. Add C1 and C2 as components
5. Create a sale quotation for K1, P1, P2
6. Set the quantity at 4 for all products
7. On the quotation, click on Delivery
8. In Done, put 4 for P1 and 3 for C1,C2,P2
9. Validate and select No Backorder
10. Click on Print, Delivery Slip
11. In the document:
12. P2 is in the kit section (K1)
13. C1,C2 in the "not associated with a kit" section

Expected behaviour:
Only C1 and C2 should be in the K1 section
Only P1 and P2 should be in the
"Products not associated with a kit" section

opw-3568390

closes odoo/odoo#159138

X-original-commit: 5d61d7023b3b0a5e6a47ad71bb12d548c797b5f9
Signed-off-by: Steve Van Essche <svs@odoo.com>
Signed-off-by: Antoine Demany (ande) <ande@odoo.com>
This commit is contained in:
Antoine (ande)
2024-03-29 12:38:05 +00:00
parent 41e3b1cdc3
commit 6c80d93f07
3 changed files with 111 additions and 26 deletions
+25 -3
View File
@@ -76,6 +76,13 @@ class StockMoveLine(models.Model):
move_line._log_message(production, move_line, 'mrp.track_production_move_template', vals)
return super(StockMoveLine, self).write(vals)
def _get_aggregated_properties(self, move_line=False, move=False):
aggregated_properties = super()._get_aggregated_properties(move_line, move)
bom = aggregated_properties['move'].bom_line_id.bom_id
aggregated_properties['bom'] = bom or False
aggregated_properties['line_key'] += f'_{bom.id if bom else ""}'
return aggregated_properties
def _get_aggregated_product_quantities(self, **kwargs):
"""Returns dictionary of products and corresponding values of interest grouped by optional kit_name
@@ -88,10 +95,25 @@ class StockMoveLine(models.Model):
"""
aggregated_move_lines = super()._get_aggregated_product_quantities(**kwargs)
kit_name = kwargs.get('kit_name')
if kit_name:
for aggregated_move_line in aggregated_move_lines:
if aggregated_move_lines[aggregated_move_line]['description'] == kit_name:
to_be_removed = []
for aggregated_move_line in aggregated_move_lines:
bom = aggregated_move_lines[aggregated_move_line]['bom']
is_phantom = bom.type == 'phantom' if bom else False
if kit_name:
product = bom.product_id or bom.product_tmpl_id if bom else False
display_name = product.display_name if product else False
description = aggregated_move_lines[aggregated_move_line]['description']
if not is_phantom or display_name != kit_name:
to_be_removed.append(aggregated_move_line)
elif description == kit_name:
aggregated_move_lines[aggregated_move_line]['description'] = ""
elif not kwargs and is_phantom:
to_be_removed.append(aggregated_move_line)
for move_line in to_be_removed:
del aggregated_move_lines[move_line]
return aggregated_move_lines
+60
View File
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import common
from odoo import Command
from odoo.exceptions import UserError
from odoo.tests import Form
@@ -440,3 +441,62 @@ class TestKitPicking(common.TestMrpCommon):
{'product_id': product.id, 'quantity': 1, 'state': 'done'},
{'product_id': compo.id, 'quantity': 1, 'state': 'done'},
])
def test_move_line_aggregated_product_quantities_with_kit(self):
""" Test the `stock.move.line` method `_get_aggregated_product_quantities`,
who returns data used to print delivery slips, using kits.
"""
uom_unit = self.env.ref('uom.product_uom_unit')
kit, kit_component_1, kit_component_2, not_kit_1, not_kit_2 = self.env['product.product'].create([{
'name': name,
'type': 'product',
'uom_id': uom_unit.id,
} for name in ['Kit', 'Kit Component 1', 'Kit Component 2', 'Not Kit 1', 'Not Kit 2']])
bom_kit = self.env['mrp.bom'].create({
'product_tmpl_id': kit.product_tmpl_id.id,
'product_uom_id': kit.product_tmpl_id.uom_id.id,
'product_id': kit.id,
'product_qty': 1.0,
'type': 'phantom',
'bom_line_ids': [
Command.create({
'product_id': kit_component_1.id,
'product_qty': 1,
}),
Command.create({
'product_id': kit_component_2.id,
'product_qty': 1,
}),
]
})
delivery_form = Form(self.env['stock.picking'])
delivery_form.picking_type_id = self.env.ref('stock.picking_type_in')
with delivery_form.move_ids_without_package.new() as move:
move.product_id = bom_kit.product_id
move.product_uom_qty = 4
with delivery_form.move_ids_without_package.new() as move:
move.product_id = not_kit_1
move.product_uom_qty = 4
with delivery_form.move_ids_without_package.new() as move:
move.product_id = not_kit_2
move.product_uom_qty = 3
delivery = delivery_form.save()
delivery.action_confirm()
delivery.move_line_ids.filtered(lambda ml: ml.product_id == kit_component_1).quantity = 3
delivery.move_line_ids.filtered(lambda ml: ml.product_id == kit_component_2).quantity = 3
delivery.move_line_ids.filtered(lambda ml: ml.product_id == not_kit_1).quantity = 4
delivery.move_line_ids.filtered(lambda ml: ml.product_id == not_kit_2).quantity = 2
backorder_wizard_dict = delivery.button_validate()
backorder_wizard_form = Form(self.env[backorder_wizard_dict['res_model']].with_context(backorder_wizard_dict['context']))
backorder_wizard_form.save().process_cancel_backorder()
aggregate_not_kit_values = delivery.move_line_ids._get_aggregated_product_quantities()
self.assertEqual(len(aggregate_not_kit_values.keys()), 2)
self.assertTrue(all('Not' in val for val in aggregate_not_kit_values), 'Only non kit products should be included')
aggregate_kit_values = delivery.move_line_ids._get_aggregated_product_quantities(kit_name=bom_kit.product_id.name)
self.assertEqual(len(aggregate_kit_values.keys()), 2)
self.assertTrue(all('Component' in val for val in aggregate_kit_values), 'Only kit products should be included')
+26 -23
View File
@@ -744,6 +744,24 @@ class StockMoveLine(models.Model):
self.env['stock.move.line'].browse(to_unlink_candidate_ids).unlink()
move_to_reassign._action_assign()
def _get_aggregated_properties(self, move_line=False, move=False):
move = move or move_line.move_id
uom = move.product_uom or move_line.product_uom_id
name = move.product_id.display_name
description = move.description_picking
if description == name or description == move.product_id.name:
description = False
product = move.product_id
line_key = f'{product.id}_{product.display_name}_{description or ""}_{uom.id}_{move.product_packaging_id or ""}'
return {
'line_key': line_key,
'name': name,
'description': description,
'product_uom': uom,
'move': move,
'packaging': move.product_packaging_id,
}
def _get_aggregated_product_quantities(self, **kwargs):
""" Returns a dictionary of products (key = id+name+description+uom+packaging) and corresponding values of interest.
@@ -756,17 +774,6 @@ class StockMoveLine(models.Model):
"""
aggregated_move_lines = {}
def get_aggregated_properties(move_line=False, move=False):
move = move or move_line.move_id
uom = move.product_uom or move_line.product_uom_id
name = move.product_id.display_name
description = move.description_picking
if description == name or description == move.product_id.name:
description = False
product = move.product_id
line_key = f'{product.id}_{product.display_name}_{description or ""}_{uom.id}_{move.product_packaging_id or ""}'
return (line_key, name, description, uom, move.product_packaging_id)
def _compute_packaging_qtys(aggregated_move_lines):
# Needs to be computed after aggregation of line qtys
for line in aggregated_move_lines.values():
@@ -785,7 +792,8 @@ class StockMoveLine(models.Model):
for move_line in self:
if kwargs.get('except_package') and move_line.result_package_id:
continue
line_key, name, description, uom, packaging = get_aggregated_properties(move_line=move_line)
aggregated_properties = self._get_aggregated_properties(move_line=move_line)
line_key, uom = aggregated_properties['line_key'], aggregated_properties['product_uom']
quantity = move_line.product_uom_id._compute_quantity(move_line.quantity, uom)
if line_key not in aggregated_move_lines:
qty_ordered = None
@@ -794,22 +802,19 @@ class StockMoveLine(models.Model):
# Filters on the aggregation key (product, description and uom) to add the
# quantities delayed to backorders to retrieve the original ordered qty.
following_move_lines = backorders.move_line_ids.filtered(
lambda ml: get_aggregated_properties(move=ml.move_id)[0] == line_key
lambda ml: self._get_aggregated_properties(move=ml.move_id)['line_key'] == line_key
)
qty_ordered += sum(following_move_lines.move_id.mapped('product_uom_qty'))
# Remove the done quantities of the other move lines of the stock move
previous_move_lines = move_line.move_id.move_line_ids.filtered(
lambda ml: get_aggregated_properties(move=ml.move_id)[0] == line_key and ml.id != move_line.id
lambda ml: self._get_aggregated_properties(move=ml.move_id)['line_key'] == line_key and ml.id != move_line.id
)
qty_ordered -= sum([m.product_uom_id._compute_quantity(m.quantity, uom) for m in previous_move_lines])
aggregated_move_lines[line_key] = {
'name': name,
'description': description,
**aggregated_properties,
'quantity': quantity,
'qty_ordered': qty_ordered or quantity,
'product_uom': uom,
'product': move_line.product_id,
'packaging': packaging,
}
else:
aggregated_move_lines[line_key]['qty_ordered'] += quantity
@@ -824,18 +829,16 @@ class StockMoveLine(models.Model):
if not (empty_move.state == "cancel" and empty_move.product_uom_qty
and float_is_zero(empty_move.quantity, precision_rounding=empty_move.product_uom.rounding)):
continue
line_key, name, description, uom, packaging = get_aggregated_properties(move=empty_move)
aggregated_properties = self._get_aggregated_properties(move=empty_move)
line_key = aggregated_properties['line_key']
if line_key not in aggregated_move_lines:
qty_ordered = empty_move.product_uom_qty
aggregated_move_lines[line_key] = {
'name': name,
'description': description,
**aggregated_properties,
'quantity': False,
'qty_ordered': qty_ordered,
'product_uom': uom,
'product': empty_move.product_id,
'packaging': packaging,
}
else:
aggregated_move_lines[line_key]['qty_ordered'] += empty_move.product_uom_qty