[IMP] mrp: improve Produce All

Currently, if the user change qty_done on any raw moves without
changing qty_producing on the MO, it's not possible to validate the
order. In this commit, we make it possible to validate the order as an
immediate production.

When qty_producing is 0 and validate the MO:
1. manual consumption moves: current consumed qty will be kept when
process the MO.
2. non-manual consumption moves: For tracked components, if
use_auto_consume_compoents_lots checked, and there is enough products
reserved, the qty_done will be filled, otherwise, an error will be
raise to ask user to fill in lot/sn. For non-tracked components,
qty_done will be filled.

Task-3116125

Part-of: odoo/odoo#113538
This commit is contained in:
yhu-odoo
2023-03-01 17:01:17 +01:00
parent ba7bf97444
commit 9c5e80fb32
5 changed files with 39 additions and 24 deletions
+8 -17
View File
@@ -1823,24 +1823,19 @@ class MrpProduction(models.Model):
return action
def _pre_button_mark_done(self):
immediate_productions = self.browse()
for production in self:
if not float_is_zero(production.qty_producing, precision_rounding=production.product_uom_id.rounding) or any(
not float_is_zero(ml.qty_done, precision_digits=ml.product_uom_id.rounding) for
ml in production.move_raw_ids.move_line_ids.filtered(lambda m: m.state not in ('done', 'cancel'))):
immediate_productions |= production
continue
production._set_quantities()
if float_is_zero(production.qty_producing, precision_rounding=production.product_uom_id.rounding):
production._set_quantities()
for production in immediate_productions:
for production in self:
if float_is_zero(production.qty_producing, precision_rounding=production.product_uom_id.rounding):
raise UserError(_('The quantity to produce must be positive!'))
if production.move_raw_ids and not any(production.move_raw_ids.mapped('quantity_done')):
raise UserError(_("You must indicate a non-zero amount consumed for at least one of your components"))
consumption_issues = immediate_productions._get_consumption_issues()
consumption_issues = self._get_consumption_issues()
if consumption_issues:
return immediate_productions._action_generate_consumption_wizard(consumption_issues)
return self._action_generate_consumption_wizard(consumption_issues)
quantity_issues = self._get_quantity_produced_issues()
if quantity_issues:
@@ -2212,15 +2207,11 @@ class MrpProduction(models.Model):
else:
self.qty_producing = self.product_qty - self.qty_produced
self._set_qty_producing()
for move in self.move_raw_ids.filtered(lambda m: m.state not in ['done', 'cancel']):
rounding = move.product_uom.rounding
for move_line in move.move_line_ids:
if move_line.reserved_uom_qty:
move_line.qty_done = min(move_line.reserved_uom_qty, move_line.move_id.should_consume_qty)
if float_compare(move.quantity_done, move.should_consume_qty, precision_rounding=rounding) >= 0:
break
if float_compare(move.product_uom_qty, move.quantity_done, precision_rounding=move.product_uom.rounding) == 1:
if move.has_tracking in ('serial', 'lot'):
if move.manual_consumption:
if move.has_tracking in ('serial', 'lot') and float_is_zero(move.quantity_done, precision_rounding=rounding):
missing_lot_id_products += "\n - %s" % move.product_id.display_name
if missing_lot_id_products:
error_msg = _('You need to supply Lot/Serial Number for products:') + missing_lot_id_products
@@ -1,7 +1,7 @@
import copy
from odoo.exceptions import UserError
from odoo.tests import common, Form
from odoo.tests import common
class TestConsumeTrackedComponentCommon(common.TransactionCase):
@@ -196,6 +196,7 @@ class TestConsumeTrackedComponentCommon(common.TransactionCase):
isSerial = tracking == 'serial'
isAvailable = all(move.state == 'assigned' for move in mrp_productions.move_raw_ids)
isComponentTracking = any(move.has_tracking != 'none' for move in mrp_productions.move_raw_ids)
countOk = True
length = len(mrp_productions)
@@ -220,4 +221,13 @@ class TestConsumeTrackedComponentCommon(common.TransactionCase):
i += 1
if isAvailable:
mrp_productions[i].button_mark_done()
error = False
try:
mrp_productions[i].button_mark_done()
except UserError:
error = True
if isComponentTracking and not mrp_productions[i].picking_type_id.use_auto_consume_components_lots:
self.assertTrue(error, "Immediate Production shall raise an error when tracked product are not provided.")
else:
self.assertFalse(error, "Immediate Production shall not raise an error.")
+12 -5
View File
@@ -1496,18 +1496,22 @@ class TestMrpOrder(TestMrpCommon):
self.assertEqual(mo.move_finished_ids.quantity_done, 1)
def test_immediate_validate_2(self):
""" In a production with a single available move raw, clicking on mark as done after filling quantity
for a stock move only will trigger an error as qty_producing is left to 0."""
""" In a production non-tracked components, clicking on mark as done after filling quantity for a move be
processed as immediate production. The manual filling quantity will be kept."""
mo, bom, p_final, p1, p2 = self.generate_mo(qty_final=1, qty_base_1=1, qty_base_2=1)
self.env['stock.quant']._update_available_quantity(p1, self.stock_location_components, 5.0)
self.env['stock.quant']._update_available_quantity(p2, self.stock_location_components, 5.0)
mo.action_assign()
details_operation_form = Form(mo.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations'))
with details_operation_form.move_line_ids.new() as ml:
ml.qty_done = 1
ml.qty_done = 2
details_operation_form.save()
with self.assertRaises(UserError):
res_dict = mo.button_mark_done()
mo.move_raw_ids.filtered('quantity_done').manual_consumption = True # this should be set automatically in front end
mo.button_mark_done()
self.assertEqual(mo.move_raw_ids.mapped('state'), ['done', 'done'])
self.assertEqual(mo.move_raw_ids.mapped('quantity_done'), [2, 1])
self.assertEqual(mo.move_finished_ids.state, 'done')
self.assertEqual(mo.move_finished_ids.quantity_done, 1)
def test_immediate_validate_3(self):
""" In a production with a serial number tracked product. Check that the immediate production only creates
@@ -1753,6 +1757,9 @@ class TestMrpOrder(TestMrpCommon):
caused by decimal rounding conversions.
"""
picking_type = self.env['stock.picking.type'].search([('code', '=', 'mrp_operation')])[0]
picking_type.use_auto_consume_components_lots = True
# the overall decimal accuracy is set to 3 digits
precision = self.env.ref('product.decimal_product_uom')
precision.digits = 3
+2
View File
@@ -376,6 +376,8 @@ class TestTraceability(TestMrpCommon):
the user can produce several productA and can then produce some productB again
"""
stock_location = self.env.ref('stock.stock_location_stock')
picking_type = self.env['stock.picking.type'].search([('code', '=', 'mrp_operation')])[0]
picking_type.use_auto_consume_components_lots = True
productA, productB, productC = self.env['product.product'].create([{
'name': 'Product A',
@@ -12,6 +12,8 @@ class TestRepairTraceability(TestMrpCommon):
Test that removing a tracked component with a repair does not block the flow of using that component in another
bom
"""
picking_type = self.env['stock.picking.type'].search([('code', '=', 'mrp_operation')])[0]
picking_type.use_auto_consume_components_lots = True
product_to_repair = self.env['product.product'].create({
'name': 'product first serial to act repair',
'tracking': 'serial',
@@ -93,6 +95,9 @@ class TestRepairTraceability(TestMrpCommon):
mo.button_mark_done()
return mo
picking_type = self.env['stock.picking.type'].search([('code', '=', 'mrp_operation')])[0]
picking_type.use_auto_consume_components_lots = True
stock_location = self.env.ref('stock.stock_location_stock')
finished, component = self.env['product.product'].create([{