# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from freezegun import freeze_time

from odoo import Command
from odoo.exceptions import AccessError, UserError
from odoo.tests import Form
from odoo.tests.common import TransactionCase
from odoo.addons.mrp_subcontracting.tests.common import TestMrpSubcontractingCommon

from odoo.tests import tagged
from dateutil.relativedelta import relativedelta


@tagged('post_install', '-at_install')
class TestSubcontractingBasic(TransactionCase):
    def test_subcontracting_location_1(self):
        """ Checks the creation and presence of the subcontracting location. """
        self.assertTrue(self.env.company.subcontracting_location_id)
        self.assertTrue(self.env.company.subcontracting_location_id.active)
        company2 = self.env['res.company'].create({'name': 'Test Company'})
        self.assertTrue(company2.subcontracting_location_id)
        self.assertTrue(self.env.company.subcontracting_location_id != company2.subcontracting_location_id)

    def test_duplicating_warehouses_recreates_their_routes_and_operation_types(self):
        """ Duplicating a warehouse should result in the creation of new routes and operation types.
        Not reusing the existing routes and operation types"""
        wh_original = self.env['stock.warehouse'].search([], limit=1)
        wh_copy = wh_original.copy(default={'name': 'Dummy Warehouse (copy)', 'code': 'Dummy'})
        if 'buy_to_resupply' in wh_original._fields:
            # If purchase is installed, the buy route would be reused instead of duplicated.
            wh_original.buy_to_resupply = False
        wh_original.manufacture_to_resupply = False
        # Check if warehouse routes got RECREATED (instead of reused)
        route_types = [
            "pbm_route_id",
            "subcontracting_route_id",
            "reception_route_id",
            "delivery_route_id"
        ]
        for route_type in route_types:
            original_route_set = wh_original[route_type]
            copy_route_set = wh_copy[route_type]
            error_message = f"At least one {route_type} (route) got reused on duplication (should have been recreated)"
            self.assertEqual(len(original_route_set & copy_route_set), 0, error_message)

        # Check if warehouse operation types (picking.type) got RECREATED (instead of reused)
        operation_types = [
            "subcontracting_type_id",
            "subcontracting_resupply_type_id",
            "pick_type_id",
            "pack_type_id",
            "out_type_id",
            "in_type_id",
            "qc_type_id",
            "store_type_id",
            "int_type_id"
        ]
        for operation_type in operation_types:
            original_type_set = wh_original[operation_type]
            copy_type_set = wh_copy[operation_type]
            error_message = f"At least one {operation_type} (operation_type) got reused on duplication (should have been recreated)"
            self.assertEqual(len(original_type_set & copy_type_set), 0, error_message)

    def test_warehouse_subcontracting_resupply_type_code(self):
        """ Assert that default operation code of resupply subcontractors is 'internal'. """
        warehouse = self.env['stock.warehouse'].create({
            'name': 'Warehouse',
            'code': 'MYWH'
        })
        self.assertEqual(warehouse.subcontracting_resupply_type_id.code, 'internal')


