from odoo.exceptions import UserError
from odoo.tests import Form, TransactionCase


class TestSubcontractingPurchaseFlows(TransactionCase):
    def setUp(self):
        super().setUp()

        self.subcontractor = self.env["res.partner"].create(
            {"name": "SuperSubcontractor"}
        )

        self.finished, self.compo = self.env["product.product"].create(
            [
                {
                    "name": "SuperProduct",
                    "type": "product",
                },
                {
                    "name": "Component",
                    "type": "consu",
                },
            ]
        )

        self.bom = self.env["mrp.bom"].create(
            {
                "product_tmpl_id": self.finished.product_tmpl_id.id,
                "type": "subcontract",
                "subcontractor_ids": [(6, 0, self.subcontractor.ids)],
                "bom_line_ids": [
                    (
                        0,
                        0,
                        {
                            "product_id": self.compo.id,
                            "product_qty": 1,
                        },
                    )
                ],
            }
        )

    def test_purchase_and_return(self):
        """
        The user buys 10 x a subcontracted product P. He receives the 10
        products and then does a return with 3 x P. The test ensures that
        the unbuild is created with the correct quantities and states
        """
        po = self.env["purchase.order"].create(
            {
                "partner_id": self.subcontractor.id,
                "order_line": [
                    (
                        0,
                        0,
                        {
                            "name": self.finished.name,
                            "product_id": self.finished.id,
                            "product_uom_qty": 10,
                            "product_uom": self.finished.uom_id.id,
                            "price_unit": 1,
                        },
                    )
                ],
            }
        )
        po.button_confirm()

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

        receipt = po.picking_ids
        receipt.move_ids.quantity_done = 10
        receipt.button_validate()

        return_form = Form(
            self.env["stock.return.picking"].with_context(
                active_id=receipt.id, active_model="stock.picking"
            )
        )
        with return_form.product_return_moves.edit(0) as line:
            line.quantity = 3
        return_wizard = return_form.save()
        return_id, _ = return_wizard._create_returns()

        return_picking = self.env["stock.picking"].browse(return_id)
        return_picking.move_ids.quantity_done = 3
        subcontractor_location = self.subcontractor.property_stock_subcontractor
        unbuild = self.env["mrp.unbuild"].search([("bom_id", "=", self.bom.id)])

        self.assertTrue(unbuild)
        self.assertEqual(
            unbuild.state, "draft", "The state of the unbuild should be draft"
        )
        self.assertEqual(
            unbuild.product_qty, 3, "The quantity of the unbuild should be 3"
        )
        self.assertEqual(
            unbuild.location_id,
            subcontractor_location,
            "The source location of the unbuild should be the property stock "
            "of the subcontractor",
        )
        self.assertEqual(
            unbuild.location_dest_id,
            subcontractor_location,
            "The destination location of the unbuild should be the property "
            "stock of the subcontractor",
        )

        return_picking.button_validate()

        self.assertEqual(self.finished.qty_available, 7.0)
        self.assertEqual(po.order_line.qty_received, 7.0)
        self.assertEqual(
            unbuild.state, "done", "The state of the unbuild should be done"
        )

        move = return_picking.move_ids
        self.assertEqual(
            move.location_id,
            receipt.location_dest_id,
            "The source location of the stock move should be the same as "
            "destination location of the original purchase",
        )
        self.assertEqual(
            move.location_dest_id,
            subcontractor_location,
            "The destination location of the stock move should be the property "
            "stock of the subcontractor",
        )

        # Call the action to view the layers associated to the pickings
        result1 = return_picking.action_view_stock_valuation_layers()
        result2 = receipt.action_view_stock_valuation_layers()
        layers1 = result1["domain"][2][2]
        layers2 = result2["domain"][2][2]
        self.assertTrue(
            layers1,
        )
        self.assertTrue(
            layers2,
        )

    def test_purchase_partial_receipt_and_refund(self):
        po = self.env["purchase.order"].create(
            {
                "partner_id": self.subcontractor.id,
                "order_line": [
                    (
                        0,
                        0,
                        {
                            "name": self.finished.name,
                            "product_id": self.finished.id,
                            "product_uom_qty": 10,
                            "product_qty": 10,
                            "product_uom": self.finished.uom_id.id,
                            "price_unit": 1,
                        },
                    )
                ],
            }
        )
        po.button_confirm()

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

        receipt = po.picking_ids.filtered(lambda x: x.state != "done")
        receipt.move_ids.quantity_done = 3
        result_dict = receipt.button_validate()
        self.env["stock.backorder.confirmation"].with_context(
            **result_dict["context"]
        ).process()
        self.assertEqual(po.order_line.qty_received, 3)

        receipt = po.picking_ids.filtered(lambda x: x.state != "done")
        receipt.move_ids.quantity_done = 3
        picking_to_return = receipt
        result_dict = receipt.button_validate()
        self.env["stock.backorder.confirmation"].with_context(
            **result_dict["context"]
        ).process()
        self.assertEqual(po.order_line.qty_received, 6)

        receipt = po.picking_ids.filtered(lambda x: x.state != "done")
        receipt.move_ids.quantity_done = 3
        result_dict = receipt.button_validate()
        self.env["stock.backorder.confirmation"].with_context(
            **result_dict["context"]
        ).process()
        self.assertEqual(po.order_line.qty_received, 9)

        self.assertEqual(len(po.picking_ids), 4)

        return_wizard = (
            self.env["stock.return.picking"]
            .with_context(
                active_id=picking_to_return.id, active_ids=picking_to_return.ids
            )
            .create(
                {
                    "location_id": picking_to_return.location_id.id,
                    "picking_id": picking_to_return.id,
                }
            )
        )
        return_wizard._onchange_picking_id()
        with self.assertRaises(UserError):
            return_id, _ = return_wizard._create_returns()

        # This part cannot be tested since we cannot unbuild
        # subcontracting orders with more than one origin.
        #
        # return_picking = self.env["stock.picking"].browse(return_id)
        # return_picking.move_ids.quantity_done = 3
        # return_picking.button_validate()
        #
        # self.assertEqual(po.order_line.qty_received, 6)
        #
        # mo = picking_to_return.mapped("move_ids.move_orig_ids.production_id")
        # unbuild = self.env["mrp.unbuild"].search([("mo_id", "in", mo.ids)])
        # self.assertTrue(unbuild.exists())


