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

from odoo import Command
from odoo.addons.stock_landed_costs.tests.common import TestStockLandedCostsCommon
from odoo.fields import Date
from odoo.tests import tagged, Form


@tagged('post_install', '-at_install')
class TestStockLandedCostsRounding(TestStockLandedCostsCommon):

    def test_stock_landed_costs_rounding(self):
        # In order to test the rounding in landed costs feature of stock, I create 2 landed cost

        # Define undivisible units
        product_uom_unit_round_1 = self.env.ref('uom.product_uom_unit')
        product_uom_unit_round_1.write({
            'name': 'Undivisible Units',
            'rounding': 1.0,
        })

        # I create 2 products with different cost prices and configure them for real_time
        # valuation and real price costing method
        product_landed_cost_3 = self.env['product.product'].create({
            'name': "LC product 3",
            'uom_id': product_uom_unit_round_1.id,
        })
        product_landed_cost_3.product_tmpl_id.categ_id.property_cost_method = 'fifo'
        product_landed_cost_3.product_tmpl_id.categ_id.property_stock_account_input_categ_id = self.company_data['default_account_expense']
        product_landed_cost_3.product_tmpl_id.categ_id.property_stock_account_output_categ_id = self.company_data['default_account_revenue']

        product_landed_cost_4 = self.env['product.product'].create({
            'name': "LC product 4",
            'uom_id': product_uom_unit_round_1.id,
        })
        product_landed_cost_4.product_tmpl_id.categ_id.property_cost_method = 'fifo'
        product_landed_cost_4.product_tmpl_id.categ_id.property_valuation = 'real_time'
        product_landed_cost_4.product_tmpl_id.categ_id.property_stock_account_input_categ_id = self.company_data['default_account_expense']
        product_landed_cost_4.product_tmpl_id.categ_id.property_stock_account_output_categ_id = self.company_data['default_account_revenue']

        picking_default_vals = self.env['stock.picking'].default_get(list(self.env['stock.picking'].fields_get()))

        # I create 2 pickings moving those products
        vals = dict(picking_default_vals, **{
            'name': 'LC_pick_3',
            'picking_type_id': self.warehouse.in_type_id.id,
            'move_ids': [(0, 0, {
                'product_id': product_landed_cost_3.id,
                'product_uom_qty': 13,
                'product_uom': product_uom_unit_round_1.id,
                'location_id': self.ref('stock.stock_location_customers'),
                'location_dest_id': self.warehouse.lot_stock_id.id,
            })],
        })
        picking_landed_cost_3 = self.env['stock.picking'].new(vals)
        picking_landed_cost_3._onchange_picking_type()
        picking_landed_cost_3.move_ids._onchange_product_id()
        picking_landed_cost_3.move_ids.name = 'move 3'
        vals = picking_landed_cost_3._convert_to_write(picking_landed_cost_3._cache)
        picking_landed_cost_3 = self.env['stock.picking'].create(vals)

        vals = dict(picking_default_vals, **{
            'name': 'LC_pick_4',
            'picking_type_id': self.warehouse.in_type_id.id,
            'move_ids': [(0, 0, {
                'product_id': product_landed_cost_4.id,
                'product_uom_qty': 1,
                'product_uom': self.ref('uom.product_uom_dozen'),
                'location_id': self.ref('stock.stock_location_customers'),
                'location_dest_id': self.warehouse.lot_stock_id.id,
                'price_unit': 17.00 / 12.00,
            })],
        })
        picking_landed_cost_4 = self.env['stock.picking'].new(vals)
        picking_landed_cost_4._onchange_picking_type()
        picking_landed_cost_4.move_ids._onchange_product_id()
        picking_landed_cost_4.move_ids.name = 'move 4'
        vals = picking_landed_cost_4._convert_to_write(picking_landed_cost_4._cache)
        picking_landed_cost_4 = self.env['stock.picking'].create(vals)

        # We perform all the tests for LC_pick_3
        # I receive picking LC_pick_3, and check how many quants are created
        picking_landed_cost_3.move_ids.price_unit = 1.0
        picking_landed_cost_3.action_confirm()
        picking_landed_cost_3.action_assign()
        picking_landed_cost_3._action_done()

        virtual_interior_design = self.env['product.product'].create({'name': 'Virtual Interior Design'})

        # I create a landed cost for picking 3
        default_vals = self.env['stock.landed.cost'].default_get(list(self.env['stock.landed.cost'].fields_get()))
        default_vals.update({
            'picking_ids': [picking_landed_cost_3.id],
            'account_journal_id': self.expenses_journal,
            'cost_lines': [(0, 0, {'product_id': virtual_interior_design.id})],
            'valuation_adjustment_lines': [],
        })
        stock_landed_cost_2 = self.env['stock.landed.cost'].new(default_vals)
        stock_landed_cost_2.cost_lines.onchange_product_id()
        stock_landed_cost_2.cost_lines.name = 'equal split'
        stock_landed_cost_2.cost_lines.split_method = 'equal'
        stock_landed_cost_2.cost_lines.price_unit = 15
        vals = stock_landed_cost_2._convert_to_write(stock_landed_cost_2._cache)
        stock_landed_cost_2 = self.env['stock.landed.cost'].create(vals)

        # I compute the landed cost using Compute button
        stock_landed_cost_2.compute_landed_cost()

        # I check the valuation adjustment lines
        for valuation in stock_landed_cost_2.valuation_adjustment_lines:
            self.assertEqual(valuation.additional_landed_cost, 15)

        # I confirm the landed cost
        stock_landed_cost_2.button_validate()

        # I check that the landed cost is now "Closed" and that it has an accounting entry
        self.assertEqual(stock_landed_cost_2.state, 'done')
        self.assertTrue(stock_landed_cost_2.account_move_id)

        # We perform all the tests for LC_pick_4
        # I receive picking LC_pick_4, and check how many quants are created
        picking_landed_cost_4.move_ids.price_unit = 17.0/12.0
        picking_landed_cost_4.action_confirm()
        picking_landed_cost_4.action_assign()
        picking_landed_cost_4._action_done()

        # I create a landed cost for picking 4
        default_vals = self.env['stock.landed.cost'].default_get(list(self.env['stock.landed.cost'].fields_get()))
        default_vals.update({
            'picking_ids': [picking_landed_cost_4.id],
            'account_journal_id': self.expenses_journal,
            'cost_lines': [(0, 0, {'product_id': virtual_interior_design.id})],
            'valuation_adjustment_lines': [],
        })
        stock_landed_cost_3 = self.env['stock.landed.cost'].new(default_vals)
        stock_landed_cost_3.cost_lines.onchange_product_id()
        stock_landed_cost_3.cost_lines.name = 'equal split'
        stock_landed_cost_3.cost_lines.split_method = 'equal'
        stock_landed_cost_3.cost_lines.price_unit = 11
        vals = stock_landed_cost_3._convert_to_write(stock_landed_cost_3._cache)
        stock_landed_cost_3 = self.env['stock.landed.cost'].create(vals)

        # I compute the landed cost using Compute button
        stock_landed_cost_3.compute_landed_cost()

        # I check the valuation adjustment lines
        for valuation in stock_landed_cost_3.valuation_adjustment_lines:
            self.assertEqual(valuation.additional_landed_cost, 11)

        # I confirm the landed cost
        stock_landed_cost_3.button_validate()

        # I check that the landed cost is now "Closed" and that it has an accounting entry
        self.assertEqual(stock_landed_cost_3.state, 'done')
        self.assertTrue(stock_landed_cost_3.account_move_id)

    def test_stock_landed_costs_rounding_02(self):
        """ The landed costs should be correctly computed, even when the decimal accuracy
        of the deciaml price is increased. """
        self.env.ref("product.decimal_price").digits = 4

        fifo_pc = self.env['product.category'].create({
            'name': 'Fifo Category',
            'parent_id': self.env.ref("product.product_category_all").id,
            'property_valuation': 'real_time',
            'property_cost_method': 'fifo',
        })

        products = self.Product.create([{
            'name': 'Super Product %s' % price,
            'categ_id': fifo_pc.id,
            'is_storable': True,
            'standard_price': price,
        } for price in [0.91, 0.93, 75.17, 20.54]])

        landed_product = self.Product.create({
            'name': 'Landed Costs',
            'type': 'service',
            'landed_cost_ok': True,
            'split_method_landed_cost': 'by_quantity',
            'standard_price': 1000.0,
        })

        po = self.env['purchase.order'].create({
            'partner_id': self.partner_a.id,
            'order_line': [(0, 0, {
                'product_id': product.id,
                'product_qty': qty,
                'price_unit': product.standard_price,
            }) for product, qty in zip(products, [6, 6, 3, 6])]
        })
        po.button_confirm()

        po.picking_ids.button_validate()

        lc_form = Form(self.LandedCost)
        lc_form.picking_ids.add(po.picking_ids)
        with lc_form.cost_lines.new() as line:
            line.product_id = landed_product
        lc = lc_form.save()
        lc.compute_landed_cost()

        self.assertEqual(sum(lc.valuation_adjustment_lines.mapped('additional_landed_cost')), 1000.0)

    def test_stock_landed_costs_rounding_03(self):
        """
        Storable AVCO product
        Receive:
            5 @ 5
            5 @ 8
            5 @ 7
            20 @ 7.33
        Add landed cost of $5 to each receipt (except the first one)
        Deliver:
            23
            2
            10
        At the end, the SVL value should be zero
        """
        self.product_a.is_storable = True
        self.product_a.categ_id.property_cost_method = 'average'

        stock_location = self.warehouse.lot_stock_id
        supplier_location_id = self.ref('stock.stock_location_suppliers')
        customer_location_id = self.ref('stock.stock_location_customers')

        receipts = self.env['stock.picking'].create([{
            'picking_type_id': self.warehouse.in_type_id.id,
            'location_id': supplier_location_id,
            'location_dest_id': stock_location.id,
            'move_ids': [(0, 0, {
                'name': self.product_a.name,
                'product_id': self.product_a.id,
                'price_unit': price,
                'product_uom': self.product_a.uom_id.id,
                'product_uom_qty': qty,
                'location_id': supplier_location_id,
                'location_dest_id': stock_location.id,
            })]
        } for qty, price in [
            (5, 5.0),
            (5, 8.0),
            (5, 7.0),
            (20, 7.33),
        ]])

        receipts.action_confirm()
        for m in receipts.move_ids:
            m.quantity = m.product_uom_qty
        receipts.button_validate()

        landed_costs = self.env['stock.landed.cost'].create([{
            'picking_ids': [(6, 0, picking.ids)],
            'account_journal_id': self.expenses_journal.id,
            'cost_lines': [(0, 0, {
                'name': 'equal split',
                'split_method': 'equal',
                'price_unit': 5.0,
                'product_id': self.landed_cost.id
            })],
        } for picking in receipts[1:]])
        landed_costs.compute_landed_cost()
        landed_costs.button_validate()

        self.assertEqual(self.product_a.standard_price, 7.47)

        deliveries = self.env['stock.picking'].create([{
            'picking_type_id': self.warehouse.out_type_id.id,
            'location_id': stock_location.id,
            'location_dest_id': customer_location_id,
            'move_ids': [(0, 0, {
                'name': self.product_a.name,
                'product_id': self.product_a.id,
                'product_uom': self.product_a.uom_id.id,
                'product_uom_qty': qty,
                'location_id': stock_location.id,
                'location_dest_id': customer_location_id,
            })]
        } for qty in [23, 2, 10]])

        deliveries.action_confirm()
        for m in deliveries.move_ids:
            m.quantity = m.product_uom_qty
        deliveries.button_validate()

        self.assertEqual(self.product_a.value_svl, 0)

    def test_lc_cost_split_cumulative_rounding_diff(self):
        """ Ensure that the sum total difference of all rounding operations during the splitting of
        an LC cost allots a sensible value to each cost line.
        I.e., we don't end up with one line which bears the brunt of this difference.
        """
        product = self.env['product.product'].create({
            'name': 'product',
            'is_storable': True,
            'standard_price': 10,
            'categ_id': self.categ_real_time.id,
        })
        purchase_order = self.env['purchase.order'].create({
            'partner_id': self.partner_a.id,
            'order_line': [Command.create({
                'product_id': product.id,
                'product_qty': 1,
            }) for _ in range(6)],
        })
        purchase_order.button_confirm()
        purchase_order.picking_ids.button_validate()
        purchase_order.action_create_invoice()
        bill = purchase_order.invoice_ids
        bill.invoice_date = Date.today()
        with Form(bill) as bill_form:
            with bill_form.invoice_line_ids.new() as inv_line:
                inv_line.product_id = self.landed_cost
                inv_line.price_unit = 6.85
                inv_line.is_landed_costs_line = True
        bill.action_post()
        action = bill.button_create_landed_costs()
        lc = Form(self.env[action['res_model']].browse(action['res_id'])).save()
        lc.picking_ids = [Command.link(purchase_order.picking_ids.id)]
        lc.cost_lines.split_method = 'equal'
        lc.button_validate()
        line_costs = lc.valuation_adjustment_lines.mapped('additional_landed_cost')
        for line_cost, expected_cost in zip(line_costs, [1.14, 1.14, 1.14, 1.14, 1.14, 1.15]):
            self.assertAlmostEqual(
                line_cost,
                expected_cost,
                delta=lc.currency_id.rounding * 0.1
            )