@tagged('post_install', '-at_install')
class TestSubcontractingFlows(TestMrpSubcontractingCommon):

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.warehouse = cls.env['stock.warehouse'].create({
            'name': 'Subcontracting Warehouse',
            'code': 'SBC',
        })

    def test_flow_1(self):
        """ Don't tick any route on the components and trigger the creation of the subcontracting
        manufacturing order through a receipt picking. Create a reordering rule in the
        subcontracting locations for a component and run the scheduler to resupply. Checks if the
        resupplying actually works
        """
        # Check subcontracting picking Type
        self.assertTrue(all(self.env['stock.warehouse'].search([]).with_context(active_test=False).mapped('subcontracting_type_id.use_create_components_lots')))
        # 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.new() as move:
            move.product_id = self.finished
            move.product_uom_qty = 1
        picking_receipt = picking_form.save()
        picking_receipt.action_confirm()

        # Nothing should be tracked
        self.assertTrue(all(m.product_uom_qty == m.quantity for m in picking_receipt.move_ids))
        self.assertEqual(picking_receipt.state, 'assigned')

        # Check the created manufacturing order
        mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])
        self.assertEqual(len(mo), 1)
        self.assertEqual(len(mo.picking_ids), 1)
        wh = picking_receipt.picking_type_id.warehouse_id
        self.assertEqual(mo.picking_type_id, wh.subcontracting_type_id)
        self.assertFalse(mo.picking_type_id.active)

        picking_receipt.move_ids.quantity = 1
        picking_receipt.move_ids.picked = True
        picking_receipt.button_validate()
        self.assertEqual(mo.state, 'done')
        self.assertEqual(mo.origin, picking_receipt.name)

        # Available quantities should be negative at the subcontracting location for each components
        avail_qty_comp1 = self.env['stock.quant']._get_available_quantity(self.comp1, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
        avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
        avail_qty_finished = self.env['stock.quant']._get_available_quantity(self.finished, wh.lot_stock_id)
        self.assertEqual(avail_qty_comp1, -1)
        self.assertEqual(avail_qty_comp2, -1)
        self.assertEqual(avail_qty_finished, 1)

        # Ensure returns to subcontractor location
        return_form = Form(self.env['stock.return.picking'].with_context(active_id=picking_receipt.id, active_model='stock.picking'))
        return_wizard = return_form.save()
        return_wizard.product_return_moves.quantity = 1
        return_picking = return_wizard._create_return()
        self.assertEqual(len(return_picking), 1)
        self.assertEqual(return_picking.move_ids.location_dest_id, self.subcontractor_partner1.property_stock_subcontractor)

    def test_flow_2(self):
        """ Tick "Resupply Subcontractor on Order" on the components and trigger the creation of
        the subcontracting manufacturing order through a receipt picking. Checks if the resupplying
        actually works. Also set a different subcontracting location on the partner.
        """
        # Tick "resupply subconractor on order"
        resupply_sub_on_order_route = self.env['stock.route'].search([('name', '=', 'Resupply Subcontractor on Order')])
        (self.comp1 + self.comp2).write({'route_ids': [(4, resupply_sub_on_order_route.id, None)]})
        # Create a different subcontract location for partner
        partner_subcontract_location = self.env['stock.location'].create({
            'name': 'Specific partner location',
            'location_id': self.env.company.subcontracting_location_id.id,
            'usage': 'internal',
            'company_id': self.env.company.id,
        })
        self.subcontractor_partner1.property_stock_subcontractor = partner_subcontract_location.id
        # Add a manufacturing lead time to check that the resupply delivery is correctly planned 2 days
        # before the subcontracting receipt
        self.bom.produce_delay = 2

        # 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.new() as move:
            move.product_id = self.finished
            move.product_uom_qty = 1
            move.quantity = 1
            move.picked = True
        picking_receipt = picking_form.save()

        # Pickings should directly be created
        mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])
        self.assertEqual(len(mo.picking_ids), 1)
        self.assertEqual(mo.state, 'confirmed')
        self.assertEqual(len(mo.picking_ids.move_ids), 2)

        picking = mo.picking_ids
        wh = picking.picking_type_id.warehouse_id

        # The picking should be a delivery order
        self.assertEqual(picking.picking_type_id, wh.subcontracting_resupply_type_id)
        # The date planned should be correct
        self.assertEqual(picking_receipt.scheduled_date, picking.scheduled_date + relativedelta(days=mo.bom_id.produce_delay))

        self.assertEqual(mo.picking_type_id, wh.subcontracting_type_id)
        self.assertFalse(mo.picking_type_id.active)

        # No manufacturing order for `self.comp2`
        comp2mo = self.env['mrp.production'].search([('bom_id', '=', self.comp2_bom.id)])
        self.assertEqual(len(comp2mo), 0)

        picking_receipt.move_ids.quantity = 1
        picking_receipt.move_ids.picked = True
        picking_receipt.button_validate()
        self.assertEqual(mo.state, 'done')
        self.assertEqual(mo.origin, picking_receipt.name)

        # Available quantities should be negative at the subcontracting location for each components
        avail_qty_comp1 = self.env['stock.quant']._get_available_quantity(self.comp1, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
        avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
        avail_qty_finished = self.env['stock.quant']._get_available_quantity(self.finished, wh.lot_stock_id)
        self.assertEqual(avail_qty_comp1, -1)
        self.assertEqual(avail_qty_comp2, -1)
        self.assertEqual(avail_qty_finished, 1)

        avail_qty_comp1_in_global_location = self.env['stock.quant']._get_available_quantity(self.comp1, self.env.company.subcontracting_location_id, allow_negative=True)
        avail_qty_comp2_in_global_location = self.env['stock.quant']._get_available_quantity(self.comp2, self.env.company.subcontracting_location_id, allow_negative=True)
        self.assertEqual(avail_qty_comp1_in_global_location, -1)
        self.assertEqual(avail_qty_comp2_in_global_location, -1)

    def test_flow_3(self):
        """ Tick "Resupply Subcontractor on Order" and "MTO" on the components and trigger the
        creation of the subcontracting manufacturing order through a receipt picking. Checks if the
        resupplying actually works. One of the component has also "manufacture" set and a BOM
        linked. Checks that an MO is created for this one.
        """
        # Tick "resupply subconractor on order"
        resupply_sub_on_order_route = self.env['stock.route'].search([('name', '=', 'Resupply Subcontractor on Order')])
        (self.comp1 + self.comp2).write({'route_ids': [(6, None, [resupply_sub_on_order_route.id])]})

        # Tick "manufacture" and MTO on self.comp2
        mto_route = self.env.ref('stock.route_warehouse0_mto')
        mto_route.active = True
        manufacture_route = self.env['stock.route'].search([('name', '=', 'Manufacture')])
        self.comp2.write({'route_ids': [(4, manufacture_route.id, None)]})
        self.comp2.write({'route_ids': [(4, mto_route.id, None)]})

        # 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.new() as move:
            move.product_id = self.finished
            move.product_uom_qty = 1
            move.quantity = 1
            move.picked = True
        picking_receipt = picking_form.save()
        picking_receipt.action_confirm()

        # Pickings should directly be created
        mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])
        self.assertEqual(mo.state, 'confirmed')

        picking_delivery = mo.picking_ids
        self.assertEqual(len(picking_delivery), 1)
        self.assertEqual(len(picking_delivery.move_ids), 2)
        self.assertEqual(picking_delivery.origin, picking_receipt.name)
        self.assertEqual(picking_delivery.partner_id, picking_receipt.partner_id.parent_id)

        # The picking should be a delivery order
        wh = picking_receipt.picking_type_id.warehouse_id
        self.assertEqual(mo.picking_ids.picking_type_id, wh.subcontracting_resupply_type_id)

        self.assertEqual(mo.picking_type_id, wh.subcontracting_type_id)
        self.assertFalse(mo.picking_type_id.active)

        # As well as a manufacturing order for `self.comp2`
        comp2mo = self.env['mrp.production'].search([('bom_id', '=', self.comp2_bom.id)])
        self.assertEqual(len(comp2mo), 1)
        picking_receipt.move_ids.quantity = 1
        picking_receipt.move_ids.picked = True
        picking_receipt.button_validate()
        self.assertEqual(mo.state, 'done')
        self.assertEqual(mo.origin, picking_receipt.name)

        # Available quantities should be negative at the subcontracting location for each components
        avail_qty_comp1 = self.env['stock.quant']._get_available_quantity(self.comp1, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
        avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
        avail_qty_finished = self.env['stock.quant']._get_available_quantity(self.finished, wh.lot_stock_id)
        self.assertEqual(avail_qty_comp1, -1)
        self.assertEqual(avail_qty_comp2, -1)
        self.assertEqual(avail_qty_finished, 1)

    def test_flow_4(self):
        """ Tick "Manufacture" and "MTO" on the components and trigger the
        creation of the subcontracting manufacturing order through a receipt
        picking. Checks that the delivery and MO for its components are
        automatically created.
        """
        # Required for `location_id` to be visible in the view
        self.env.user.group_ids += self.env.ref('stock.group_stock_multi_locations')
        # Tick "manufacture" and MTO on self.comp2
        mto_route = self.env.ref('stock.route_warehouse0_mto')
        mto_route.active = True
        manufacture_route = self.env['stock.route'].search([('name', '=', 'Manufacture')])
        self.comp2.write({'route_ids': [(6, None, [manufacture_route.id, mto_route.id])]})
        picking_type_in = self.env.ref('stock.picking_type_in')
        self.env.ref('mrp_subcontracting.route_resupply_subcontractor_mto').active = False

        orderpoint_form = Form(self.env['stock.warehouse.orderpoint'])
        orderpoint_form.product_id = self.comp2
        orderpoint_form.product_min_qty = 0.0
        orderpoint_form.product_max_qty = 10.0
        orderpoint_form.location_id = self.env.company.subcontracting_location_id
        orderpoint = orderpoint_form.save()

        # Create a receipt picking from the subcontractor
        picking_form = Form(self.env['stock.picking'])
        picking_form.picking_type_id = picking_type_in
        picking_form.partner_id = self.subcontractor_partner1
        with picking_form.move_ids.new() as move:
            move.product_id = self.finished
            move.product_uom_qty = 1
            move.quantity = 1
            move.picked = True
        picking_receipt = picking_form.save()

        warehouse = picking_receipt.picking_type_id.warehouse_id

        # Pickings should directly be created
        mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])
        self.assertEqual(mo.state, 'confirmed')

        moves = self.env['stock.move'].search([
            ('product_id', '=', self.comp2.id),
            ('location_id', '=', warehouse.lot_stock_id.id),
            ('location_dest_id', '=', self.env.company.subcontracting_location_id.id)
        ])
        self.assertTrue(moves)
        picking_delivery = moves.picking_id
        self.assertTrue(picking_delivery)
        self.assertEqual(sum(moves.mapped('product_uom_qty')), 11.0)

        # As well as a manufacturing order for `self.comp2`
        comp2mo = self.env['mrp.production'].search([('bom_id', '=', self.comp2_bom.id)])
        self.assertEqual(len(comp2mo), 1)

    def test_flow_5(self):
        """ Check that the correct BoM is chosen accordingly to the partner
        """
        # We create a second partner of type subcontractor
        main_partner_2 = self.env['res.partner'].create({'name': 'main_partner'})
        subcontractor_partner2 = self.env['res.partner'].create({
            'name': 'subcontractor_partner',
            'parent_id': main_partner_2.id,
            'company_id': self.env.ref('base.main_company').id
        })

        # We create a different BoM for the same product
        comp3 = self.env['product.product'].create({
            'name': 'Component1',
            'is_storable': True,
        })

        bom_form = Form(self.env['mrp.bom'])
        bom_form.type = 'subcontract'
        bom_form.product_tmpl_id = self.finished.product_tmpl_id
        with bom_form.bom_line_ids.new() as bom_line:
            bom_line.product_id = self.comp1
            bom_line.product_qty = 1
        with bom_form.bom_line_ids.new() as bom_line:
            bom_line.product_id = comp3
            bom_line.product_qty = 1
        bom2 = bom_form.save()

        # We assign the second BoM to the new partner
        self.bom.write({'subcontractor_ids': [(4, self.subcontractor_partner1.id, None)]})
        bom2.write({'subcontractor_ids': [(4, subcontractor_partner2.id, None)]})

        # Create a receipt picking from the subcontractor1
        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.new() as move:
            move.product_id = self.finished
            move.product_uom_qty = 1
            move.quantity = 1
            move.picked = True
        picking_receipt1 = picking_form.save()

        # Create a receipt picking from the subcontractor2
        picking_form = Form(self.env['stock.picking'])
        picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
        picking_form.partner_id = subcontractor_partner2
        with picking_form.move_ids.new() as move:
            move.product_id = self.finished
            move.product_uom_qty = 1
            move.quantity = 1
            move.picked = True
        picking_receipt2 = picking_form.save()

        mo_pick1 = picking_receipt1.move_ids.mapped('move_orig_ids.production_id')
        mo_pick2 = picking_receipt2.move_ids.mapped('move_orig_ids.production_id')
        self.assertEqual(len(mo_pick1), 1)
        self.assertEqual(len(mo_pick2), 1)
        self.assertEqual(mo_pick1.bom_id, self.bom)
        self.assertEqual(mo_pick2.bom_id, bom2)

    def test_flow_6(self):
        """ Extra quantity on the move.
        """
        # We create a second partner of type subcontractor
        main_partner_2 = self.env['res.partner'].create({'name': 'main_partner'})
        subcontractor_partner2 = self.env['res.partner'].create({
            'name': 'subcontractor_partner',
            'parent_id': main_partner_2.id,
            'company_id': self.env.ref('base.main_company').id,
        })
        self.env.invalidate_all()

        # We create a different BoM for the same product
        comp3 = self.env['product.product'].create({
            'name': 'Component3',
            'is_storable': True,
        })

        bom_form = Form(self.env['mrp.bom'])
        bom_form.type = 'subcontract'
        bom_form.product_tmpl_id = self.finished.product_tmpl_id
        with bom_form.bom_line_ids.new() as bom_line:
            bom_line.product_id = self.comp1
            bom_line.product_qty = 1
        with bom_form.bom_line_ids.new() as bom_line:
            bom_line.product_id = comp3
            bom_line.product_qty = 2
        bom2 = bom_form.save()

        # We assign the second BoM to the new partner
        self.bom.write({'subcontractor_ids': [(4, self.subcontractor_partner1.id, None)]})
        bom2.write({'subcontractor_ids': [(4, subcontractor_partner2.id, None)]})

        # Create a receipt picking from the subcontractor1
        picking_form = Form(self.env['stock.picking'])
        picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
        picking_form.partner_id = subcontractor_partner2
        with picking_form.move_ids.new() as move:
            move.product_id = self.finished
            move.product_uom_qty = 1
        picking_receipt = picking_form.save()
        picking_receipt.action_confirm()

        picking_receipt.move_ids.quantity = 3.0
        picking_receipt.move_ids.picked = True
        picking_receipt._action_done()
        mo = picking_receipt._get_subcontract_production()
        move_comp1 = mo.move_raw_ids.filtered(lambda m: m.product_id == self.comp1)
        move_comp3 = mo.move_raw_ids.filtered(lambda m: m.product_id == comp3)
        self.assertEqual(sum(move_comp1.mapped('product_uom_qty')), 3.0)
        self.assertEqual(sum(move_comp3.mapped('product_uom_qty')), 6.0)
        self.assertEqual(sum(move_comp1.mapped('quantity')), 3.0)
        self.assertEqual(sum(move_comp3.mapped('quantity')), 6.0)
        move_finished = mo.move_finished_ids
        self.assertEqual(sum(move_finished.mapped('product_uom_qty')), 3.0)
        self.assertEqual(sum(move_finished.mapped('quantity')), 3.0)

    def test_flow_8(self):
        resupply_sub_on_order_route = self.env['stock.route'].search([('name', '=', 'Resupply Subcontractor on Order')])
        (self.comp1 + self.comp2).write({'route_ids': [(4, resupply_sub_on_order_route.id, None)]})

        # 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.new() as move:
            move.product_id = self.finished
            move.product_uom_qty = 5
        picking_receipt = picking_form.save()
        picking_receipt.action_confirm()

        picking_receipt.move_ids.quantity = 3
        picking_receipt.move_ids.picked = True
        Form.from_action(self.env, picking_receipt.button_validate()).save().process()

        backorder = self.env['stock.picking'].search([('backorder_id', '=', picking_receipt.id)])
        self.assertTrue(backorder)
        self.assertEqual(backorder.move_ids.product_uom_qty, 2)
        mo_done = picking_receipt.move_ids._get_subcontract_production().filtered(lambda p: p.state == 'done')
        backorder_mo = backorder.move_ids.move_orig_ids.production_id.filtered(lambda p: p.state != 'done')
        self.assertTrue(mo_done)
        self.assertEqual(mo_done.qty_produced, 3)
        self.assertEqual(mo_done.product_uom_qty, 3)
        self.assertTrue(backorder_mo)
        self.assertEqual(backorder_mo.product_uom_qty, 2)
        self.assertEqual(backorder_mo.qty_produced, 0)
        backorder.move_ids.quantity = 2
        backorder.move_ids.picked = True
        backorder._action_done()
        self.assertTrue(picking_receipt.move_ids.move_orig_ids[0].production_id.state == 'done')

    def test_flow_9(self):
        """Ensure that cancel the subcontract moves will also delete the
        components need for the subcontractor.
        """
        resupply_sub_on_order_route = self.env['stock.route'].search([
            ('name', '=', 'Resupply Subcontractor on Order')
        ])
        (self.comp1 + self.comp2).write({
            'route_ids': [(4, resupply_sub_on_order_route.id)]
        })

        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.new() as move:
            move.product_id = self.finished
            move.product_uom_qty = 5
            move.quantity = 5
            move.picked = True
        picking_receipt = picking_form.save()
        picking_receipt.action_confirm()

        picking_delivery = self.env['stock.move'].search([
            ('product_id', 'in', (self.comp1 | self.comp2).ids)
        ]).picking_id
        self.assertTrue(picking_delivery)
        self.assertEqual(picking_delivery.state, 'confirmed')
        self.assertEqual(self.comp1.virtual_available, -5)
        self.assertEqual(self.comp2.virtual_available, -5)
        # action_cancel is not call on the picking in order
        # to test behavior from other source than picking (e.g. puchase).
        picking_receipt.move_ids._action_cancel()
        self.assertEqual(picking_delivery.state, 'cancel')
        self.assertEqual(self.comp1.virtual_available, 0.0)
        self.assertEqual(self.comp1.virtual_available, 0.0)

    def test_flow_10(self):
        """Receipts from a children contact of a subcontractor are properly
        handled.
        """
        # Create a children contact
        subcontractor_contact = self.env['res.partner'].create({
            'name': 'Test children subcontractor contact',
            'parent_id': self.subcontractor_partner1.id,
        })
        # 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 = subcontractor_contact
        with picking_form.move_ids.new() as move:
            move.product_id = self.finished
            move.product_uom_qty = 1
        picking_receipt = picking_form.save()
        picking_receipt.action_confirm()

        # Check that a manufacturing order is created
        mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])
        self.assertEqual(len(mo), 1)

    def test_flow_flexible_bom_1(self):
        """ Record Component for a bom subcontracted with a flexible and flexible + warning consumption """
        self.bom.consumption = 'flexible'
        # 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.new() as move:
            move.product_id = self.finished
            move.product_uom_qty = 1
        picking_receipt = picking_form.save()
        picking_receipt.action_confirm()

        action = picking_receipt.move_ids.action_show_subcontract_details()
        mo = self.env['mrp.production'].browse(action['res_id'])
        mo_form = Form(mo.with_context(**action['context']), view=action['views'][0][0])
        with mo_form.move_raw_ids.edit(0) as move:
            self.assertEqual(move.product_id, self.comp1)
            self.assertEqual(move.quantity, 0)
            move.quantity = 2
        mo = mo_form.save()
        self.assertEqual(mo.move_raw_ids[0].move_line_ids.quantity, 2)

        picking_receipt.button_validate()
        self.assertEqual(mo.state, 'done')
        avail_qty_comp1 = self.env['stock.quant']._get_available_quantity(self.comp1, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
        self.assertEqual(avail_qty_comp1, -2)

    def test_mrp_report_bom_structure_subcontracting(self):
        self.comp2_bom.write({'type': 'subcontract', 'subcontractor_ids': [Command.link(self.subcontractor_partner1.id)]})
        self.finished.seller_ids.price = 10
        supplier = self.env['product.supplierinfo'].create({
            'product_tmpl_id': self.comp2.product_tmpl_id.id,
            'partner_id': self.subcontractor_partner1.id,
            'price': 5,
        })
        self.env['product.supplierinfo'].create({
            'product_tmpl_id': self.comp2.product_tmpl_id.id,
            'partner_id': self.subcontractor_partner1.id,
            'price': 1,
            'min_qty': 5,
        })
        self.assertTrue(supplier.is_subcontractor)
        self.comp1.standard_price = 5
        report_values = self.env['report.mrp.report_bom_structure']._get_report_data(self.bom.id, searchQty=1, searchVariant=False)
        subcontracting_values = report_values['lines']['subcontracting']
        self.assertEqual(subcontracting_values['name'], self.subcontractor_partner1.display_name)
        self.assertEqual(report_values['lines']['bom_cost'], 20)  # 10 For subcontracting + 5 for comp1 + 5 for subcontracting of comp2_bom
        self.assertEqual(subcontracting_values['bom_cost'], 10)
        self.assertEqual(report_values['lines']['components'][0]['bom_cost'], 5)
        self.assertEqual(report_values['lines']['components'][1]['bom_cost'], 5)
        report_values = self.env['report.mrp.report_bom_structure']._get_report_data(self.bom.id, searchQty=3, searchVariant=False)
        subcontracting_values = report_values['lines']['subcontracting']
        self.assertEqual(report_values['lines']['bom_cost'], 60)  # 30 for subcontracting + 15 for comp1 + 15 for subcontracting of comp2_bom
        self.assertEqual(subcontracting_values['bom_cost'], 30)
        self.assertEqual(report_values['lines']['components'][0]['bom_cost'], 15)
        self.assertEqual(report_values['lines']['components'][1]['bom_cost'], 15)
        report_values = self.env['report.mrp.report_bom_structure']._get_report_data(self.bom.id, searchQty=5, searchVariant=False)
        subcontracting_values = report_values['lines']['subcontracting']
        self.assertEqual(report_values['lines']['bom_cost'], 80)  # 50 for subcontracting + 25 for comp1 + 5 for subcontracting of comp2_bom
        self.assertEqual(subcontracting_values['bom_cost'], 50)
        self.assertEqual(report_values['lines']['components'][0]['bom_cost'], 25)
        self.assertEqual(report_values['lines']['components'][1]['bom_cost'], 5)

    def test_several_backorders(self):
        def process_picking(picking, qty):
            picking.move_ids.quantity = qty
            picking.move_ids.picked = True
            action = picking.button_validate()
            if isinstance(action, dict):
                Form.from_action(self.env, action).save().process()

        resupply_route = self.env['stock.route'].search([('name', '=', 'Resupply Subcontractor on Order')])
        finished, component = self.env['product.product'].create([{
            'name': 'Finished Product',
            'is_storable': True,
        }, {
            'name': 'Component',
            'is_storable': True,
            'route_ids': [(4, resupply_route.id)],
        }])

        bom = 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 = self.env.ref('stock.picking_type_in')
        picking_form.partner_id = self.subcontractor_partner1
        with picking_form.move_ids.new() as move:
            move.product_id = finished
            move.product_uom_qty = 5
        picking = picking_form.save()
        picking.action_confirm()

        supply_picking = self.env['mrp.production'].search([('bom_id', '=', bom.id)]).picking_ids
        process_picking(supply_picking, 5)

        process_picking(picking, 1.25)

        backorder01 = picking.backorder_ids
        process_picking(backorder01, 1)

        backorder02 = backorder01.backorder_ids
        self.assertEqual(backorder02.move_ids.quantity, 2.75)

        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 quantity 0 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, happen 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
            move_line = picking.move_line_ids.ensure_one()
            picking.move_ids.quantity = qty
            action = picking.button_validate()
            if isinstance(action, dict):
                Form.from_action(self.env, action).save().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})

        finished = self.env['product.product'].create({'name': 'Finished Product', 'is_storable': True})
        component = self.env['product.product'].create([{'name': 'Component', 'is_storable': True}])
        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.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_date_warning(self):
        with Form(self.env['stock.picking']) as picking_form:
            picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
            picking_form.partner_id = self.subcontractor_partner1
            with picking_form.move_ids.new() as move:
                move.product_id = self.finished
                move.product_uom_qty = 3
                move.quantity = 3
            picking_receipt = picking_form.save()
        picking_receipt.action_confirm()
        self.assertEqual(picking_form.json_popover, False)

        subcontract = picking_receipt._get_subcontract_production()
        self.assertEqual(subcontract.date_start, picking_receipt.scheduled_date)
        self.assertEqual(subcontract.date_finished, picking_receipt.scheduled_date)

    def test_subcontracting_set_quantity_done(self):
        """ Tests to set a quantity done directly on a subcontracted move without using the subcontracting wizard.
            Checks that it does the same as it would do with the wizard.
        """
        self.bom.consumption = 'flexible'
        quantities = [10, 15, 12, 14]

        with Form(self.env['stock.picking']) as picking_form:
            picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
            picking_form.partner_id = self.subcontractor_partner1
            with picking_form.move_ids.new() as move:
                move.product_id = self.finished
                move.product_uom_qty = quantities[0]
            picking_receipt = picking_form.save()
        picking_receipt.action_confirm()
        move = picking_receipt.move_ids

        for qty in quantities[1:]:
            move.quantity = qty
            subcontracted = move._get_subcontract_production().filtered(lambda p: p.state != 'cancel')
            self.assertEqual(sum(subcontracted.mapped('product_qty')), qty)
            self.assertEqual(move.product_uom_qty, quantities[0])

        picking_receipt.button_validate()
        self.assertEqual(move.product_uom_qty, quantities[0])
        self.assertEqual(move.quantity, quantities[-1])
        subcontracted = move._get_subcontract_production().filtered(lambda p: p.state == 'done')
        self.assertEqual(sum(subcontracted.mapped('qty_produced')), quantities[-1])

    def test_change_reception_serial(self):
        self.env.ref('base.group_user').write({'implied_ids': [(4, self.env.ref('stock.group_production_lot').id)]})
        self.finished.tracking = 'serial'
        self.bom.consumption = 'flexible'

        finished_lots = self.env['stock.lot'].create([{
            'name': 'lot_%s' % number,
            'product_id': self.finished.id,
        } for number in range(3)])

        with Form(self.env['stock.picking']) as picking_form:
            picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
            picking_form.partner_id = self.subcontractor_partner1
            with picking_form.move_ids.new() as move:
                move.product_id = self.finished
                move.product_uom_qty = 3
            picking_receipt = picking_form.save()
        picking_receipt.action_confirm()
        subcontract_move = picking_receipt.move_ids.filtered(lambda m: m.is_subcontract)

        # Register serial number for each finished product
        action = picking_receipt.move_ids.action_show_details()
        self.assertEqual(action['name'], 'Detailed Operations', "It should open the detailed operations view.")
        with Form(subcontract_move.with_context(action['context']), view=action['view_id']) as move_form:
            for idx, lot in enumerate(finished_lots):
                with move_form.move_line_ids.edit(idx) as move_line:
                    move_line.lot_id = lot
            move_form.save()

        self.assertEqual(len(subcontract_move._get_subcontract_production()), 3)
        self.assertEqual(len(subcontract_move._get_subcontract_production().lot_producing_ids), 3)
        self.assertRecordValues(subcontract_move._get_subcontract_production().lot_producing_ids.sorted('id'), [
            {'id': finished_lots[0].id},
            {'id': finished_lots[1].id},
            {'id': finished_lots[2].id},
        ])

        new_lot = self.env['stock.lot'].create({
            'name': 'lot_alter',
            'product_id': self.finished.id,
        })
        action = picking_receipt.move_ids.action_show_details()
        self.assertEqual(action['name'], 'Detailed Operations', "The subcontract record components wizard shouldn't be available now.")
        with Form(subcontract_move.with_context(action['context']), view=action['view_id']) as move_form:
            with move_form.move_line_ids.edit(2) as move_line:
                move_line.lot_id = new_lot
            move_form.save()

        subcontracted_mo = subcontract_move._get_subcontract_production()
        self.assertEqual(len(subcontracted_mo.filtered(lambda p: p.lot_producing_ids == new_lot)), 1)
        self.assertEqual(len(subcontracted_mo.filtered(lambda p: p.lot_producing_ids != new_lot)), 2)

    def test_decrease_quantity_done(self):
        self.bom.consumption = 'flexible'
        supplier_location = self.env.ref('stock.stock_location_suppliers')
        uom_duo = self.env['uom.uom'].create({
            'name': 'Duos',
            'relative_factor': 2.0,
            'relative_uom_id': self.env.ref('uom.product_uom_unit').id,
        })

        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, {
                'product_id': self.finished.id,
                'product_uom_qty': 10.0,
                'product_uom': uom_duo.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': 0.0, 'product_qty': 6.0, 'state': 'confirmed'},
        ])

        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': 0.0, 'product_qty': 9.0, 'state': 'confirmed'},
        ])

        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': 0.0, 'product_qty': 7.0, 'state': 'confirmed'},
        ])

        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': 0.0, 'product_qty': 4.0, 'state': 'confirmed'},
        ])

        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': 0.0, 'product_qty': 10.0, 'state': 'confirmed'},
        ])

    def test_change_partner_subcontracting_location(self):
        """On creating a subcontrating picking, the destination location of the picking is equal to
        the subcontracting location of the contact if specified. Otherwise, it will be equal to the
        default warehouse subcontracting location.
        """
        custom_subcontract_location = self.env['stock.location'].create({
            'name': 'custom partner location',
            'location_id': self.env.company.subcontracting_location_id.id,
            'usage': 'internal',
            'company_id': self.env.company.id,
        })
        subcontractor = self.env['res.partner'].create({'name': 'subcontractor'})

        def create_picking(subcontractor):
            picking_form = Form(self.env['stock.picking'])
            picking_form.picking_type_id = self.warehouse.subcontracting_resupply_type_id
            picking_form.partner_id = subcontractor
            with picking_form.move_ids.new() as move:
                move.product_id = self.comp1
                move.product_uom_qty = 1.0
            picking = picking_form.save()
            picking.action_confirm()
            return picking

        picking_with_default_location = create_picking(subcontractor)
        self.assertEqual(picking_with_default_location.location_dest_id, self.warehouse.subcontracting_resupply_type_id.default_location_dest_id)

        subcontractor.property_stock_subcontractor = custom_subcontract_location.id
        picking_with_custom_location = create_picking(subcontractor)
        self.assertEqual(picking_with_custom_location.location_dest_id, custom_subcontract_location)

    def test_validate_partial_subcontracting_without_backorder(self):
        """ Test the validation of a partial subcontracting without creating a backorder."""
        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': [Command.create({
                'product_id': self.finished.id,
                'product_uom_qty': 20.0,
                'location_id': supplier_location.id,
                'location_dest_id': self.warehouse.lot_stock_id.id,
            })],
        })
        receipt.action_confirm()
        self.assertEqual(receipt.state, 'assigned')
        receipt.move_ids.quantity = 19.8
        # Validate picking without backorder
        backorder_wizard_dict = receipt.button_validate()
        backorder_wizard_form = Form(self.env[backorder_wizard_dict['res_model']].with_context(backorder_wizard_dict['context']))
        backorder_wizard_form.save().process_cancel_backorder()
        self.assertEqual(receipt.state, 'done')
        productions = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)]).sorted('id')
        self.assertRecordValues(productions, [
            {'product_qty': 19.8, 'qty_producing': 19.8, 'state': 'done'},
        ])

    def test_replenish_with_subcontracting_bom(self):
        """ Checks that a subcontracting bom cannot trigger a 'Manufacture' replenish.
        """
        self.assertEqual(self.finished.bom_ids.type, 'subcontract')
        self.finished.seller_ids.unlink()
        replenish_wizard = self.env['product.replenish'].create({
            'product_id': self.finished.id,
            'product_tmpl_id': self.finished.product_tmpl_id.id,
            'product_uom_id': self.finished.uom_id.id,
            'quantity': 1,
            'warehouse_id': self.warehouse.id,
        })
        self.assertFalse(replenish_wizard.allowed_route_ids)

    def test_subcontracting_unbuild_warning(self):
        with Form(self.env['stock.picking']) as picking_form:
            picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
            picking_form.partner_id = self.subcontractor_partner1
            with picking_form.move_ids.new() as move:
                move.product_id = self.finished
                move.product_uom_qty = 3
                move.quantity = 3
            picking_receipt = picking_form.save()
        picking_receipt.action_confirm()
        subcontract = picking_receipt._get_subcontract_production()
        error_message = "You can't unbuild a subcontracted Manufacturing Order."
        with self.assertRaisesRegex(UserError, error_message):
            subcontract.button_unbuild()

    def test_subcontracted_product_return_locations(self):
        """
        Verify that when returning subcontracted and non-subcontracted products:
        - the picking has destination location set to the supplier location.
        - The returned move line for the subcontracted product has destination location set to the subcontractor's stock location.
        - The returned move line for the non-subcontracted product returns to the supplier location.
        """
        supplier_location = self.env.ref('stock.stock_location_suppliers')
        stock_location = self.warehouse.lot_stock_id
        picking_receipt = self.env['stock.picking'].create({
            'picking_type_id': self.env.ref('stock.picking_type_in').id,
            'partner_id': self.subcontractor_partner1.id,
            'location_id': supplier_location.id,
            'location_dest_id': stock_location.id,
            'move_ids': [
                Command.create({
                    'product_id': self.finished.id,
                }),
                Command.create({
                    'product_id': self.comp1.id,
                }),
            ]
        })
        picking_receipt.action_confirm()
        picking_receipt.move_ids.quantity = 1
        picking_receipt.move_ids.picked = True
        picking_receipt.button_validate()
        self.assertEqual(picking_receipt.state, 'done')
        # Ensure returns to subcontractor location
        return_form = Form(self.env['stock.return.picking'].with_context(active_id=picking_receipt.id, active_model='stock.picking'))
        return_wizard = return_form.save()
        return_wizard.product_return_moves.quantity = 1
        return_picking = return_wizard._create_return()
        self.assertEqual(len(return_picking), 1)
        self.assertEqual(return_picking.location_dest_id, supplier_location)
        self.assertEqual(return_picking.location_id, stock_location)
        self.assertRecordValues(return_picking.move_ids.move_line_ids, [
            {'product_id': self.finished.id, 'location_id': stock_location.id, 'location_dest_id': self.subcontractor_partner1.property_stock_subcontractor.id},
            {'product_id': self.comp1.id, 'location_id': stock_location.id, 'location_dest_id': supplier_location.id}
        ])
        self.assertRecordValues(return_picking.move_ids, [
            {'product_id': self.finished.id, 'location_id': stock_location.id, 'location_dest_id': self.subcontractor_partner1.property_stock_subcontractor.id},
            {'product_id': self.comp1.id, 'location_id': stock_location.id, 'location_dest_id': supplier_location.id}
        ])

    def test_flow_tracked_1(self):
        """ This test mimics test_flow_1 but with a BoM that has tracking included in it.
        """
        # Create a receipt picking from the subcontractor
        self.finished.tracking = 'lot'
        self.comp1.tracking = 'serial'
        picking_form = Form(self.env['stock.picking'])
        picking_form.picking_type_id = self.warehouse.in_type_id
        picking_form.partner_id = self.subcontractor_partner1
        with picking_form.move_ids.new() as move:
            move.product_id = self.finished
            move.product_uom_qty = 1
            move.picked = True
        picking_receipt = picking_form.save()
        picking_receipt.action_confirm()

        # Check the created manufacturing order
        mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])
        self.assertEqual(len(mo), 1)
        self.assertEqual(len(mo.picking_ids), 0)
        self.assertEqual(mo.picking_type_id, self.warehouse.subcontracting_type_id)
        self.assertFalse(mo.picking_type_id.active)

        lot_id = self.env['stock.lot'].create({
            'name': 'lot1',
            'product_id': self.finished.id,
        })
        serial_id = self.env['stock.lot'].create({
            'name': 'lot1',
            'product_id': self.comp1.id,
        })

        action = picking_receipt.move_ids.action_show_details()
        with Form(picking_receipt.move_ids.with_context(action['context']), view=action['view_id']) as move_form:
            with move_form.move_line_ids.new() as move_line:
                move_line.lot_id = lot_id
                move_line.picked = True
                move_line.quantity = 1
            move_form.save()
        action = picking_receipt.move_ids.action_show_subcontract_details()
        mo = self.env['mrp.production'].browse(action['res_id'])
        action = mo.move_raw_ids[0].action_show_details()
        with Form(mo.move_raw_ids[0].with_context(action['context']), view=action['view_id']) as move_form:
            with move_form.move_line_ids.new() as move_line:
                move_line.lot_id = serial_id
                move_line.picked = True
                move_line.quantity = 1
            move_form.save()

        picking_receipt.button_validate()
        self.assertEqual(mo.state, 'done')

        # Available quantities should be negative at the subcontracting location for each components
        avail_qty_comp1 = self.env['stock.quant']._get_available_quantity(self.comp1, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
        avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
        avail_qty_finished = self.env['stock.quant']._get_available_quantity(self.finished, self.warehouse.lot_stock_id)
        self.assertEqual(avail_qty_comp1, -1)
        self.assertEqual(avail_qty_comp2, -1)
        self.assertEqual(avail_qty_finished, 1)

    def test_flow_tracked_only_finished(self):
        """ Test when only the finished product is tracked """
        self.finished.tracking = "serial"
        self.comp1.tracking = "none"
        nb_finished_product = 3
        # Create a receipt picking from the subcontractor
        picking_form = Form(self.env['stock.picking'])
        picking_form.picking_type_id = self.warehouse.in_type_id
        picking_form.partner_id = self.subcontractor_partner1
        with picking_form.move_ids.new() as move:
            move.product_id = self.finished
            move.product_uom_qty = nb_finished_product
        picking_receipt = picking_form.save()
        picking_receipt.action_confirm()
        picking_receipt.do_unreserve()

        lots = self.env['stock.lot'].create([
            {'name': f"subtracked_{i}", 'product_id': self.finished.id}
            for i in range(nb_finished_product)
        ])

        move_details = Form(picking_receipt.move_ids, view='stock.view_stock_move_operations')
        for lot_id in lots:
            with move_details.move_line_ids.new() as ml:
                ml.quantity = 1
                ml.lot_id = lot_id
        move_details.save()
        picking_receipt.move_ids.picked = True
        picking_receipt.button_validate()
        # Check the created manufacturing order
        # Should have one mo by serial number
        mos = picking_receipt.move_ids.move_orig_ids.production_id
        self.assertEqual(len(mos), nb_finished_product)
        self.assertEqual(mos.mapped("state"), ["done"] * nb_finished_product)
        self.assertEqual(mos.picking_type_id, self.warehouse.subcontracting_type_id)
        self.assertFalse(mos.picking_type_id.active)
        self.assertEqual(set(mos.lot_producing_ids.mapped("name")), {f"subtracked_{i}" for i in range(nb_finished_product)})

        # Available quantities should be negative at the subcontracting location for each components
        avail_qty_comp1 = self.env['stock.quant']._get_available_quantity(self.comp1, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
        avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
        avail_qty_finished = self.env['stock.quant']._get_available_quantity(self.finished,
                                                                             self.warehouse.lot_stock_id)
        self.assertEqual(avail_qty_comp1, -nb_finished_product)
        self.assertEqual(avail_qty_comp2, -nb_finished_product)
        self.assertEqual(avail_qty_finished, nb_finished_product)

    def test_flow_tracked_backorder(self):
        """ This test uses tracked (serial and lot) component and tracked (serial) finished product """
        todo_nb = 4
        self.comp2.tracking = 'lot'
        self.finished.tracking = 'serial'

        # Create a receipt picking from the subcontractor
        picking_form = Form(self.env['stock.picking'])
        picking_form.picking_type_id = self.warehouse.in_type_id
        picking_form.partner_id = self.subcontractor_partner1
        with picking_form.move_ids.new() as move:
            move.product_id = self.finished
            move.product_uom_qty = todo_nb
        picking_receipt = picking_form.save()
        picking_receipt.action_confirm()

        # Check the created manufacturing order
        mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])
        self.assertEqual(len(mo), 1)
        self.assertEqual(len(mo.picking_ids), 0)
        self.assertEqual(mo.picking_type_id, self.warehouse.subcontracting_type_id)
        self.assertFalse(mo.picking_type_id.active)

        lot_comp2 = self.env['stock.lot'].create({
            'name': 'lot_comp2',
            'product_id': self.comp2.id,
        })
        serials_finished = []
        serials_comp1 = []
        for i in range(todo_nb):
            serials_finished.append(self.env['stock.lot'].create({
                'name': 'serial_fin_%s' % i,
                'product_id': self.finished.id,
            }))
            serials_comp1.append(self.env['stock.lot'].create({
                'name': 'serials_comp1_%s' % i,
                'product_id': self.comp1.id,
            }))

        # Final product
        action = picking_receipt.move_ids.action_show_details()
        with Form(picking_receipt.move_ids.with_context(action['context']), view=action['view_id']) as move_form:
            for idx, serial in enumerate(serials_finished):
                with move_form.move_line_ids.edit(idx) as move_line:
                    move_line.lot_id = serial
                    move_line.picked = True
                    move_line.quantity = 1
            move_form.save()

        # Components
        for mo, compo_1_serial in zip(picking_receipt._get_subcontract_production(), serials_comp1):
            action = mo.move_raw_ids[0].action_show_details()
            with Form(mo.move_raw_ids[0].with_context(action['context']), view=action['view_id']) as move_form:
                with move_form.move_line_ids.new() as move_line:
                    self.assertEqual(move_line.product_id, self.comp1)
                    move_line.lot_id = compo_1_serial
                    move_line.picked = True
                    move_line.quantity = 1
                move_form.save()
            action = mo.move_raw_ids[1].action_show_details()
            with Form(mo.move_raw_ids[1].with_context(action['context']), view=action['view_id']) as move_form:
                with move_form.move_line_ids.new() as move_line:
                    self.assertEqual(move_line.product_id, self.comp2)
                    move_line.lot_id = lot_comp2
                    move_line.picked = True
                    move_line.quantity = 1
                move_form.save()

        picking_receipt.button_validate()
        self.assertEqual(mo.state, 'done')

        # Available quantities should be negative at the subcontracting location for each components
        avail_qty_comp1 = self.env['stock.quant']._get_available_quantity(self.comp1, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
        avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
        avail_qty_finished = self.env['stock.quant']._get_available_quantity(self.finished, self.warehouse.lot_stock_id)
        self.assertEqual(avail_qty_comp1, -todo_nb)
        self.assertEqual(avail_qty_comp2, -todo_nb)
        self.assertEqual(avail_qty_finished, todo_nb)

    def test_flow_backorder_production(self):
        """ Test subcontracted MO backorder (i.e. through record production window, NOT through
        picking backorder). Finished product is serial tracked to ensure subcontracting MO window
        is opened. Check that MO backorder auto-reserves components
        """
        todo_nb = 3
        self.warehouse.subcontracting_to_resupply = True
        self.finished.tracking = 'serial'
        finished_serials = self.env['stock.lot'].create([{
            'name': 'sn_%s' % str(i),
            'product_id': self.finished.id,
        } for i in range(todo_nb)])

        self.env['stock.quant']._update_available_quantity(self.comp1, self.warehouse.lot_stock_id, todo_nb)

        # 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.new() as move:
            move.product_id = self.finished
            move.product_uom_qty = todo_nb
            move.picked = True
        picking_receipt = picking_form.save()
        picking_receipt.action_confirm()

        mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])

        # Process the delivery of the components
        compo_picking = mo.picking_ids
        for move in compo_picking.move_ids:
            move.quantity = todo_nb
            move.picked = True
        compo_picking.button_validate()

        picking_receipt = self.env['stock.picking'].search([('partner_id', '=', self.subcontractor_partner1.id), ('state', '!=', 'done')])
        action = picking_receipt.move_ids.action_show_details()
        with Form(picking_receipt.move_ids.with_context(action['context']), view=action['view_id']) as move_form:
            for sn in finished_serials:
                with move_form.move_line_ids.new() as move_line:
                    move_line.lot_id = sn
                    move_line.picked = True
            move_form.save()

        # Validate the picking
        picking_receipt.button_validate()
        self.assertEqual(picking_receipt.state, 'done')

    def test_flow_subcontracting_portal(self):
        # Create a receipt picking from the subcontractor
        self.finished.tracking = 'lot'
        other_product = self.env['product.product'].create({'name': 'Other Product', 'is_storable': True})
        self.portal_user = self.env['res.users'].create({
            'name': 'portal user (subcontractor)',
            'partner_id': self.subcontractor_partner1.id,
            'login': 'subcontractor',
            'password': 'subcontractor',
            'email': 'subcontractor@subcontracting.portal',
            'group_ids': [(6, 0, [self.env.ref('base.group_portal').id, self.env.ref('stock.group_production_lot').id])]
        })
        picking_form = Form(self.env['stock.picking'])
        picking_form.picking_type_id = self.warehouse.in_type_id
        picking_form.partner_id = self.subcontractor_partner1
        with picking_form.move_ids.new() as move:
            move.product_id = self.finished
            move.product_uom_qty = 2
        picking_receipt = picking_form.save()
        picking_receipt.action_confirm()

        # Using the subcontractor (portal user)
        lot1 = self.env['stock.lot'].with_user(self.portal_user).create({
            'name': 'lot1',
            'product_id': self.finished.id,
        })
        lot2 = self.env['stock.lot'].with_user(self.portal_user).create({
            'name': 'lot2',
            'product_id': self.finished.id,
        })
        serial1 = self.env['stock.lot'].with_user(self.portal_user).create({
            'name': 'lot1',
            'product_id': self.comp1.id,
        })
        serial2 = self.env['stock.lot'].with_user(self.portal_user).create({
            'name': 'lot2',
            'product_id': self.comp1.id,
        })
        serial3 = self.env['stock.lot'].with_user(self.portal_user).create({
            'name': 'lot3',
            'product_id': self.comp1.id,
        })
        action = picking_receipt.with_user(self.portal_user).with_context({'is_subcontracting_portal': 1}).move_ids.action_show_details()
        with Form(picking_receipt.move_ids.with_context(action['context']), view=action['view_id']) as move_form:
            with move_form.move_line_ids.edit(0) as move_line:
                move_line.lot_id = lot1
                move_line.picked = True
                move_line.quantity = 1
            with move_form.move_line_ids.new() as move_line:
                move_line.lot_id = lot2
                move_line.picked = True
                move_line.quantity = 1
            move_form.save()
        mo_1, mo_2 = picking_receipt.with_user(self.portal_user)._get_subcontract_production()
        # Registering components for the first manufactured product
        action = mo_1.move_raw_ids[0].with_user(self.portal_user).with_context({'is_subcontracting_portal': 1}).action_show_details()
        with Form(mo_1.move_raw_ids[0].with_user(self.portal_user).with_context(action['context']), view=action['view_id']) as move_form:
            with move_form.move_line_ids.new() as move_line:
                move_line.lot_id = serial1
                move_line.picked = True
                move_line.quantity = 1
            move_form.save()

        # Registering components for the second manufactured product with over-consumption, which leads to a warning
        action = mo_2.move_raw_ids[0].with_user(self.portal_user).with_context({'is_subcontracting_portal': 1}).action_show_details()
        with Form(mo_2.move_raw_ids[0].with_user(self.portal_user).with_context(action['context']), view=action['view_id']) as move_form:
            for compo_serial in (serial2, serial3):
                with move_form.move_line_ids.new() as move_line:
                    move_line.lot_id = compo_serial
                    move_line.picked = True
                    move_line.quantity = 1
            move_form.save()
        action = mo_2.move_raw_ids[1].with_user(self.portal_user).with_context({'is_subcontracting_portal': 1}).action_show_details()
        with Form(mo_2.move_raw_ids[1].with_user(self.portal_user).with_context(action['context']), view=action['view_id']) as move_form:
            with move_form.move_line_ids.new() as move_line:
                move_line.picked = True
                move_line.quantity = 2
            move_form.save()

        # The portal user should not be able to add a product not in the BoM
        action = picking_receipt.with_user(self.portal_user).move_ids.action_show_subcontract_details()
        mo_form = Form(mo_2.with_context(**action['context']), view=action['views'][1][0])
        with self.assertRaises(AccessError):
            with mo_form.move_line_raw_ids.new() as move:
                move.product_id = other_product
        mo = mo_form.save()

        # Attempt to validate from the portal user should give an error
        with self.assertRaises(UserError):
            picking_receipt.with_user(self.portal_user).button_validate()

        # Validation from the backend user
        picking_receipt.button_validate()
        self.assertEqual(mo.state, 'done')
        self.assertEqual(mo.move_line_raw_ids[0].quantity, 1)
        self.assertEqual(mo.move_line_raw_ids[0].lot_id, serial2)
        self.assertEqual(mo.move_line_raw_ids[1].quantity, 1)
        self.assertEqual(mo.move_line_raw_ids[1].lot_id, serial3)
        self.assertEqual(mo.move_line_raw_ids[2].quantity, 2)

    def test_resupply_subcontractor_in_mtso(self):
        """
        Check the 'resupply subcontractor on order' route when the associated rule is
        updated to 'Take From Stock, if unavailable, Trigger Another Rule' (mtso)
        """
        resupply_route = self.env.ref('mrp_subcontracting.route_resupply_subcontractor_mto')
        resupply_route.warehouse_ids = [Command.set(self.warehouse.ids)]
        resupply_route.rule_ids.procure_method = 'mts_else_mto'
        receipt = self.env['stock.picking'].create({
            'partner_id': self.subcontractor_partner1.id,
            'location_id': self.ref('stock.stock_location_suppliers'),
            'location_dest_id': self.warehouse.lot_stock_id.id,
            'picking_type_id': self.warehouse.in_type_id.id,
            'move_ids': [Command.create({
                'product_id': self.finished.id,
                'product_uom_qty': 10.0,
                'location_id': self.ref('stock.stock_location_suppliers'),
                'location_dest_id': self.warehouse.lot_stock_id.id,
            })],
        })
        receipt.action_confirm()
        # Note that the subcontractor of the MO is the commercial_partner_id of subcontractor_partner1
        subcontracted_mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)], limit=1)
        resupply_subcontractor_delivery = self.env['stock.picking'].search([('partner_id', '=', subcontracted_mo.subcontractor_id.id)], limit=1)
        self.assertRecordValues(resupply_subcontractor_delivery.move_ids, [
            {'product_id': self.comp1.id, 'product_uom_qty': 10.0},
            {'product_id': self.comp2.id, 'product_uom_qty': 10.0},
        ])


