[FIX] mrp_subcontracting: fix origin moves on backorder

This commit adapts commits d4abaafa4757ace22010be49bb1f7ce1c4fbfc0d and
7a78839ca6cf45ddec7adb59051da132e0ebceb4

The commits above try to split the origin moves during a split
(backorder). The issues was the split move lost its correct origin move
and the backorder move had all the origin moves linked to it.

HOW TO REPRODUCE:
- Create product FNS (storable)
- Create subcontracted BoM for FNS
- On Operation type 'Receipt', set Show Detailed Operations = True and
  Pre-fill Detailed Operations = True
- Create PO for 10 units of FNS -> Confirm
- Go to Receipt > Detailed operation > Set quantity = 1 > Validate (with
  backorder)
- Repeat step above on the created backorder

OR

- Create storable product FNS tracked by serial number
- Create subcontracting BoM, with strict consumption
- Create PO for 10 units of FNS -> Confirm
- Open detailed operation, add 2 lines with SN, confirm, Validate &
  create backorder
- Redo the same step with backorder receipt

OPW-3838250 OPW-3812937

closes odoo/odoo#163553

X-original-commit: db78c0bdd4a50e094f8867b58b651e06a8822a8a
Signed-off-by: William Henrotin (whe) <whe@odoo.com>
Signed-off-by: David Fesquet (dafr) <dafr@odoo.com>
This commit is contained in:
David (dafr)
2024-04-26 18:54:37 +00:00
parent b9491abd1f
commit 42c51588cd
2 changed files with 60 additions and 10 deletions
@@ -276,19 +276,9 @@ class StockMove(models.Model):
def _prepare_move_split_vals(self, qty):
vals = super(StockMove, self)._prepare_move_split_vals(qty)
if self.is_subcontract:
vals['move_orig_ids'] = [] if not self.move_orig_ids else [(4, self.move_orig_ids[-1].id)]
vals['location_id'] = self.location_id.id
return vals
def _split(self, qty, restrict_partner_id=False):
self.ensure_one()
new_move_vals = super()._split(qty=qty, restrict_partner_id=restrict_partner_id)
# Update the origin moves to remove the split one
if self.move_orig_ids and self.is_subcontract:
self.move_orig_ids = (self.move_orig_ids - self.move_orig_ids[-1]).ids
return new_move_vals
def _should_bypass_reservation(self, forced_location=False):
""" If the move is subcontracted then ignore the reservation. """
should_bypass_reservation = super()._should_bypass_reservation(forced_location=forced_location)
@@ -709,6 +709,66 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
self.assertEqual(self.env['mrp.production'].search_count([('bom_id', '=', bom.id)]), 3)
def test_several_backorders_2(self):
# This test ensure that the backorders finished moves are correctly made (Production -> Subcontracting -> Stock)
# When the receipt is done, the Subcontracting location should have 0 quantity of the finished product.
# In more detail, this test checks that everything is done correctly
# when the quantity of the backorder is set on the stock.move.line instead of the stock.move,
# it can for example happens if the finished product is tracked by Serial Number.
def process_picking_with_backorder(picking, qty):
# Process the picking by putting the given quantity on the stock.move.line
picking.move_line_ids.ensure_one().qty_done = qty
action = picking.button_validate()
if isinstance(action, dict):
wizard = Form(self.env[action['res_model']].with_context(action['context'])).save()
wizard.process()
return picking.backorder_ids
def check_quants(product, stock_qty, sub_qty, prod_qty):
# Check the quantities of the Stock, Subcontracting and Production locations for the given product
subcontracting_location = self.env.company.subcontracting_location_id
production_location = product.property_stock_production
stock_location = self.env.ref('stock.stock_location_stock')
self.assertEqual(sub_qty, self.env['stock.quant']._gather(product, subcontracting_location).quantity)
self.assertEqual(stock_qty, self.env['stock.quant']._gather(product, stock_location).quantity)
self.assertEqual(prod_qty, self.env['stock.quant']._gather(product, production_location).quantity)
in_pck_type = self.env.ref('stock.picking_type_in')
in_pck_type.write({'show_operations': True, 'show_reserved': True})
finished = self.env['product.product'].create({'name': 'Finished Product', 'type': 'product'})
component = self.env['product.product'].create([{'name': 'Component', 'type': 'product'}])
self.env['mrp.bom'].create({
'product_tmpl_id': finished.product_tmpl_id.id,
'product_qty': 1.0,
'type': 'subcontract',
'subcontractor_ids': [(4, self.subcontractor_partner1.id)],
'bom_line_ids': [(0, 0, {'product_id': component.id, 'product_qty': 1.0})],
})
picking_form = Form(self.env['stock.picking'])
picking_form.picking_type_id = in_pck_type
picking_form.partner_id = self.subcontractor_partner1
with picking_form.move_ids_without_package.new() as move:
move.product_id = finished
move.product_uom_qty = 6
picking = picking_form.save()
picking.action_confirm()
backorder01 = process_picking_with_backorder(picking, 1)
check_quants(product=finished, stock_qty=1, sub_qty=0, prod_qty=-1)
check_quants(product=component, stock_qty=0, sub_qty=-1, prod_qty=1)
backorder02 = process_picking_with_backorder(backorder01, 2)
check_quants(product=finished, stock_qty=3, sub_qty=0, prod_qty=-3)
check_quants(product=component, stock_qty=0, sub_qty=-3, prod_qty=3)
process_picking_with_backorder(backorder02, 3)
check_quants(product=finished, stock_qty=6, sub_qty=0, prod_qty=-6)
check_quants(product=component, stock_qty=0, sub_qty=-6, prod_qty=6)
def test_subcontracting_rules_replication(self):
""" Test activate/archive subcontracting location rules."""
reference_location_rules = self.env['stock.rule'].search(['|', ('location_src_id', '=', self.env.company.subcontracting_location_id.id), ('location_dest_id', '=', self.env.company.subcontracting_location_id.id)])