class TestSubcontractingTracking(TransactionCase):
    def setUp(self):
        super(TestSubcontractingTracking, self).setUp()
        # 1: Create a subcontracting partner
        main_company_1 = self.env["res.partner"].create({"name": "main_partner"})
        self.subcontractor_partner1 = self.env["res.partner"].create(
            {
                "name": "Subcontractor 1",
                "parent_id": main_company_1.id,
                "company_id": self.env.ref("base.main_company").id,
            }
        )

        # 2. Create a BOM of subcontracting type
        # 2.1. Comp1 has tracking by lot
        self.comp1_sn = self.env["product.product"].create(
            {
                "name": "Component1",
                "type": "product",
                "categ_id": self.env.ref("product.product_category_all").id,
                "tracking": "serial",
            }
        )
        self.comp2 = self.env["product.product"].create(
            {
                "name": "Component2",
                "type": "product",
                "categ_id": self.env.ref("product.product_category_all").id,
            }
        )

        # 2.2. Finished prodcut has tracking by serial number
        self.finished_product = self.env["product.product"].create(
            {
                "name": "finished",
                "type": "product",
                "categ_id": self.env.ref("product.product_category_all").id,
                "tracking": "lot",
            }
        )
        bom_form = Form(self.env["mrp.bom"])
        bom_form.type = "subcontract"
        bom_form.subcontractor_ids.add(self.subcontractor_partner1)
        bom_form.product_tmpl_id = self.finished_product.product_tmpl_id
        with bom_form.bom_line_ids.new() as bom_line:
            bom_line.product_id = self.comp1_sn
            bom_line.product_qty = 1
        with bom_form.bom_line_ids.new() as bom_line:
            bom_line.product_id = self.comp2
            bom_line.product_qty = 1
        self.bom_tracked = bom_form.save()

    def test_purchase_and_return_with_serial_numbers(self):
        """
        The user buys one subcontracted product P with serial number.
        Then does the return . The test ensures that the unbuild is
        created with the correct quantities, serial number of the product and states
        """
        # Create a receipt picking from the subcontractor
        picking_form = Form(self.env["stock.picking"])
        picking_form.picking_type_id = self.env.ref("stock.picking_type_in")
        picking_form.partner_id = self.subcontractor_partner1
        with picking_form.move_ids_without_package.new() as move:
            move.product_id = self.finished_product
            move.product_uom_qty = 1
        picking_receipt = picking_form.save()
        picking_receipt.action_confirm()

        # We should be able to call the 'record_components' button
        self.assertTrue(picking_receipt.display_action_record_components)

        # Check the created manufacturing order
        mo = self.env["mrp.production"].search([("bom_id", "=", self.bom_tracked.id)])
        self.assertEqual(len(mo), 1)
        self.assertEqual(len(mo.picking_ids), 0)
        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)

        # Create a RR
        pg1 = self.env["procurement.group"].create({})
        self.env["stock.warehouse.orderpoint"].create(
            {
                "name": "xxx",
                "product_id": self.comp1_sn.id,
                "product_min_qty": 0,
                "product_max_qty": 0,
                "location_id": self.env.user.company_id.subcontracting_location_id.id,
                "group_id": pg1.id,
            }
        )

        # Run the scheduler and check the created picking
        self.env["procurement.group"].run_scheduler()
        picking = self.env["stock.picking"].search([("group_id", "=", pg1.id)])
        self.assertEqual(len(picking), 1)
        self.assertEqual(picking.picking_type_id, wh.subcontracting_resupply_type_id)

        lot_id = self.env["stock.lot"].create(
            {
                "name": "lot1",
                "product_id": self.finished_product.id,
                "company_id": self.env.company.id,
            }
        )
        serial_id = self.env["stock.lot"].create(
            {
                "name": "lot1",
                "product_id": self.comp1_sn.id,
                "company_id": self.env.company.id,
            }
        )

        action = picking_receipt.action_record_components()
        mo = self.env["mrp.production"].browse(action["res_id"])
        mo_form = Form(mo.with_context(**action["context"]), view=action["view_id"])
        mo_form.qty_producing = 1
        mo_form.lot_producing_id = lot_id
        with mo_form.move_line_raw_ids.edit(0) as ml:
            ml.lot_id = serial_id
        mo = mo_form.save()
        mo.subcontracting_record_component()

        # We should not be able to call the 'record_components' button
        self.assertEqual(picking_receipt.display_action_record_components, "hide")

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

        return_form = Form(
            self.env["stock.return.picking"].with_context(
                active_id=picking_receipt.id, active_model="stock.picking"
            )
        )
        with return_form.product_return_moves.edit(0) as line:
            line.quantity = 1
        return_wizard = return_form.save()
        return_id, _ = return_wizard._create_returns()

        return_picking = self.env["stock.picking"].browse(return_id)
        return_picking.move_ids.quantity_done = 1
        subcontractor_location = (
            self.subcontractor_partner1.property_stock_subcontractor
        )
        unbuild = self.env["mrp.unbuild"].search([("bom_id", "=", self.bom_tracked.id)])

        self.assertTrue(unbuild)
        self.assertEqual(
            unbuild.state, "draft", "The state of the unbuild should be draft"
        )
        self.assertEqual(
            unbuild.product_qty, 1, "The quantity of the unbuild should be 1"
        )
        self.assertEqual(
            unbuild.location_id,
            subcontractor_location,
            "The source location of the unbuild should be the property stock "
            "of the subcontractor",
        )
        self.assertEqual(
            unbuild.location_dest_id,
            subcontractor_location,
            "The destination location of the unbuild should be the property "
            "stock of the subcontractor",
        )
        return_picking.move_line_ids_without_package.lot_id = lot_id
        return_picking.button_validate()

        self.assertEqual(
            unbuild.state, "done", "The state of the unbuild should be done"
        )

        move = return_picking.move_ids
        self.assertEqual(
            move.location_id,
            picking_receipt.location_dest_id,
            "The source location of the stock move should be the same as "
            "destination location of the original purchase",
        )
        self.assertEqual(
            move.location_dest_id,
            subcontractor_location,
            "The destination location of the stock move should be the property "
            "stock of the subcontractor",
        )