class TestSubcontractingSerialMassReceipt(TransactionCase):

    def setUp(self):
        super().setUp()
        self.subcontractor = self.env['res.partner'].create({
            'name': 'Subcontractor',
        })
        self.resupply_route = self.env['stock.route'].search([('name', '=', 'Resupply Subcontractor on Order')])
        self.raw_material = self.env['product.product'].create({
            'name': 'Component',
            'is_storable': True,
            'route_ids': [Command.link(self.resupply_route.id)],
        })
        self.finished = self.env['product.product'].create({
            'name': 'Finished',
            'is_storable': True,
            'tracking': 'serial'
        })
        self.bom = self.env['mrp.bom'].create({
            'product_id': self.finished.id,
            'product_tmpl_id': self.finished.product_tmpl_id.id,
            'product_qty': 1.0,
            'type': 'subcontract',
            'subcontractor_ids': [Command.link(self.subcontractor.id)],
            'consumption': 'strict',
            'bom_line_ids': [
                Command.create({'product_id': self.raw_material.id, 'product_qty': 1}),
            ]
        })

    def generate_subcontracting_receipt_and_mo(self, product_qty, warehouse=None):
        if not warehouse:
            warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
        receipt = self.env['stock.picking'].create({
            'picking_type_id': warehouse.in_type_id.id,
            'partner_id': self.subcontractor.id,
            'location_id': self.ref('stock.stock_location_suppliers'),
            'location_dest_id': warehouse.lot_stock_id.id,
            'move_ids': [Command.create({
                'product_id': self.finished.id,
                'product_uom_qty': product_qty,
                'product_uom': self.finished.uom_id.id,
                'location_id': self.ref('stock.stock_location_suppliers'),
                'location_dest_id': warehouse.lot_stock_id.id,
            })]
        })
        receipt.action_confirm()
        action = receipt.move_ids.action_show_subcontract_details()
        return receipt, self.env['mrp.production'].browse(action['res_id'])

    def test_receive_after_resupply(self):
        quantities = [5, 4, 1]
        # Make needed component stock
        self.env['stock.quant']._update_available_quantity(self.raw_material, self.env.ref('stock.stock_location_stock'), sum(quantities))
        # 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
        with picking_form.move_ids.new() as move:
            move.product_id = self.finished
            move.product_uom_qty = sum(quantities)
        picking_receipt = picking_form.save()
        picking_receipt.action_confirm()
        # Process the delivery of the components
        picking_deliver = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)]).picking_ids
        picking_deliver.action_assign()
        picking_deliver.button_validate()
        # Receive
        for quantity in quantities:
            # Receive <quantity> finished products
            picking_receipt.do_unreserve()
            lot_name = self.env['stock.lot']._get_next_serial(picking_receipt.company_id, picking_receipt.move_ids[0].product_id) or 'sn#1'
            picking_receipt.move_ids[0]._generate_serial_numbers(lot_name, quantity)
            picking_receipt.move_ids.picked = True
            wizard_data = picking_receipt.button_validate()
            if wizard_data is not True:
                # Create backorder
                Form.from_action(self.env, wizard_data).save().process()
                self.assertEqual(picking_receipt.state, 'done')
                picking_receipt = picking_receipt.backorder_ids[-1]
                self.assertEqual(picking_receipt.state, 'assigned')
        self.assertEqual(picking_receipt.state, 'done')
        self.assertEqual(self.env['stock.quant']._get_available_quantity(self.raw_material, self.env.ref('stock.stock_location_stock')), 0)
        self.assertEqual(self.env['stock.quant']._get_available_quantity(self.raw_material, self.subcontractor.property_stock_subcontractor), 0)

    def test_receive_no_resupply(self):
        quantity = 5
        # 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
        with picking_form.move_ids.new() as move:
            move.product_id = self.finished
            move.product_uom_qty = quantity
        picking_receipt = picking_form.save()
        picking_receipt.action_confirm()
        picking_receipt.do_unreserve()
        # Receive finished products
        lot_name = self.env['stock.lot']._get_next_serial(picking_receipt.company_id, picking_receipt.move_ids[0].product_id) or 'sn#1'
        picking_receipt.move_ids[0]._generate_serial_numbers(lot_name, quantity)
        picking_receipt.move_ids.picked = True
        picking_receipt.button_validate()
        self.assertEqual(picking_receipt.state, 'done')
        self.assertEqual(self.env['stock.quant']._get_available_quantity(self.raw_material, self.env.ref('stock.stock_location_stock')), 0)
        self.assertEqual(self.env['stock.quant']._get_available_quantity(self.raw_material, self.subcontractor.property_stock_subcontractor, allow_negative=True), -quantity)

    def test_bom_subcontracting_product_dynamic_attribute(self):
        """
            Test that the report BOM data is available for a product with an dynamic attribute
            but without variant.
        """
        dynamic_attribute = self.env['product.attribute'].create({
            'name': 'flavour',
            'create_variant': 'dynamic',
        })
        value_1 = self.env['product.attribute.value'].create({
            'name': 'Vanilla',
            'attribute_id': dynamic_attribute.id,
        })
        value_2 = self.env['product.attribute.value'].create({
            'name': 'Chocolate',
            'attribute_id': dynamic_attribute.id,
        })
        product_template = self.env['product.template'].create({
            'name': 'Cake',
            'uom_id': self.env.ref('uom.product_uom_unit').id,
            'is_storable': True,
        })
        self.env['product.template.attribute.line'].create({
            'product_tmpl_id': product_template.id,
            'attribute_id': dynamic_attribute.id,
            'value_ids': [Command.set([value_1.id, value_2.id])],
        })
        bom = self.env['mrp.bom'].create({
            'product_tmpl_id': product_template.id,
            'type': 'subcontract',
            'subcontractor_ids': [Command.set([self.subcontractor.id])],
        })
        report_values = self.env['report.mrp.report_bom_structure']._get_report_data(bom_id=bom.id, searchVariant=False)
        self.assertTrue(report_values)

    def test_subcontracting_multiple_backorders(self):
        """
        Check that processing multiple backorders in a raw for a
        subcontracted prodcut is well behaved.
        """
        subcontracted_produt = self.env['product.product'].create({
            'name': 'Lovely product',
            'is_storable': True,
        })
        self.env['mrp.bom'].create({
            'product_tmpl_id': subcontracted_produt.product_tmpl_id.id,
            'type': 'subcontract',
            'subcontractor_ids': [Command.set(self.subcontractor.ids)],
        })
        warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
        warehouse.in_type_id.create_backorder = 'always'
        receipt = self.env['stock.picking'].create({
            'picking_type_id': warehouse.in_type_id.id,
            'partner_id': self.subcontractor.id,
            'location_id': self.ref('stock.stock_location_suppliers'),
            'location_dest_id': warehouse.lot_stock_id.id,
            'move_ids': [Command.create({
                'product_id': subcontracted_produt.id,
                'product_uom_qty': 100,
                'product_uom': subcontracted_produt.uom_id.id,
                'location_id': self.ref('stock.stock_location_suppliers'),
                'location_dest_id': warehouse.lot_stock_id.id,
            })],
        })
        receipt.action_confirm()
        action = receipt.move_ids.action_show_subcontract_details()
        mo = self.env['mrp.production'].browse(action['res_id'])
        with Form(mo.with_context(**action['context']), view=action['views'][0][0]) as mo_form:
            mo_form.product_qty = 5.0
        self.assertRecordValues(receipt.move_line_ids, [
            {'quantity': 5.0, 'state': 'partially_available', 'picked': False}
        ])
        receipt.button_validate()
        backorder = receipt.backorder_ids
        action = backorder.move_ids.action_show_subcontract_details()
        mo = self.env['mrp.production'].browse(action['res_id'])
        with Form(mo.with_context(**action['context']), view=action['views'][0][0]) as mo_form:
            mo_form.product_qty = 3.0
        self.assertRecordValues(backorder.move_line_ids, [
            {'quantity': 3.0, 'state': 'partially_available', 'picked': False}
        ])
        backorder.button_validate()
        backorder_backorder = backorder.backorder_ids
        action = backorder_backorder.move_ids.action_show_subcontract_details()
        mo = self.env['mrp.production'].browse(action['res_id'])
        with Form(mo.with_context(**action['context']), view=action['views'][0][0]) as mo_form:
            mo_form.product_qty = 1.0
        self.assertRecordValues(backorder_backorder.move_line_ids, [
            {'quantity': 1.0, 'state': 'partially_available', 'picked': False}
        ])
        backorder_backorder.button_validate()
        self.assertEqual(subcontracted_produt.qty_available, 9.0)

    def test_use_customized_serial_sequence_in_subcontracting_productions(self):
        """
        Test that serial numbers are generated with the correct prefix and sequence,
        that manually provided serial numbers are correctly applied, and that serial
        numbering remains consistent across multiple (subcontracted) manufacturing orders.
        """
        warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)

        receipt, mo = self.generate_subcontracting_receipt_and_mo(2, warehouse)
        self.finished.lot_sequence_id.prefix = 'TEST'
        self.finished.lot_sequence_id.number_next_actual = 1
        serials_wizard = Form.from_action(self.env, mo.action_generate_serial())
        self.assertEqual(serials_wizard.lot_name, 'TEST0000001')
        serials_wizard.save().action_generate_serial_numbers()
        serials_wizard.save().action_apply()
        self.assertRecordValues(mo._get_subcontract_move().lot_ids.sorted('name'), [
            {'name': 'TEST0000001'},
            {'name': 'TEST0000002'},
        ])
        self.assertRecordValues(receipt.move_ids[0].lot_ids.sorted('name'), [
            {'name': 'TEST0000001'},
            {'name': 'TEST0000002'},
        ])
        receipt.button_validate()
        self.assertEqual(self.env['stock.quant']._get_available_quantity(self.finished, warehouse.lot_stock_id), 2)
        self.assertRecordValues(self.env['stock.lot'].search([('product_id', '=', self.finished.id)]).sorted('name'), [
            {'name': 'TEST0000001'},
            {'name': 'TEST0000002'},
        ])
        self.assertEqual(self.finished.serial_prefix_format + self.finished.next_serial, 'TEST0000003')

        second_receipt, second_mo = self.generate_subcontracting_receipt_and_mo(5, warehouse)
        second_serials_wizard = Form.from_action(self.env, second_mo.action_generate_serial())
        self.assertEqual(second_serials_wizard.lot_name, 'TEST0000003')
        second_serials_wizard.serial_numbers = 'TEST0000005\nLOREM002\nTEST0000003\nIPSUM101\nTEST0000004'
        second_serials_wizard.save().action_apply()
        self.assertRecordValues(second_mo._get_subcontract_move().lot_ids.sorted('name'), [
            {'name': 'IPSUM101'},
            {'name': 'LOREM002'},
            {'name': 'TEST0000003'},
            {'name': 'TEST0000004'},
            {'name': 'TEST0000005'},
        ])
        self.assertRecordValues(second_receipt.move_ids[0].lot_ids.sorted('name'), [
            {'name': 'IPSUM101'},
            {'name': 'LOREM002'},
            {'name': 'TEST0000003'},
            {'name': 'TEST0000004'},
            {'name': 'TEST0000005'},
        ])
        second_receipt.button_validate()
        self.assertEqual(self.env['stock.quant']._get_available_quantity(self.finished, warehouse.lot_stock_id), 2 + 5)
        self.assertRecordValues(self.env['stock.lot'].search([('product_id', '=', self.finished.id)]).sorted('name'), [
            {'name': 'IPSUM101'},
            {'name': 'LOREM002'},
            {'name': 'TEST0000001'},
            {'name': 'TEST0000002'},
            {'name': 'TEST0000003'},
            {'name': 'TEST0000004'},
            {'name': 'TEST0000005'},
        ])

        _third_receipt, third_mo = self.generate_subcontracting_receipt_and_mo(2, warehouse)
        third_mo.action_confirm()
        third_serials_wizard = Form.from_action(self.env, third_mo.action_generate_serial())
        self.assertEqual(third_serials_wizard.lot_name, 'TEST0000006')

    @freeze_time('2024-02-03')
    def test_use_interpolated_prefix_in_subcontracting_productions(self):
        """
        Test that prefixes are correctly interpolated when generating a subcontracting MO
        """
        warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
        receipt, mo = self.generate_subcontracting_receipt_and_mo(2, warehouse)
        self.finished.lot_sequence_id.prefix = '%(day)s-%(month)s-'
        self.finished.lot_sequence_id.number_next_actual = 1
        serials_wizard = Form.from_action(self.env, mo.action_generate_serial())
        self.assertEqual(serials_wizard.lot_name, '03-02-0000001')
        serials_wizard.save().action_generate_serial_numbers()
        serials_wizard.save().action_apply()
        self.assertRecordValues(mo._get_subcontract_move().lot_ids.sorted('name'), [
            {'name': '03-02-0000001'},
            {'name': '03-02-0000002'},
        ])
        self.assertRecordValues(receipt.move_ids.lot_ids.sorted('name'), [
            {'name': '03-02-0000001'},
            {'name': '03-02-0000002'},
        ])
        receipt.button_validate()
        self.assertEqual(self.env['stock.quant']._get_available_quantity(self.finished, warehouse.lot_stock_id), 2)
        self.assertRecordValues(self.env['stock.lot'].search([('product_id', '=', self.finished.id)]).sorted('name'), [
            {'name': '03-02-0000001'},
            {'name': '03-02-0000002'},
        ])
        self.assertEqual(self.finished.next_serial, '0000003')

    def test_subcontract_move_lines_are_linked_to_picking(self):
        """Test to ensure that when we generate mass serial numbers for a
        subcontracting order, the created move lines are linked to the picking.

        Also ensure that applying the serial number generation wizard without
        generating any lot/serial numbers raises a UserError."""
        warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
        receipt = self.env['stock.picking'].create({
            'picking_type_id': warehouse.in_type_id.id,
            'partner_id': self.subcontractor.id,
            'move_ids': [Command.create({
                'product_id': self.finished.id,
                'product_uom_qty': 3,
            })],
        })
        receipt.action_confirm()
        mo = self.env['mrp.production'].browse(receipt.move_ids.action_show_subcontract_details()['res_id'])

        self.finished.lot_sequence_id.number_next_actual = 1
        wizard = Form.from_action(self.env, mo.action_generate_serial()).save()
        # Applying the wizard without generating any lot/serial numbers should raise a UserError.
        with self.assertRaises(UserError):
            wizard.action_apply()
        wizard.action_generate_serial_numbers()
        wizard.action_apply()
        self.assertRecordValues(mo._get_subcontract_move().lot_ids, [
            {'name': '0000001'},
            {'name': '0000002'},
            {'name': '0000003'},
        ])
        receipt.button_validate()
        self.assertEqual(receipt.move_line_ids.lot_id, mo._get_subcontract_move().lot_ids)
