# Part of Odoo. See LICENSE file for full copyright and licensing details.

import datetime

from odoo import fields
from odoo.fields import Command
from odoo.tests import Form, freeze_time, tagged

from odoo.addons.sale.tests.common import TestSaleCommon


@tagged('post_install', '-at_install')
class TestStockMoveInvoice(TestSaleCommon):

    @classmethod
    def setUpClass(cls):
        super().setUpClass()

        cls.ProductProduct = cls.env['product.product']
        cls.SaleOrder = cls.env['sale.order']
        cls.AccountJournal = cls.env['account.journal']

        cls.partner_18 = cls.env['res.partner'].create({'name': 'My Test Customer'})
        cls.product_11 = cls.env['product.product'].create({'name': 'A product to deliver'})
        cls.product_cable_management_box = cls.env['product.product'].create({
            'name': 'Another product to deliver',
            'weight': 1.0,
            'invoice_policy': 'order',
        })
        cls.product_uom_unit = cls.env.ref('uom.product_uom_unit')
        cls.product_delivery_normal = cls.env['product.product'].create({
            'name': 'Normal Delivery Charges',
            'invoice_policy': 'order',
            'type': 'service',
            'list_price': 10.0,
            'categ_id': cls.env.ref('delivery.product_category_deliveries').id,
        })
        cls.normal_delivery = cls.env['delivery.carrier'].create({
            'name': 'Normal Delivery Charges',
            'fixed_price': 10,
            'delivery_type': 'fixed',
            'product_id': cls.product_delivery_normal.id,
        })

    def test_01_delivery_stock_move(self):
        # Test if the stored fields of stock moves are computed with invoice before delivery flow
        self.sale_prepaid = self.SaleOrder.create({
            'partner_id': self.partner_18.id,
            'partner_invoice_id': self.partner_18.id,
            'partner_shipping_id': self.partner_18.id,
            'order_line': [(0, 0, {
                'name': 'Cable Management Box',
                'product_id': self.product_cable_management_box.id,
                'product_uom_qty': 2,
                'price_unit': 750.00,
            })],
        })

        # I add delivery cost in Sales order
        delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({
            'default_order_id': self.sale_prepaid.id,
            'default_carrier_id': self.normal_delivery.id,
        }))
        choose_delivery_carrier = delivery_wizard.save()
        choose_delivery_carrier.button_confirm()

        # I confirm the SO.
        self.sale_prepaid.action_confirm()
        self.sale_prepaid._create_invoices()

        # I check that the invoice was created
        self.assertEqual(len(self.sale_prepaid.invoice_ids), 1, "Invoice not created.")

        # I confirm the invoice

        self.invoice = self.sale_prepaid.invoice_ids
        self.invoice.action_post()

        # I pay the invoice.
        self.journal = self.AccountJournal.search([('type', '=', 'cash'), ('company_id', '=', self.sale_prepaid.company_id.id)], limit=1)

        register_payments = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=self.invoice.ids).create({
            'journal_id': self.journal.id,
        })
        register_payments._create_payments()

        # Check the SO after paying the invoice
        self.assertNotEqual(self.sale_prepaid.invoice_count, 0, 'order not invoiced')
        self.assertTrue(self.sale_prepaid.invoice_status == 'invoiced', 'order is not invoiced')
        self.assertEqual(len(self.sale_prepaid.picking_ids), 1, 'pickings not generated')

        # Check the stock moves
        moves = self.sale_prepaid.picking_ids.move_ids
        self.assertEqual(moves[0].product_qty, 2, 'wrong product_qty')
        self.assertEqual(moves[0].weight, 2.0, 'wrong move weight')

        # Ship
        moves.move_line_ids.write({'quantity': 2})
        self.picking = self.sale_prepaid.picking_ids._action_done()
        self.assertEqual(moves[0].move_line_ids.sale_price, 1725.0, 'wrong shipping value')

    def test_02_delivery_stock_move(self):
        # Test if SN product shipment line has the correct amount
        self.product_cable_management_box.write({
            'tracking': 'serial'
        })

        serial_numbers = self.env['stock.lot'].create([{
            'name': str(x),
            'product_id': self.product_cable_management_box.id,
        } for x in range(5)])

        self.sale_prepaid = self.SaleOrder.create({
            'partner_id': self.partner_18.id,
            'partner_invoice_id': self.partner_18.id,
            'partner_shipping_id': self.partner_18.id,
            'order_line': [(0, 0, {
                'name': 'Cable Management Box',
                'product_id': self.product_cable_management_box.id,
                'product_uom_qty': 2,
                'price_unit': 750.00,
            })],
        })

        # I add delivery cost in Sales order
        delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({
            'default_order_id': self.sale_prepaid.id,
            'default_carrier_id': self.normal_delivery.id,
        }))
        choose_delivery_carrier = delivery_wizard.save()
        choose_delivery_carrier.button_confirm()

        # I confirm the SO.
        self.sale_prepaid.action_confirm()
        moves = self.sale_prepaid.picking_ids.move_ids
        # Ship
        for ml, lot in zip(moves.move_line_ids, serial_numbers):
            ml.write({'quantity': 1, 'lot_id': lot.id})
        self.picking = self.sale_prepaid.picking_ids._action_done()
        self.assertEqual(moves[0].move_line_ids[0].sale_price, 862.5, 'wrong shipping value')

    def test_03_invoiced_status(self):
        super_product = self.env['product.product'].create({
            'name': 'Super Product',
            'invoice_policy': 'delivery',
        })
        great_product = self.env['product.product'].create({
            'name': 'Great Product',
            'invoice_policy': 'delivery',
        })

        so = self.env['sale.order'].create({
            'name': 'Sale order',
            'partner_id': self.partner_a.id,
            'partner_invoice_id': self.partner_a.id,
            'order_line': [
                (0, 0, {'name': super_product.name, 'product_id': super_product.id, 'product_uom_qty': 1, 'price_unit': 1,}),
                (0, 0, {'name': great_product.name, 'product_id': great_product.id, 'product_uom_qty': 1, 'price_unit': 1,}),
            ]
        })
        # Confirm the SO
        so.action_confirm()

        # Deliver one product and create a backorder
        self.assertEqual(sum([line.quantity for line in so.picking_ids.move_ids]), 2)
        so.picking_ids.move_ids[0].quantity = 1
        so.picking_ids.move_ids[0].picked = True
        Form.from_action(self.env, so.picking_ids.button_validate()).save().process()
        self.assertEqual(len(so.picking_ids), 2)
        self.assertEqual(sum([line.quantity for line in so.picking_ids.move_ids]), 2)

        # Invoice the delivered product
        invoice = so._create_invoices()
        invoice.action_post()
        self.assertEqual(so.invoice_status, 'no')

        # Add delivery fee
        delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({
            'default_order_id': so.id,
            'default_carrier_id': self.normal_delivery.id
        }))
        choose_delivery_carrier = delivery_wizard.save()
        choose_delivery_carrier.button_confirm()

        self.assertEqual(so.invoice_status, 'no', 'The status should still be "Nothing To Invoice"')

    def test_delivery_carrier_from_confirmed_so(self):
        """Test if adding shipping method in sale order after confirmation
           will add it in pickings too"""

        sale_order = self.SaleOrder.create({
            "partner_id": self.partner_18.id,
            "partner_invoice_id": self.partner_18.id,
            "partner_shipping_id": self.partner_18.id,
            "order_line": [(0, 0, {
                "name": "Cable Management Box",
                "product_id": self.product_cable_management_box.id,
                "product_uom_qty": 2,
                "price_unit": 750.00,
            })],
        })

        sale_order.action_confirm()
        sale_order.picking_ids.move_ids.quantity = 2
        sale_order.picking_ids.button_validate()

        # Return picking
        return_form = Form(self.env["stock.return.picking"].with_context(active_id=sale_order.picking_ids.id, active_model="stock.picking"))
        return_wizard = return_form.save()
        return_wizard.product_return_moves.quantity = 2
        action = return_wizard.action_create_returns()
        return_picking = self.env["stock.picking"].browse(action["res_id"])

        # add new product so new picking is created
        sale_order.write({
            "order_line": [(0, 0, {
                "name": "Another product to deliver",
                "product_id": self.product_11.id,
                "product_uom_qty": 2,
                "price_unit": 750.00,
            })],
        })

        # Add delivery cost in Sales order
        delivery_wizard = Form(self.env["choose.delivery.carrier"].with_context({
            "default_order_id": sale_order.id,
            "default_carrier_id": self.normal_delivery.id,
        }))
        choose_delivery_carrier = delivery_wizard.save()
        choose_delivery_carrier.button_confirm()

        # Check the carrier in picking after confirm sale order
        delivery_for_product_11 = sale_order.picking_ids.filtered(lambda p: self.product_11 in p.move_ids.product_id)
        self.assertEqual(delivery_for_product_11.carrier_id, self.normal_delivery, "The shipping method should be set in pending deliveries.")

        done_delivery = sale_order.picking_ids.filtered(lambda p: p.state == "done")
        self.assertFalse(done_delivery.carrier_id.id, "The shipping method should not be set in done deliveries.")
        self.assertFalse(return_picking.carrier_id.id, "The shipping method should not set in return pickings")

    def test_picking_weight(self):
        """Test if the picking weight is correctly computed when the product of the move changes."""
        self.product_cable_management_box.weight = 1.0
        self.product_a.weight = 2.0
        so = self.SaleOrder.create({
            "partner_id": self.partner_18.id,
            "order_line": [(0, 0, {
                "name": "Cable Management Box",
                "product_id": self.product_cable_management_box.id,
                "product_uom_qty": 1,
                "price_unit": 750.00,
            })],
        })
        so.action_confirm()
        picking = so.picking_ids
        self.assertEqual(picking.weight, 1.0, "The weight of the picking should be 1.0")
        self.product_cable_management_box.weight = 2.0
        self.assertEqual(picking.weight, 1.0, "The weight of the picking should not change")
        picking.move_ids.product_id = self.product_a
        self.assertEqual(picking.weight, 2.0, "The weight of the picking should be 2.0")

    @freeze_time("2024-06-06 11:00")
    def test_picking_change_scheduled_date(self):
        """
        Check that changing the scheduled date of a move can affect the scheduled date
        of the picking but not its sibling moves.
        """
        wh = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
        receipt = self.env['stock.picking'].create({
            'picking_type_id': wh.in_type_id.id,
            'location_id': self.ref('stock.stock_location_customers'),
            'location_dest_id': wh.lot_stock_id.id,
            'move_ids': [
                Command.create({
                    'product_id': self.product_a.id,
                    'product_uom_qty': 1,
                    'location_id': self.ref('stock.stock_location_customers'),
                    'location_dest_id': wh.lot_stock_id.id,
                }),
                Command.create({
                    'product_id': self.product_b.id,
                    'product_uom_qty': 1,
                    'location_id': self.ref('stock.stock_location_customers'),
                    'location_dest_id': wh.lot_stock_id.id,
                }),
            ],
        })
        receipt.action_confirm()
        today, yesterday = fields.Datetime.now(), fields.Datetime.now() - datetime.timedelta(days=1)
        self.assertEqual(receipt.scheduled_date, today)
        with Form(receipt) as picking_form:
            with picking_form.move_ids.edit(0) as move:
                move.date = yesterday
        self.assertEqual(receipt.scheduled_date, yesterday)
        self.assertRecordValues(receipt.move_ids, [
            {'date': yesterday},
            {'date': today},
        ])

    @freeze_time("2024-06-06 11:00")
    def test_delivery_slip_product_value(self):
        """Test that product value reported on the delivery slip is correct.
        """
        product = self.product_cable_management_box
        tax = self.company_data['default_tax_sale']
        other_currency = self.setup_other_currency('EUR', rates=[
            ('2024-06-06', 0.1),
        ])
        pricelist_in_other_curr = self.env['product.pricelist'].create({
            'name': 'Test Pricelist (EUR)',
            'currency_id': other_currency.id,
        })

        sale_order = self.env['sale.order'].create({
            'partner_id': self.partner_a.id,
            'partner_invoice_id': self.partner_a.id,
            'partner_shipping_id': self.partner_a.id,
            'pricelist_id': pricelist_in_other_curr.id,
            'order_line': [
                Command.create({
                    'name': product.name,
                    'product_id': product.id,
                    'product_uom_qty': 180,
                    'product_uom_id': product.uom_id.id,
                    'price_unit': 1.49,
                    'tax_ids': [Command.set(tax.ids)],
                })],
        })

        sale_order.action_confirm()

        # Testing full quantity, should be equal to the price total on the sale order line
        sale_order.picking_ids.move_ids.quantity = 180
        self.assertEqual(sale_order.picking_ids.move_line_ids.sale_price, sale_order.order_line.price_total, "Price on delivery slip is not correct")

        # Testing a partial quantity
        sale_order.picking_ids.move_ids.quantity = 150
        self.assertEqual(sale_order.picking_ids.move_line_ids.sale_price, 257.03, "Price on delivery slip is not correct")
