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

from odoo.tests import Form, tagged
from odoo.addons.mrp.tests.common import TestMrpCommon

@tagged('post_install', '-at_install')
class TestRepairTraceability(TestMrpCommon):

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.env.ref('base.group_user').write({'implied_ids': [(4, cls.env.ref('stock.group_production_lot').id)]})

    def test_tracking_repair_production(self):
        """
        Test that removing a tracked component with a repair does not block the flow of using that component in another
        bom
        """
        product_to_repair = self.env['product.product'].create({
            'name': 'product first serial to act repair',
            'tracking': 'serial',
        })
        ptrepair_lot = self.env['stock.lot'].create({
            'name': 'A1',
            'product_id': product_to_repair.id,
        })
        product_to_remove = self.env['product.product'].create({
            'name': 'other first serial to remove with repair',
            'tracking': 'serial',
        })
        ptremove_lot = self.env['stock.lot'].create({
            'name': 'B2',
            'product_id': product_to_remove.id,
        })
        # Create a manufacturing order with product (with SN A1)
        mo_form = Form(self.env['mrp.production'])
        mo_form.product_id = product_to_repair
        with mo_form.move_raw_ids.new() as move:
            move.product_id = product_to_remove
            move.product_uom_qty = 1
        mo = mo_form.save()
        mo.action_confirm()
        # Set serial to A1
        mo.lot_producing_id = ptrepair_lot
        # Set component serial to B2
        mo.move_raw_ids.move_line_ids.lot_id = ptremove_lot
        mo.move_raw_ids.picked = True
        mo.button_mark_done()

        with Form(self.env['repair.order']) as ro_form:
            ro_form.product_id = product_to_repair
            ro_form.lot_id = ptrepair_lot  # Repair product Serial A1
            with ro_form.move_ids.new() as operation:
                operation.repair_line_type = 'remove'
                operation.product_id = product_to_remove
            ro = ro_form.save()
        ro.action_validate()
        ro.move_ids[0].lot_ids = ptremove_lot # Remove product Serial B2 from the product.
        ro.action_repair_start()
        ro.move_ids.picked = True
        ro.action_repair_end()

        # Create a manufacturing order with product (with SN A2)
        mo2_form = Form(self.env['mrp.production'])
        mo2_form.product_id = product_to_repair
        with mo2_form.move_raw_ids.new() as move:
            move.product_id = product_to_remove
            move.product_uom_qty = 1
        mo2 = mo2_form.save()
        mo2.action_confirm()
        # Set serial to A2
        mo2.lot_producing_id = self.env['stock.lot'].create({
            'name': 'A2',
            'product_id': product_to_repair.id,
        })
        # Set component serial to B2 again, it is possible
        mo2.move_raw_ids.move_line_ids.lot_id = ptremove_lot
        mo2.move_raw_ids.picked = True
        # We are not forbidden to use that serial number, so nothing raised here
        mo2.button_mark_done()

    def test_mo_with_used_sn_component(self):
        """
        Suppose a tracked-by-usn component has been used to produce a product. Then, using a repair order,
        this component is removed from the product and returned as available stock. The user should be able to
        use the component in a new MO
        """
        def produce_one(product, component):
            mo_form = Form(self.env['mrp.production'])
            mo_form.product_id = product
            with mo_form.move_raw_ids.new() as raw_line:
                raw_line.product_id = component
                raw_line.product_uom_qty = 1
            mo = mo_form.save()
            mo.action_confirm()
            mo.action_assign()
            mo.move_raw_ids.picked = True
            mo.button_mark_done()
            return mo


        stock_location = self.env.ref('stock.stock_location_stock')

        finished, component = self.env['product.product'].create([{
            'name': 'Finished Product',
            'is_storable': True,
        }, {
            'name': 'SN Componentt',
            'is_storable': True,
            'tracking': 'serial',
        }])

        sn_lot = self.env['stock.lot'].create({
            'product_id': component.id,
            'name': 'USN01',
        })
        self.env['stock.quant']._update_available_quantity(component, stock_location, 1, lot_id=sn_lot)

        mo = produce_one(finished, component)
        self.assertEqual(mo.state, 'done')
        self.assertEqual(mo.move_raw_ids.lot_ids, sn_lot)
        ro_form = Form(self.env['repair.order'])
        ro_form.product_id = finished
        with ro_form.move_ids.new() as ro_line:
            ro_line.repair_line_type = 'recycle'
            ro_line.product_id = component
        ro = ro_form.save()
        ro.action_validate()
        ro.move_ids[0].lot_ids = sn_lot
        ro.action_repair_start()
        ro.move_ids.picked = True
        ro.action_repair_end()
        mo = produce_one(finished, component)
        self.assertEqual(mo.state, 'done')
        self.assertEqual(mo.move_raw_ids.lot_ids, sn_lot)
        # Now, we will test removing the component and putting it back in stock,
        # then placing it back into the product and removing it a second time.
        # The user should be able to use the component in a new MO.
        ro_form = Form(self.env['repair.order'])
        ro_form.product_id = finished
        with ro_form.move_ids.new() as ro_line:
            ro_line.repair_line_type = 'recycle'
            ro_line.product_id = component
            ro_line.location_dest_id = stock_location
        ro = ro_form.save()
        ro.action_validate()
        ro.move_ids[0].lot_ids = sn_lot
        ro.action_repair_start()
        ro.action_repair_end()
        self.assertEqual(ro.state, 'done')
        # Add the component into the product
        ro_form = Form(self.env['repair.order'])
        ro_form.product_id = finished
        with ro_form.move_ids.new() as ro_line:
            ro_line.repair_line_type = 'add'
            ro_line.product_id = component
            ro_line.location_id = stock_location
        ro = ro_form.save()
        ro.action_validate()
        ro.move_ids[0].lot_ids = sn_lot
        ro.action_repair_start()
        ro.action_repair_end()
        self.assertEqual(ro.state, 'done')
        # Removing it a second time
        ro_form = Form(self.env['repair.order'])
        ro_form.product_id = finished
        with ro_form.move_ids.new() as ro_line:
            ro_line.repair_line_type = 'recycle'
            ro_line.product_id = component
            ro_line.location_dest_id = stock_location
        ro = ro_form.save()
        ro.action_validate()
        ro.move_ids[0].lot_ids = sn_lot
        ro.action_repair_start()
        ro.action_repair_end()
        self.assertEqual(ro.state, 'done')
        # check if the removed component can be used in a new MO
        mo = produce_one(finished, component)
        self.assertEqual(mo.state, 'done')
        self.assertEqual(mo.move_raw_ids.lot_ids, sn_lot)

    def test_mo_with_used_sn_component_02(self):
        """
        Suppose a tracked-by-usn component has been remvoed in a repair order. Then, using to produce a product,
        but this product has been unbuild. The user should be able to use the component in a new MO
        """
        finished, component = self.env['product.product'].create([{
            'name': 'Finished Product',
            'is_storable': True,
        }, {
            'name': 'SN Componentt',
            'is_storable': True,
            'tracking': 'serial',
        }])

        sn_lot = self.env['stock.lot'].create({
            'product_id': component.id,
            'name': 'USN01',
            'company_id': self.env.company.id,
        })

        # create a repair order
        ro_form = Form(self.env['repair.order'])
        ro_form.product_id = self.product_1
        with ro_form.move_ids.new() as ro_line:
            ro_line.repair_line_type = 'remove'
            ro_line.product_id = component
        ro = ro_form.save()
        ro.action_validate()
        ro.move_ids[0].lot_ids = sn_lot
        ro.action_repair_start()
        ro.action_repair_end()

        stock_location = self.env.ref('stock.stock_location_stock')
        self.env['stock.quant']._update_available_quantity(component, stock_location, 1, lot_id=sn_lot)
        self.assertEqual(component.qty_available, 1)

        # create a manufacturing order
        mo_form = Form(self.env['mrp.production'])
        mo_form.product_id = finished
        with mo_form.move_raw_ids.new() as raw_line:
            raw_line.product_id = component
            raw_line.product_uom_qty = 1
        mo = mo_form.save()
        mo.action_confirm()
        mo.action_assign()
        mo.move_raw_ids.move_line_ids.quantity = 1
        mo.move_raw_ids.picked = True
        mo.button_mark_done()
        self.assertEqual(mo.state, 'done')
        self.assertEqual(mo.move_raw_ids.lot_ids, sn_lot)
        # unbuild the mo
        unbuild_form = Form(self.env['mrp.unbuild'])
        unbuild_form.mo_id = mo
        unbuild_form.save().action_unbuild()
        # create another mo and use the same SN
        mo_form = Form(self.env['mrp.production'])
        mo_form.product_id = finished
        with mo_form.move_raw_ids.new() as raw_line:
            raw_line.product_id = component
            raw_line.product_uom_qty = 1
        mo = mo_form.save()
        mo.action_confirm()
        mo.action_assign()
        mo.move_raw_ids.move_line_ids.quantity = 1
        mo.move_raw_ids.picked = True
        mo.button_mark_done()
        self.assertEqual(mo.state, 'done')
        self.assertEqual(mo.move_raw_ids.lot_ids, sn_lot)

    def test_mo_with_unscrapped_tracked_component(self):
        """
        Tracked-by-sn component
        Use it in a MO
        Repair the finished product:
            Remove the component, destination: scrap location
        Move the component back to the stock
        Use it in a MO
        """
        stock_location = self.env.ref('stock.stock_location_stock')
        scrap_location = self.env['stock.location'].search([('company_id', '=', self.env.company.id), ('scrap_location', '=', True)], limit=1)
        internal_type = self.env.ref('stock.picking_type_internal')

        finished = self.bom_4.product_id
        component = self.bom_4.bom_line_ids.product_id
        component.write({
            'is_storable': True,
            'tracking': 'serial',
        })

        sn_lot = self.env['stock.lot'].create({
            'product_id': component.id,
            'name': 'SN01',
            'company_id': self.env.company.id,
        })
        self.env['stock.quant']._update_available_quantity(component, stock_location, 1, lot_id=sn_lot)

        mo_form = Form(self.env['mrp.production'])
        mo_form.bom_id = self.bom_4
        mo = mo_form.save()
        mo.action_confirm()
        mo.qty_producing = 1
        mo.move_raw_ids.move_line_ids.quantity = 1
        mo.move_raw_ids.move_line_ids.picked = True
        mo.button_mark_done()

        ro = self.env['repair.order'].create({
            'product_id': finished.id,
            'picking_type_id': self.warehouse_1.repair_type_id.id,
            'move_ids': [
                (0, 0, {
                    'name': 'foo',
                    'product_id': component.id,
                    'lot_ids': [(4, sn_lot.id)],
                    'repair_line_type': 'remove',
                    'location_dest_id': scrap_location.id,
                    'price_unit': 0,
                })
            ],
        })
        ro.action_validate()
        ro.action_repair_start()
        ro.action_repair_end()

        sm = self.env['stock.move'].create({
            'name': component.name,
            'product_id': component.id,
            'product_uom_qty': 1,
            'product_uom': component.uom_id.id,
            'picking_id': internal_type.id,
            'location_id': scrap_location.id,
            'location_dest_id': stock_location.id,
        })
        sm._action_confirm()
        sm.move_line_ids.write({
            'quantity': 1.0,
            'lot_id': sn_lot.id,
            'picked': True,
        })
        sm._action_done()

        mo_form = Form(self.env['mrp.production'])
        mo_form.bom_id = self.bom_4
        mo = mo_form.save()
        mo.action_confirm()
        mo.qty_producing = 1
        mo.move_raw_ids.move_line_ids.quantity = 1
        mo.move_raw_ids.move_line_ids.picked = True
        mo.button_mark_done()

        self.assertRecordValues(mo.move_raw_ids.move_line_ids, [
            {'product_id': component.id, 'lot_id': sn_lot.id, 'quantity': 1.0, 'state': 'done'},
        ])

    def test_repair_with_consumable_kit(self):
        """Test that a consumable kit can be repaired."""
        self.assertEqual(self.bom_2.type, 'phantom')
        kit_product = self.bom_2.product_id
        kit_product.type = 'consu'
        self.assertEqual(kit_product.type, 'consu')
        ro = self.env['repair.order'].create({
            'product_id': kit_product.id,
            'picking_type_id': self.warehouse_1.repair_type_id.id,
        })
        ro.action_validate()
        ro.action_repair_start()
        ro.action_repair_end()
        self.assertEqual(ro.state, 'done')
