[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:
@@ -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.")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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([{
|
||||
|
||||
Reference in New Issue
Block a user