[FIX] mrp_subcontracting: decrease done quantity

To reproduce the issue:
1. Create a subcontracted product P
2. Create and confirm a planned transfer with 10 x P
3. Set the done quantity to 8
4. Set the done quantity to 7
5. Open the detailed operation (i.e., the MO)

Error: The producing quantity of the MO is 1, it should be 3

When setting a smaller done quantity than the demand (step 8), we
update the expected quantity of the related MO, we set its producing
quantity, and we create a backorder.

When decreasing the done quantity, we update/cancel the related
productions. But here is the issue: in the above case, we simply
decrease the expected quantity of the backorder, so we just lose the
cancelled quantity, the user "can't" produce this quantity anymore.

In such situation (when decreasing the done quantity), we should
consider the last production (i.e., the last backorder) as the WIP one.
So, we should update/cancel the other productions and increase the
expected quantity of this last backorder, so the user can produce it
again.

OPW-3557632

closes odoo/odoo#142513

Signed-off-by: William Henrotin (whe) <whe@odoo.com>
Signed-off-by: Quentin Wolfs (quwo) <quwo@odoo.com>
This commit is contained in:
Adrien Widart (awt)
2023-11-22 23:58:13 +00:00
committed by clesgow
parent 803237a9bf
commit ef604945d8
2 changed files with 80 additions and 2 deletions
+11 -2
View File
@@ -77,7 +77,7 @@ class StockMove(models.Model):
move._auto_record_components(delta_qty)
to_set_moves -= move
elif float_compare(delta_qty, 0, precision_rounding=move.product_uom.rounding) < 0:
move._reduce_subcontract_order_qty(abs(delta_qty))
move.with_context(transfer_qty=True)._reduce_subcontract_order_qty(abs(delta_qty))
if to_set_moves:
super(StockMove, to_set_moves)._set_quantity()
@@ -295,8 +295,17 @@ class StockMove(models.Model):
def _reduce_subcontract_order_qty(self, quantity_to_remove):
self.ensure_one()
productions = self.move_orig_ids.production_id.filtered(lambda p: p.state not in ('done', 'cancel'))[::-1]
wip_production = productions[0] if self._context.get('transfer_qty') and len(productions) > 1 else self.env['mrp.production']
# Transfer removed qty to WIP production
if wip_production:
self.env['change.production.qty'].with_context(skip_activity=True).create({
'mo_id': wip_production.id,
'product_qty': wip_production.product_uom_qty + quantity_to_remove
}).change_prod_qty()
# Cancel productions until reach new_quantity
for production in productions:
for production in (productions - wip_production):
if quantity_to_remove >= production.product_qty:
quantity_to_remove -= production.product_qty
production.with_context(skip_activity=True).action_cancel()
@@ -837,6 +837,75 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
{'product_qty': 3, 'state': 'cancel'},
])
def test_decrease_quantity_done(self):
self.bom.consumption = 'flexible'
supplier_location = self.env.ref('stock.stock_location_suppliers')
receipt = self.env['stock.picking'].create({
'partner_id': self.subcontractor_partner1.id,
'location_id': supplier_location.id,
'location_dest_id': self.warehouse.lot_stock_id.id,
'picking_type_id': self.warehouse.in_type_id.id,
'move_ids': [(0, 0, {
'name': self.finished.name,
'product_id': self.finished.id,
'product_uom_qty': 10.0,
'product_uom': self.finished.uom_id.id,
'location_id': supplier_location.id,
'location_dest_id': self.warehouse.lot_stock_id.id,
})],
})
receipt.action_confirm()
productions = self.env['mrp.production'].search([('product_id', '=', self.finished.id)], order='id')
self.assertRecordValues(productions, [
{'qty_producing': 0.0, 'product_qty': 10.0, 'state': 'confirmed'},
])
receipt.move_ids.quantity = 6
productions = self.env['mrp.production'].search([('product_id', '=', self.finished.id)], order='id')
self.assertEqual(receipt.move_ids.product_uom_qty, 10.0, 'Demand should not be impacted')
self.assertRecordValues(productions, [
{'qty_producing': 6.0, 'product_qty': 6.0, 'state': 'to_close'},
{'qty_producing': 4.0, 'product_qty': 4.0, 'state': 'to_close'},
])
receipt.move_ids.quantity = 9
productions = self.env['mrp.production'].search([('product_id', '=', self.finished.id)], order='id')
self.assertEqual(receipt.move_ids.product_uom_qty, 10.0, 'Demand should not be impacted')
self.assertRecordValues(productions, [
{'qty_producing': 6.0, 'product_qty': 6.0, 'state': 'to_close'},
{'qty_producing': 3.0, 'product_qty': 3.0, 'state': 'to_close'},
{'qty_producing': 1.0, 'product_qty': 1.0, 'state': 'to_close'},
])
receipt.move_ids.quantity = 7
productions = self.env['mrp.production'].search([('product_id', '=', self.finished.id)], order='id')
self.assertEqual(receipt.move_ids.product_uom_qty, 10.0, 'Demand should not be impacted')
self.assertRecordValues(productions, [
{'qty_producing': 6.0, 'product_qty': 6.0, 'state': 'to_close'},
{'qty_producing': 1.0, 'product_qty': 1.0, 'state': 'to_close'},
{'qty_producing': 3.0, 'product_qty': 3.0, 'state': 'to_close'},
])
receipt.move_ids.quantity = 4
productions = self.env['mrp.production'].search([('product_id', '=', self.finished.id)], order='id')
self.assertEqual(receipt.move_ids.product_uom_qty, 10.0, 'Demand should not be impacted')
self.assertRecordValues(productions, [
{'qty_producing': 4.0, 'product_qty': 4.0, 'state': 'to_close'},
{'qty_producing': 1.0, 'product_qty': 1.0, 'state': 'cancel'},
{'qty_producing': 6.0, 'product_qty': 6.0, 'state': 'to_close'},
])
receipt.move_ids.quantity = 0
productions = self.env['mrp.production'].search([('product_id', '=', self.finished.id)], order='id')
self.assertEqual(receipt.move_ids.product_uom_qty, 10.0, 'Demand should not be impacted')
self.assertRecordValues(productions, [
{'qty_producing': 4.0, 'product_qty': 4.0, 'state': 'cancel'},
{'qty_producing': 1.0, 'product_qty': 1.0, 'state': 'cancel'},
{'qty_producing': 10.0, 'product_qty': 10.0, 'state': 'to_close'},
])
@tagged('post_install', '-at_install')
class TestSubcontractingTracking(TransactionCase):