[FIX] mrp_subcontracting: validate a partial picking

When validating a receipt, if the product is subcontracted and if the
picking is not fully done, it will be impossible to create a backorder

To reproduce the issue:
1. Create two products P_compo, P_finished
    - Both storable
    - Both tracked by lot
    - P_compo must have the route "Resupply Subcontractor on Order"
2. Update P_compo's quantity: 4
3. Create a BoM:
    - Product: P_finished
    - BoM type: Subcontracting
    - Subcontractors: a partner P
    - Components: 1 x P_compo
4. In Inventory, create a planned transfer T:
    - Operation Type: Receipt
    - Receive From: P
    - Operations: 4 x P_finished
5. Mark as Todo
6. Inventory > Delivery Orders, find the delivery of P_compo for P and
process it
7. Back to T, Record Components:
    - Quantity: 3/4
    - Set a Lot for P_finished
8. Validate T, Create Backorder

Error: a User Error is raised "You need to supply a Lot/Serial Number
for product: - P_finished"

Since P_finished is subcontracted, a related MO has been generated. On
step 7, when recording the used components, since all P_finished have
not been produced, a second MO is created for the last P_finished.
However, when validating T, both MOs are selected and validated. This is
an error since the user has not yet recorded the used components for the
last MO. The latter should not be validated.

OPW-2582538

closes odoo/odoo#78496

X-original-commit: 21068076b4a6b134f5f07c4c03de7c4eb37b4688
Signed-off-by: William Henrotin <Whenrow@users.noreply.github.com>
This commit is contained in:
Adrien Widart
2021-10-18 07:56:30 +00:00
committed by William Henrotin
parent bac2a86ce2
commit 67e19a5dc7
2 changed files with 76 additions and 2 deletions
@@ -62,7 +62,7 @@ class MrpProduction(models.Model):
backorder._set_qty_producing()
self.product_qty = self.qty_producing
action = self._get_subcontract_move()._action_record_components()
action = self._get_subcontract_move().filtered(lambda m: m.state not in ('done', 'cancel'))._action_record_components()
action['res_id'] = backorder.id
return action
return {'type': 'ir.actions.act_window_close'}
@@ -75,7 +75,7 @@ class MrpProduction(models.Model):
def _update_finished_move(self):
""" After producing, set the move line on the subcontract picking. """
self.ensure_one()
subcontract_move_id = self._get_subcontract_move()
subcontract_move_id = self._get_subcontract_move().filtered(lambda m: m.state not in ('done', 'cancel'))
if subcontract_move_id:
quantity = self.qty_producing
if self.lot_producing_id:
@@ -141,6 +141,8 @@ class MrpProduction(models.Model):
return False
if not all(line.lot_id for line in mo.move_raw_ids.filtered(lambda sm: sm.has_tracking != 'none').move_line_ids):
return False
if mo.product_id.tracking != 'none' and not mo.lot_producing_id:
return False
return True
return self.filtered(filter_in)
@@ -836,3 +836,75 @@ class TestSubcontractingTracking(TransactionCase):
self.assertEqual(avail_qty_comp1, -todo_nb)
self.assertEqual(avail_qty_comp2, -todo_nb)
self.assertEqual(avail_qty_finished, todo_nb)
def test_flow_tracked_backorder02(self):
""" Both component and finished product are tracked by lot. """
todo_nb = 4
resupply_sub_on_order_route = self.env['stock.location.route'].search([('name', '=', 'Resupply Subcontractor on Order')])
finished_product, component = self.env['product.product'].create([{
'name': 'SuperProduct',
'type': 'product',
'tracking': 'lot',
}, {
'name': 'Component',
'type': 'product',
'tracking': 'lot',
'route_ids': [(4, resupply_sub_on_order_route.id)],
}])
bom_form = Form(self.env['mrp.bom'])
bom_form.type = 'subcontract'
bom_form.subcontractor_ids.add(self.subcontractor_partner1)
bom_form.product_tmpl_id = finished_product.product_tmpl_id
with bom_form.bom_line_ids.new() as bom_line:
bom_line.product_id = component
bom_line.product_qty = 1
bom = bom_form.save()
finished_lot, component_lot = self.env['stock.production.lot'].create([{
'name': 'lot_%s' % product.name,
'product_id': product.id,
'company_id': self.env.company.id,
} for product in [finished_product, component]])
self.env['stock.quant']._update_available_quantity(component, self.env.ref('stock.stock_location_stock'), todo_nb, lot_id=component_lot)
# Create a receipt picking from the subcontractor
picking_form = Form(self.env['stock.picking'])
picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
picking_form.partner_id = self.subcontractor_partner1
with picking_form.move_ids_without_package.new() as move:
move.product_id = finished_product
move.product_uom_qty = todo_nb
picking_receipt = picking_form.save()
picking_receipt.action_confirm()
mo = self.env['mrp.production'].search([('bom_id', '=', bom.id)])
# Process the delivery of the components
compo_picking = mo.picking_ids
compo_picking.action_assign()
wizard_data = compo_picking.button_validate()
wizard = Form(self.env[wizard_data['res_model']].with_context(wizard_data['context'])).save()
wizard.process()
for qty in [3, 1]:
# Record the receiption of <qty> finished products
picking_receipt = self.env['stock.picking'].search([('partner_id', '=', self.subcontractor_partner1.id), ('state', '!=', 'done')])
action = picking_receipt.action_record_components()
mo = self.env['mrp.production'].browse(action['res_id'])
mo_form = Form(mo.with_context(**action['context']), view=action['view_id'])
mo_form.qty_producing = qty
mo_form.lot_producing_id = finished_lot
with mo_form.move_line_raw_ids.edit(0) as ml:
ml.lot_id = component_lot
mo = mo_form.save()
mo.subcontracting_record_component()
# Validate the picking and create a backorder
wizard_data = picking_receipt.button_validate()
if qty == 3:
wizard = Form(self.env[wizard_data['res_model']].with_context(wizard_data['context'])).save()
wizard.process()
self.assertEqual(picking_receipt.state, 'done')