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

import time
from datetime import datetime, timedelta
from freezegun import freeze_time
from unittest.mock import patch

import odoo
from odoo import fields, exceptions, Command
from odoo.tests import Form
from odoo.tests.common import TransactionCase, tagged
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
from odoo.addons.stock.tests.common import TestStockCommon


class TestStockValuation(TransactionCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.url_extract_rec_id_and_model = TestStockCommon.url_extract_rec_id_and_model
        cls.supplier_location = cls.env.ref('stock.stock_location_suppliers')
        cls.stock_location = cls.env.ref('stock.stock_location_stock')
        cls.partner_id = cls.env['res.partner'].create({
            'name': 'Wood Corner Partner',
            'company_id': cls.env.user.company_id.id,
        })
        cls.product1 = cls.env['product.product'].create({
            'name': 'Large Desk',
            'standard_price': 1299.0,
            'list_price': 1799.0,
            # Ignore tax calculations for these tests.
            'supplier_taxes_id': False,
            'is_storable': True,
        })
        Account = cls.env['account.account']
        cls.stock_input_account = Account.create({
            'name': 'Stock Input',
            'code': 'StockIn',
            'account_type': 'asset_current',
            'reconcile': True,
        })
        cls.stock_output_account = Account.create({
            'name': 'Stock Output',
            'code': 'StockOut',
            'account_type': 'asset_current',
            'reconcile': True,
        })
        cls.stock_valuation_account = Account.create({
            'name': 'Stock Valuation',
            'code': 'StockValuation',
            'account_type': 'asset_current',
        })
        cls.stock_journal = cls.env['account.journal'].create({
            'name': 'Stock Journal',
            'code': 'STJTEST',
            'type': 'general',
        })
        cls.product1.categ_id.write({
            'property_valuation': 'real_time',
            'property_stock_account_input_categ_id': cls.stock_input_account.id,
            'property_stock_account_output_categ_id': cls.stock_output_account.id,
            'property_stock_valuation_account_id': cls.stock_valuation_account.id,
            'property_stock_journal': cls.stock_journal.id,
        })
        cls.env.ref('base.EUR').active = True


    def test_different_uom(self):
        """ Set a quantity to replenish via the "Buy" route
        where product_uom is different from purchase uom
        """

        self.env['ir.config_parameter'].sudo().set_param('stock.propagate_uom', False)

        # Create and set a new weight unit.
        kgm = self.env.ref('uom.product_uom_kgm')
        ap = self.env['uom.uom'].create({
            'category_id': kgm.category_id.id,
            'name': 'Algerian Pounds',
            'uom_type': 'bigger',
            'ratio': 2.47541,
            'rounding': 0.001,
        })
        kgm_price = 100
        ap_price = kgm_price / ap.factor

        self.product1.uom_id = ap
        self.product1.uom_po_id = kgm

        # Set vendor
        vendor = self.env['res.partner'].create(dict(name='The Replenisher'))
        supplierinfo = self.env['product.supplierinfo'].create({
            'partner_id': vendor.id,
            'price': kgm_price,
        })
        self.product1.seller_ids = [(4, supplierinfo.id, 0)]

        # Automated stock valuation
        self.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'
        self.product1.product_tmpl_id.categ_id.property_cost_method = 'fifo'

        # Create a manual replenishment
        replenishment_uom_qty = 200
        replenish_wizard = self.env['product.replenish'].with_context(default_product_tmpl_id=self.product1.product_tmpl_id.id).create({
            'product_id': self.product1.id,
            'product_tmpl_id': self.product1.product_tmpl_id.id,
            'product_uom_id': ap.id,
            'quantity': replenishment_uom_qty,
            'warehouse_id': self.env['stock.warehouse'].search([('company_id', '=', self.env.user.id)], limit=1).id,
        })
        genrated_picking = replenish_wizard.launch_replenishment()
        links = genrated_picking.get("params", {}).get("links")
        url = links and links[0].get("url", "") or ""
        purchase_order_id, model_name = self.url_extract_rec_id_and_model(url)
        last_po_id = False
        if purchase_order_id and model_name:
            last_po_id = self.env[model_name].browse(int(purchase_order_id))

        order_line = last_po_id.order_line.search([('product_id', '=', self.product1.id)])
        self.assertEqual(order_line.product_qty,
            ap._compute_quantity(replenishment_uom_qty, kgm, rounding_method='HALF-UP'),
            'Quantities does not match')

        # Recieve products
        last_po_id.button_confirm()
        picking = last_po_id.picking_ids[0]
        move = picking.move_ids[0]
        move.quantity = move.product_uom_qty
        move.picked = True
        picking.button_validate()

        self.assertEqual(move.stock_valuation_layer_ids.unit_cost,
            last_po_id.currency_id.round(ap_price),
            "Wrong Unit price")

    def test_change_unit_cost_average_1(self):
        """ Confirm a purchase order and create the associated receipt, change the unit cost of the
        purchase order before validating the receipt, the value of the received goods should be set
        according to the last unit cost.
        """
        self.product1.product_tmpl_id.categ_id.property_cost_method = 'average'
        po1 = self.env['purchase.order'].create({
            'partner_id': self.partner_id.id,
            'order_line': [
                (0, 0, {
                    'name': self.product1.name,
                    'product_id': self.product1.id,
                    'product_qty': 10.0,
                    'product_uom': self.product1.uom_po_id.id,
                    'price_unit': 100.0,
                    'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
                }),
            ],
        })
        po1.button_confirm()

        picking1 = po1.picking_ids[0]
        move1 = picking1.move_ids[0]

        # the unit price of the purchase order line is copied to the in move
        self.assertEqual(move1.price_unit, 100)

        # update the unit price on the purchase order line
        po1.order_line.price_unit = 200


        # validate the receipt
        picking1.button_validate()

        # the unit price of the valuationlayer used the latest value
        self.assertEqual(move1.stock_valuation_layer_ids.unit_cost, 200)

        self.assertEqual(self.product1.value_svl, 2000)

    def test_standard_price_change_1(self):
        """ Confirm a purchase order and create the associated receipt, change the unit cost of the
        purchase order and the standard price of the product before validating the receipt, the
        value of the received goods should be set according to the last standard price.
        """
        self.product1.product_tmpl_id.categ_id.property_cost_method = 'standard'

        # set a standard price
        self.product1.product_tmpl_id.standard_price = 10

        po1 = self.env['purchase.order'].create({
            'partner_id': self.partner_id.id,
            'order_line': [
                (0, 0, {
                    'name': self.product1.name,
                    'product_id': self.product1.id,
                    'product_qty': 10.0,
                    'product_uom': self.product1.uom_po_id.id,
                    'price_unit': 11.0,
                    'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
                }),
            ],
        })
        po1.button_confirm()

        picking1 = po1.picking_ids[0]
        move1 = picking1.move_ids[0]

        # the move's unit price reflects the purchase order line's cost even if it's useless when
        # the product's cost method is standard
        self.assertEqual(move1.price_unit, 11)

        # set a new standard price
        self.product1.product_tmpl_id.standard_price = 12

        # the unit price on the stock move is not directly updated
        self.assertEqual(move1.price_unit, 11)

        # validate the receipt
        picking1.button_validate()

        # the unit price of the valuation layer used the latest value
        self.assertEqual(move1.stock_valuation_layer_ids.unit_cost, 12)

        self.assertEqual(self.product1.value_svl, 120)

    def test_extra_move_fifo_1(self):
        """ Check that the extra move when over processing a receipt is correctly merged back in
        the original move.
        """
        self.product1.product_tmpl_id.categ_id.property_cost_method = 'fifo'
        po1 = self.env['purchase.order'].create({
            'partner_id': self.partner_id.id,
            'order_line': [
                (0, 0, {
                    'name': self.product1.name,
                    'product_id': self.product1.id,
                    'product_qty': 10.0,
                    'product_uom': self.product1.uom_po_id.id,
                    'price_unit': 100.0,
                    'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
                }),
            ],
        })
        po1.button_confirm()

        picking1 = po1.picking_ids[0]
        move1 = picking1.move_ids[0]
        move1.quantity = 15
        move1.picked = True
        picking1.button_validate()

        # there should be only one move
        self.assertEqual(len(picking1.move_ids), 1)
        self.assertEqual(move1.price_unit, 100)
        self.assertEqual(move1.stock_valuation_layer_ids.unit_cost, 100)
        self.assertEqual(move1.product_qty, 10)
        self.assertEqual(move1.quantity, 15)
        self.assertEqual(self.product1.value_svl, 1500)

    def test_backorder_fifo_1(self):
        """ Check that the backordered move when under processing a receipt correctly keep the
        price unit of the original move.
        """
        self.product1.product_tmpl_id.categ_id.property_cost_method = 'fifo'
        po1 = self.env['purchase.order'].create({
            'partner_id': self.partner_id.id,
            'order_line': [
                (0, 0, {
                    'name': self.product1.name,
                    'product_id': self.product1.id,
                    'product_qty': 10.0,
                    'product_uom': self.product1.uom_po_id.id,
                    'price_unit': 100.0,
                    'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
                }),
            ],
        })
        po1.button_confirm()

        picking1 = po1.picking_ids[0]
        move1 = picking1.move_ids[0]
        move1.quantity = 5
        move1.picked = True
        res_dict = picking1.button_validate()
        self.assertEqual(res_dict['res_model'], 'stock.backorder.confirmation')
        wizard = self.env[(res_dict.get('res_model'))].browse(res_dict.get('res_id')).with_context(res_dict['context'])
        wizard.process()

        self.assertEqual(len(picking1.move_ids), 1)
        self.assertEqual(move1.price_unit, 100)
        self.assertEqual(move1.product_qty, 5)

        picking2 = po1.picking_ids.filtered(lambda p: p.backorder_id)
        move2 = picking2.move_ids[0]
        self.assertEqual(len(picking2.move_ids), 1)
        self.assertEqual(move2.price_unit, 100)
        self.assertEqual(move2.product_qty, 5)


@tagged('post_install', '-at_install')
class TestStockValuationWithCOA(AccountTestInvoicingCommon):

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

        cls.supplier_location = cls.env.ref('stock.stock_location_suppliers')
        cls.stock_location = cls.env.ref('stock.stock_location_stock')
        cls.partner_id = cls.env['res.partner'].create({'name': 'Wood Corner Partner'})
        cls.product1 = cls.env['product.product'].create({'name': 'Large Desk'})

        cls.cat = cls.env['product.category'].create({
            'name': 'cat',
        })
        cls.product1 = cls.env['product.product'].create({
            'name': 'product1',
            'is_storable': True,
            'categ_id': cls.cat.id,
        })
        cls.product1_copy = cls.env['product.product'].create({
            'name': 'product1',
            'is_storable': True,
            'categ_id': cls.cat.id,
        })

        Account = cls.env['account.account']
        cls.usd_currency = cls.env.ref('base.USD')
        cls.eur_currency = cls.env.ref('base.EUR')
        cls.usd_currency.active = True
        cls.eur_currency.active = True

        cls.stock_input_account = Account.create({
            'name': 'Stock Input',
            'code': 'StockIn',
            'account_type': 'asset_current',
            'reconcile': True,
        })
        cls.stock_output_account = Account.create({
            'name': 'Stock Output',
            'code': 'StockOut',
            'account_type': 'asset_current',
            'reconcile': True,
        })
        cls.stock_valuation_account = Account.create({
            'name': 'Stock Valuation',
            'code': 'StockValuation',
            'account_type': 'asset_current',
        })
        cls.price_diff_account = Account.create({
            'name': 'price diff account',
            'code': 'priceDiffAccount',
            'account_type': 'asset_current',
        })
        cls.stock_journal = cls.env['account.journal'].create({
            'name': 'Stock Journal',
            'code': 'STJTEST',
            'type': 'general',
        })
        cls.product1.categ_id.write({
            'property_stock_account_input_categ_id': cls.stock_input_account.id,
            'property_stock_account_output_categ_id': cls.stock_output_account.id,
            'property_stock_valuation_account_id': cls.stock_valuation_account.id,
            'property_stock_journal': cls.stock_journal.id,
            'property_account_creditor_price_difference_categ': cls.product1.product_tmpl_id.get_product_accounts()['expense'],
            'property_valuation': 'real_time',
        })

        old_action_post = odoo.addons.account.models.account_move.AccountMove.action_post
        old_create = odoo.models.BaseModel.create

        def new_action_post(self):
            """ Force the creation of tracking values. """
            res = old_action_post(self)
            if self:
                cls.env.flush_all()
                cls.cr.flush()
            return res

        def new_create(self, vals_list):
            cls.cr._now = datetime.now()
            return old_create(self, vals_list)

        post_patch = patch('odoo.addons.account.models.account_move.AccountMove.action_post', new_action_post)
        create_patch = patch('odoo.models.BaseModel.create', new_create)
        cls.startClassPatcher(post_patch)
        cls.startClassPatcher(create_patch)

    @classmethod
    def default_env_context(cls):
        # OVERRIDE
        return {}

    def _bill(self, po, qty=None, price=None):
        action = po.action_create_invoice()
        bill = self.env["account.move"].browse(action["res_id"])
        bill.invoice_date = fields.Date.today()
        if qty is not None:
            bill.invoice_line_ids.quantity = qty
        if price is not None:
            bill.invoice_line_ids.price_unit = price
        bill.action_post()
        return bill

    def _refund(self, inv, qty=None):
        ctx = {'active_ids': inv.ids, 'active_model': 'account.move'}
        credit_note_wizard = self.env['account.move.reversal'].with_context(ctx).create({
            'journal_id': inv.journal_id.id,
        })
        rinv = self.env['account.move'].browse(credit_note_wizard.refund_moves()['res_id'])
        if qty is not None:
            rinv.invoice_line_ids.quantity = qty
        rinv.action_post()
        return rinv

    def _return(self, picking, qty=None):
        wizard_form = Form(self.env['stock.return.picking'].with_context(active_ids=picking.ids, active_id=picking.id, active_model='stock.picking'))
        wizard = wizard_form.save()
        qty = qty or picking.move_ids.quantity
        for line in wizard.product_return_moves:
            line.quantity = qty
        action = wizard.action_create_returns()
        return_picking = self.env["stock.picking"].browse(action["res_id"])
        return_picking.move_ids.move_line_ids.quantity = qty
        return_picking.move_ids.picked = True
        return_picking.button_validate()
        return return_picking

    def test_change_currency_rate_average_1(self):
        """ Confirm a purchase order in another currency and create the associated receipt, change
        the currency rate, validate the receipt and then check that the value of the received goods
        is set according to the last currency rate.
        """
        self.env['res.currency.rate'].search([]).unlink()
        usd_currency = self.env.ref('base.USD')
        self.env.company.currency_id = usd_currency.id

        eur_currency = self.env.ref('base.EUR')

        self.product1.product_tmpl_id.categ_id.property_cost_method = 'average'

        # default currency is USD, create a purchase order in EUR
        po1 = self.env['purchase.order'].create({
            'partner_id': self.partner_id.id,
            'currency_id': eur_currency.id,
            'order_line': [
                (0, 0, {
                    'name': self.product1.name,
                    'product_id': self.product1.id,
                    'product_qty': 10.0,
                    'product_uom': self.product1.uom_po_id.id,
                    'price_unit': 100.0,
                    'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
                }),
            ],
        })
        po1.button_confirm()

        picking1 = po1.picking_ids[0]
        move1 = picking1.move_ids[0]

        # convert the price unit in the company currency
        price_unit_usd = po1.currency_id._convert(
            po1.order_line.price_unit, po1.company_id.currency_id,
            self.env.company, fields.Date.today(), round=False)

        # the unit price of the move is the unit price of the purchase order line converted in
        # the company's currency
        self.assertAlmostEqual(move1.price_unit, price_unit_usd, places=2)

        # change the rate of the currency
        self.env['res.currency.rate'].create({
            'name': time.strftime('%Y-%m-%d'),
            'rate': 2.0,
            'currency_id': eur_currency.id,
            'company_id': po1.company_id.id,
        })
        eur_currency._compute_current_rate()
        price_unit_usd_new_rate = po1.currency_id._convert(
            po1.order_line.price_unit, po1.company_id.currency_id,
            self.env.company, fields.Date.today(), round=False)

        # the new price_unit is lower than th initial because of the rate's change
        self.assertLess(price_unit_usd_new_rate, price_unit_usd)

        # the unit price on the stock move is not directly updated
        self.assertAlmostEqual(move1.price_unit, price_unit_usd, places=2)

        # validate the receipt
        picking1.button_validate()

        # the unit price of the valuation layer used the latest value
        self.assertAlmostEqual(move1.stock_valuation_layer_ids.unit_cost, price_unit_usd_new_rate)

        self.assertAlmostEqual(self.product1.value_svl, price_unit_usd_new_rate * 10, delta=0.1)

    def test_fifo_anglosaxon_return(self):
        self.env.company.anglo_saxon_accounting = True
        self.product1.product_tmpl_id.categ_id.property_cost_method = 'fifo'
        self.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'

        # Receive 10@10 ; create the vendor bill
        po1 = self.env['purchase.order'].create({
            'partner_id': self.partner_id.id,
            'order_line': [
                (0, 0, {
                    'name': self.product1.name,
                    'product_id': self.product1.id,
                    'product_qty': 10.0,
                    'product_uom': self.product1.uom_po_id.id,
                    'price_unit': 10.0,
                    'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
                }),
            ],
        })
        po1.button_confirm()
        receipt_po1 = po1.picking_ids[0]
        receipt_po1.move_ids.quantity = 10
        receipt_po1.move_ids.picked = True
        receipt_po1.button_validate()

        move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
        move_form.invoice_date = move_form.date
        move_form.partner_id = self.partner_id
        move_form.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-po1.id)
        invoice_po1 = move_form.save()
        invoice_po1.action_post()

        # Receive 10@20 ; create the vendor bill
        po2 = self.env['purchase.order'].create({
            'partner_id': self.partner_id.id,
            'order_line': [
                (0, 0, {
                    'name': self.product1.name,
                    'product_id': self.product1.id,
                    'product_qty': 10.0,
                    'product_uom': self.product1.uom_po_id.id,
                    'price_unit': 20.0,
                    'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
                }),
            ],
        })
        po2.button_confirm()
        receipt_po2 = po2.picking_ids[0]
        receipt_po2.move_ids.quantity = 10
        receipt_po2.move_ids.picked = True
        receipt_po2.button_validate()

        move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
        move_form.invoice_date = move_form.date
        move_form.partner_id = self.partner_id
        move_form.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-po2.id)
        invoice_po2 = move_form.save()
        invoice_po2.action_post()

        # valuation of product1 should be 300
        self.assertEqual(self.product1.value_svl, 300)

        # return the second po
        stock_return_picking_form = Form(self.env['stock.return.picking'].with_context(
            active_ids=receipt_po2.ids, active_id=receipt_po2.ids[0], active_model='stock.picking'))
        stock_return_picking = stock_return_picking_form.save()
        stock_return_picking.product_return_moves.quantity = 10
        stock_return_picking_action = stock_return_picking.action_create_returns()
        return_pick = self.env['stock.picking'].browse(stock_return_picking_action['res_id'])
        return_pick.move_ids[0].move_line_ids[0].quantity = 10
        return_pick.move_ids[0].picked = True
        return_pick.button_validate()

        # valuation of product1 should be 200 as the first items will be sent out
        self.assertEqual(self.product1.value_svl, 200)
        # create a credit note for po2
        move_form = Form(self.env['account.move'].with_context(default_move_type='in_refund'))
        move_form.invoice_date = move_form.date
        move_form.partner_id = self.partner_id

        # Not supposed to see/change the purchase order of a refund invoice by default
        # <field name="purchase_id" invisible="1"/>
        # <label for="purchase_vendor_bill_id" string="Auto-Complete" class="oe_edit_only"
        #         invisible="state != 'draft' or move_type != 'in_invoice'" />
        # <field name="purchase_vendor_bill_id" nolabel="1"
        #         invisible="state != 'draft' or move_type != 'in_invoice'"
        move_form._view['modifiers']['purchase_id']['invisible'] = 'False'
        move_form.purchase_id = po2
        with move_form.invoice_line_ids.edit(0) as line_form:
            line_form.quantity = 10
        creditnote_po2 = move_form.save()
        creditnote_po2.action_post()

        # check the anglo saxon entries
        price_diff_entry = self.env['account.move.line'].search([
            ('account_id', '=', self.stock_valuation_account.id),
            ('move_id.stock_move_id', '=', return_pick.move_ids[0].id)])
        self.assertEqual(price_diff_entry.credit, 100)

    def test_anglosaxon_valuation(self):
        self.env.company.anglo_saxon_accounting = True
        self.product1.product_tmpl_id.categ_id.property_cost_method = 'fifo'
        self.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'

        # Create PO
        po_form = Form(self.env['purchase.order'])
        po_form.partner_id = self.partner_id
        with po_form.order_line.new() as po_line:
            po_line.product_id = self.product1
            po_line.product_qty = 1
            po_line.price_unit = 10.0
        order = po_form.save()
        order.button_confirm()

        # Receive the goods
        receipt = order.picking_ids[0]
        receipt.move_ids.quantity = 1
        receipt.move_ids.picked = True
        receipt.button_validate()

        stock_valuation_aml = self.env['account.move.line'].search([('account_id', '=', self.stock_valuation_account.id)])
        receipt_aml = stock_valuation_aml[0]
        self.assertEqual(len(stock_valuation_aml), 1, "For now, only one line for the stock valuation account")
        self.assertAlmostEqual(receipt_aml.debit, 10, msg="Should be equal to the PO line unit price (10)")

        # Create an invoice with a different price
        move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
        move_form.invoice_date = move_form.date
        move_form.partner_id = order.partner_id
        move_form.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-order.id)
        with move_form.invoice_line_ids.edit(0) as line_form:
            line_form.price_unit = 15.0
        invoice = move_form.save()
        invoice.action_post()

        # Check what was posted in the stock valuation account
        stock_valuation_aml = self.env['account.move.line'].search([('account_id', '=', self.stock_valuation_account.id)])
        price_diff_aml = stock_valuation_aml - receipt_aml
        self.assertEqual(len(stock_valuation_aml), 2, "A second line should have been generated for the price difference.")
        self.assertAlmostEqual(price_diff_aml.debit, 5, msg="Price difference should be equal to 5 (15-10)")
        self.assertAlmostEqual(
            sum(stock_valuation_aml.mapped('debit')), 15,
            msg="Total debit value on stock valuation account should be equal to the invoiced price of the product.")

        # Check what was posted in stock input account
        input_aml = self.env['account.move.line'].search([('account_id', '=', self.stock_input_account.id)])
        self.assertEqual(len(input_aml), 3, "Only three lines should have been generated in stock input account: one when receiving the product, one when making the invoice.")
        invoice_amls = input_aml.filtered(lambda l: l.move_id == invoice)
        picking_aml = input_aml - invoice_amls
        self.assertEqual(sum(invoice_amls.mapped('debit')), 15, "Total debit value on stock input account should be equal to the invoice price of the product.")
        self.assertEqual(sum(invoice_amls.mapped('credit')), 0, "Invoice account move lines should not contains information on stock input at this point.")
        self.assertEqual(sum(picking_aml.mapped('credit')), 15, "Total credit value on stock input account should be equal to the invoice price of the product.")

    def test_valuation_from_increasing_tax(self):
        """ Check that a tax without account will increment the stock value.
        """

        tax_with_no_account = self.env['account.tax'].create({
            'name': "Tax with no account",
            'amount_type': 'fixed',
            'amount': 5,
            'sequence': 8,
        })

        self.product1.product_tmpl_id.categ_id.property_cost_method = 'fifo'
        self.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'

        # Receive 10@10 ; create the vendor bill
        po1 = self.env['purchase.order'].create({
            'partner_id': self.partner_id.id,
            'order_line': [
                (0, 0, {
                    'name': self.product1.name,
                    'product_id': self.product1.id,
                    'taxes_id': [(4, tax_with_no_account.id)],
                    'product_qty': 10.0,
                    'product_uom': self.product1.uom_po_id.id,
                    'price_unit': 10.0,
                    'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
                }),
            ],
        })
        po1.button_confirm()
        receipt_po1 = po1.picking_ids[0]
        receipt_po1.move_ids.quantity = 10
        receipt_po1.move_ids.picked = True
        receipt_po1.button_validate()

        # valuation of product1 should be 15 as the tax with no account set
        # has gone to the stock account, and must be reflected in inventory valuation
        self.assertEqual(self.product1.value_svl, 150)

    def test_standard_valuation_multicurrency(self):
        company = self.env.user.company_id
        company.anglo_saxon_accounting = True
        company.currency_id = self.usd_currency

        date_po = '2019-01-01'

        self.product1.product_tmpl_id.categ_id.property_cost_method = 'standard'
        self.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'
        self.product1.standard_price = 10

        # SetUp currency and rates   1$ = 2 Euros
        self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", (self.usd_currency.id, company.id))
        self.env['res.currency.rate'].search([]).unlink()
        self.env['res.currency.rate'].create({
            'name': date_po,
            'rate': 1.0,
            'currency_id': self.usd_currency.id,
            'company_id': company.id,
        })

        self.env['res.currency.rate'].create({
            'name': date_po,
            'rate': 2,
            'currency_id': self.eur_currency.id,
            'company_id': company.id,
        })

        # Create PO
        po = self.env['purchase.order'].create({
            'currency_id': self.eur_currency.id,
            'partner_id': self.partner_id.id,
            'order_line': [
                (0, 0, {
                    'name': self.product1.name,
                    'product_id': self.product1.id,
                    'product_qty': 1.0,
                    'product_uom': self.product1.uom_po_id.id,
                    'price_unit': 100.0, # 50$
                    'date_planned': date_po,
                }),
            ],
        })
        po.button_confirm()

        # Receive the goods
        receipt = po.picking_ids[0]
        receipt.move_line_ids.quantity = 1
        receipt.move_ids.picked = True
        receipt.button_validate()

        # Create a vendor bill
        inv = self.env['account.move'].with_context(default_move_type='in_invoice').create({
            'move_type': 'in_invoice',
            'invoice_date': date_po,
            'date': date_po,
            'currency_id': self.eur_currency.id,
            'partner_id': self.partner_id.id,
            'invoice_line_ids': [(0, 0, {
                'name': 'Test',
                'price_unit': 100.0,
                'product_id': self.product1.id,
                'purchase_line_id': po.order_line.id,
                'quantity': 1.0,
                'account_id': self.stock_input_account.id,
            })]
        })

        inv.action_post()

        # Check what was posted in stock input account
        input_amls = self.env['account.move.line'].search([('account_id', '=', self.stock_input_account.id)])
        self.assertEqual(len(input_amls), 3, "Only three lines should have been generated in stock input account: one when receiving the product, one when making the invoice.")
        invoice_amls = input_amls.filtered(lambda l: l.move_id == inv)
        picking_aml = input_amls - invoice_amls
        payable_aml = invoice_amls.filtered(lambda l: l.amount_currency > 0)
        diff_aml = invoice_amls - payable_aml

        # check USD
        self.assertAlmostEqual(payable_aml.debit, 50, msg="Total debit value should be equal to the original PO price of the product.")
        self.assertAlmostEqual(picking_aml.credit, 10, msg="credit value for stock should be equal to the standard price of the product.")
        self.assertAlmostEqual(diff_aml.credit, 40, msg="credit value for price difference")

        # check EUR
        self.assertAlmostEqual(payable_aml.amount_currency, 100, msg="Total debit value should be equal to the original PO price of the product.")
        self.assertAlmostEqual(picking_aml.amount_currency, -20, msg="credit value for stock should be equal to the standard price of the product.")
        self.assertAlmostEqual(diff_aml.amount_currency, -80, msg="credit value for price difference")

    def test_bill_on_ordered_qty_valuation_multicurrency(self):
        """
            Product with an invoice policy on ordered quantity should keep the valuation
            of the bill with the exchange rate at the bill date
        """
        company = self.env.user.company_id
        company.anglo_saxon_accounting = True
        company.currency_id = self.usd_currency

        date_po = '1993-07-18'

        self.product1.product_tmpl_id.categ_id.property_cost_method = 'average'
        self.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'
        self.product1.purchase_method = 'purchase'

        self.env['res.currency.rate'].create([{
            'name': date_po,
            'rate': 1 / 1.5,
            'currency_id': self.eur_currency.id,
            'company_id': company.id,
        }])

        # Create PO
        po = self.env['purchase.order'].create({
            'currency_id': self.eur_currency.id,
            'partner_id': self.partner_id.id,
            'order_line': [
                (0, 0, {
                    'name': self.product1.name,
                    'product_id': self.product1.id,
                    'product_qty': 1.0,
                    'product_uom': self.product1.uom_po_id.id,
                    'price_unit': 100.0,  # 100€ = 150$
                    'date_planned': date_po,
                }),
            ],
        })
        po.button_confirm()

        # Create and post the vendor bill before recieving the product
        self.env['account.move'].with_context(default_move_type='in_invoice').create({
            'move_type': 'in_invoice',
            'invoice_date': date_po,
            'date': date_po,
            'currency_id': self.usd_currency.id,
            'partner_id': self.partner_id.id,
            'invoice_line_ids': [(0, 0, {
                'name': 'Test',
                'price_unit': 100.0,  # 100$
                'product_id': self.product1.id,
                'purchase_line_id': po.order_line.id,
                'quantity': 1.0,
                'account_id': self.stock_input_account.id,
            })]
        }).action_post()

        receipt = po.picking_ids
        receipt.move_ids.picked = True
        receipt.button_validate()

        product_aml = po.invoice_ids.line_ids.filtered('product_id')
        self.assertEqual(receipt.move_ids.stock_valuation_layer_ids.value, 100)
        self.assertTrue(product_aml.reconciled)
        self.assertTrue(product_aml.full_reconcile_id)

    def test_valuation_rounding(self):
        company = self.env.user.company_id
        company.anglo_saxon_accounting = True
        company.currency_id = self.usd_currency

        self.product1.product_tmpl_id.categ_id.property_cost_method = 'fifo'
        self.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'

        self.env.ref('product.decimal_price').digits = 5

        po = self.env['purchase.order'].create({
            'partner_id': self.partner_id.id,
            'order_line': [
                (0, 0, {
                    'name': self.product1.name,
                    'product_id': self.product1.id,
                    'product_qty': 1500.0,
                    'product_uom': self.product1.uom_po_id.id,
                    'price_unit': 3.30125,
                }),
            ],
        })
        po.button_confirm()

        # Receive the goods
        receipt = po.picking_ids
        receipt.button_validate()

        self._bill(po)
        layers = self.env['stock.valuation.layer'].search([('product_id', '=', self.product1.id)])
        self.assertEqual(len(layers), 1)
        self.assertEqual(layers.quantity, 1500)
        self.assertEqual(layers.value, 4951.88)

    def test_valuation_rounding_price_diff(self):
        company = self.env.user.company_id
        company.anglo_saxon_accounting = True
        company.currency_id = self.usd_currency

        self.product1.product_tmpl_id.categ_id.property_cost_method = 'fifo'
        self.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'

        self.env.ref('product.decimal_price').digits = 5

        po = self.env['purchase.order'].create({
            'partner_id': self.partner_id.id,
            'order_line': [
                (0, 0, {
                    'name': self.product1.name,
                    'product_id': self.product1.id,
                    'product_qty': 1500.0,
                    'product_uom': self.product1.uom_po_id.id,
                    'price_unit': 3.30125,
                }),
            ],
        })
        po.button_confirm()

        # Receive the goods
        receipt = po.picking_ids
        receipt.button_validate()

        self._bill(po, price=3.31125)
        layers = self.env['stock.valuation.layer'].search([('product_id', '=', self.product1.id)])
        self.assertEqual(len(layers), 2)
        self.assertEqual(layers[0].quantity, 1500)
        self.assertEqual(layers[1].quantity, 0)
        self.assertEqual(layers[0].value, 4951.88)
        self.assertEqual(round(layers[1].value, company.currency_id.decimal_places), 15)

    def test_valuation_multicurrency_with_tax(self):
        """ Check that the amount_currency does not include the tax.
        """

        company = self.env.user.company_id
        company.anglo_saxon_accounting = True
        company.currency_id = self.usd_currency

        date_po = '2019-01-01'

        self.product1.product_tmpl_id.categ_id.property_cost_method = 'fifo'
        self.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'

        # SetUp currency and rates 1$ = 2Euros
        self.env['res.currency.rate'].search([]).unlink()
        self.env['res.currency.rate'].create({
            'name': date_po,
            'rate': 1.0,
            'currency_id': self.usd_currency.id,
            'company_id': company.id,
        })
        self.env['res.currency.rate'].create({
            'name': date_po,
            'rate': 2,
            'currency_id': self.eur_currency.id,
            'company_id': company.id,
        })

        repartition_line_vals = [
            (0, 0, {"repartition_type": "base"}),
            (0, 0, {
                "factor_percent": 100,
                "repartition_type": "tax",
                "account_id": self.company_data['default_account_tax_sale'].id,
            }),
        ]
        tax = self.env['account.tax'].create({
            "name": "Tax with no account",
            "amount_type": "percent",
            "amount": 5,
            "price_include_override": "tax_included",
            "invoice_repartition_line_ids": repartition_line_vals,
            "refund_repartition_line_ids": repartition_line_vals,
        })

        # Create PO
        po = self.env['purchase.order'].create({
            'currency_id': self.eur_currency.id,
            'partner_id': self.partner_id.id,
            'order_line': [
                (0, 0, {
                    'name': self.product1.name,
                    'product_id': self.product1.id,
                    'product_qty': 1.0,
                    'product_uom': self.product1.uom_po_id.id,
                    'price_unit': 105.0,  # 50$
                    'taxes_id': [(4, tax.id)],
                    'date_planned': date_po,
                }),
            ],
        })

        po.button_confirm()

        # Receive the goods
        receipt = po.picking_ids[0]
        receipt.move_line_ids.quantity = 1
        receipt.move_ids.picked = True
        receipt.button_validate()

        # empty stock to generate the AML values for the already out quantities
        inventory_quant = self.env['stock.quant'].sudo().search([
            ('location_id', '=', receipt.location_dest_id.id),
            ('product_id', '=', self.product1.id),
        ])
        inventory_quant.inventory_quantity = 0
        inventory_quant.action_apply_inventory()

        # Create a vendor bill
        inv = self.env['account.move'].with_context(default_move_type='in_invoice').create({
            'move_type': 'in_invoice',
            'invoice_date': date_po,
            'date': date_po,
            'currency_id': self.eur_currency.id,
            'partner_id': self.partner_id.id,
            'invoice_line_ids': [(0, 0, {
                'name': 'Test',
                'price_unit': 105.0,
                'product_id': self.product1.id,
                'purchase_line_id': po.order_line.id,
                'tax_ids': [(4, tax.id)],
                'quantity': 1.0,
                'account_id': self.stock_input_account.id,
            })]
        })

        inv.action_post()

        invoice_aml = inv.invoice_line_ids
        self.assertEqual(len(inv.line_ids), 3)
        picking_am = receipt.move_ids.stock_valuation_layer_ids.account_move_id.ensure_one()
        picking_aml = picking_am.line_ids.filtered(lambda line: line.account_id.id == self.stock_valuation_account.id)

        # check EUR
        self.assertAlmostEqual(invoice_aml.amount_currency, 100, msg="Total debit value should be equal to the original PO price of the product.")
        self.assertAlmostEqual(picking_aml.amount_currency, 100, msg="credit value for stock should be equal to the untaxed price of the product.")

    def test_valuation_multicurrency_with_tax_without_account(self):
        """ Similar test as test_valuation_multicurrency_with_tax, but without the accounts in the tax.
        Tax without accounts will increment the stock value (check test_valuation_from_increasing_tax).
        In this test, we verify that the amount_currency is also impacted by these changes.
        """

        company = self.env.user.company_id
        company.anglo_saxon_accounting = True
        company.currency_id = self.usd_currency

        self.product1.product_tmpl_id.categ_id.property_cost_method = 'fifo'
        self.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'

        # SetUp currency and rates 1$ = 2Euros
        self.env['res.currency.rate'].create({'currency_id': self.usd_currency.id, 'rate': 1})
        self.env['res.currency.rate'].create({'currency_id': self.eur_currency.id, 'rate': 2})

        tax_without_account = self.env['account.tax'].create({
            "name": "Tax with no account",
            "amount_type": "fixed",
            "amount": 5,
            "price_include_override": "tax_included",
        })

        # Create PO
        po = self.env['purchase.order'].create({
            'currency_id': self.eur_currency.id,
            'partner_id': self.partner_id.id,
            'order_line': [
                (0, 0, {
                    'name': self.product1.name,
                    'product_id': self.product1.id,
                    'product_qty': 1.0,
                    'product_uom': self.product1.uom_po_id.id,
                    'price_unit': 100.0,  # 50$
                    'taxes_id': [(4, tax_without_account.id)],
                }),
            ],
        })

        po.button_confirm()

        # Receive the goods
        receipt = po.picking_ids[0]
        receipt.move_line_ids.quantity = 1
        receipt.move_ids.picked = True
        receipt.button_validate()

        picking_am = receipt.move_ids.stock_valuation_layer_ids.account_move_id.ensure_one()
        picking_aml = picking_am.line_ids.filtered(lambda line: line.account_id.id == self.stock_valuation_account.id)

        self.assertAlmostEqual(picking_aml.amount_currency, 100, msg="The tax without account should be included in the stock value, and impact the amount_currency.")

    def test_average_realtime_anglo_saxon_valuation_multicurrency_same_date(self):
        """
        The PO and invoice are in the same foreign currency.
        The PO is invoiced on the same date as its creation.
        This shouldn't create a price difference entry.
        """
        company = self.env.user.company_id
        company.anglo_saxon_accounting = True
        company.currency_id = self.usd_currency

        date_po = '2019-01-01'

        # SetUp product
        self.product1.product_tmpl_id.cost_method = 'average'
        self.product1.product_tmpl_id.valuation = 'real_time'
        self.product1.product_tmpl_id.purchase_method = 'purchase'

        # SetUp currency and rates
        self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", (self.usd_currency.id, company.id))
        self.env['res.currency.rate'].search([]).unlink()
        self.env['res.currency.rate'].create({
            'name': date_po,
            'rate': 1.0,
            'currency_id': self.usd_currency.id,
            'company_id': company.id,
        })

        self.env['res.currency.rate'].create({
            'name': date_po,
            'rate': 1.5,
            'currency_id': self.eur_currency.id,
            'company_id': company.id,
        })

        # Proceed
        po = self.env['purchase.order'].create({
            'currency_id': self.eur_currency.id,
            'partner_id': self.partner_id.id,
            'order_line': [
                (0, 0, {
                    'name': self.product1.name,
                    'product_id': self.product1.id,
                    'product_qty': 1.0,
                    'product_uom': self.product1.uom_po_id.id,
                    'price_unit': 100.0,
                    'date_planned': date_po,
                }),
            ],
        })
        po.button_confirm()

        inv = self.env['account.move'].with_context(default_move_type='in_invoice').create({
            'move_type': 'in_invoice',
            'invoice_date': date_po,
            'date': date_po,
            'currency_id': self.eur_currency.id,
            'partner_id': self.partner_id.id,
            'invoice_line_ids': [(0, 0, {
                'name': 'Test',
                'price_unit': 100.0,
                'product_id': self.product1.id,
                'purchase_line_id': po.order_line.id,
                'quantity': 1.0,
                'account_id': self.stock_input_account.id,
                'tax_ids': [],
            })]
        })

        inv.action_post()

        move_lines = inv.line_ids
        self.assertEqual(len(move_lines), 4)

        payable_line = move_lines.filtered(lambda l: l.account_id.account_type == 'liability_payable')

        self.assertEqual(payable_line.amount_currency, -100.0)
        self.assertAlmostEqual(payable_line.balance, -66.67)

        stock_line = move_lines.filtered(lambda l: l.account_id == self.stock_input_account and l.balance > 0)
        self.assertEqual(stock_line.amount_currency, 100.0)
        self.assertAlmostEqual(stock_line.balance, 66.67)

    def test_realtime_anglo_saxon_valuation_multicurrency_different_dates(self):
        """
        The PO and invoice are in the same foreign currency.
        The PO is invoiced at a later date than its creation.
        This should create a price difference entry for standard cost method
        Not for average cost method though, since the PO and invoice have the same currency
        """
        company = self.env.user.company_id
        company.anglo_saxon_accounting = True
        company.currency_id = self.usd_currency
        self.product1.product_tmpl_id.categ_id.property_cost_method = 'average'
        self.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'

        date_po = '2019-01-01'
        date_invoice = '2019-01-16'

        # SetUp product Average
        self.product1.product_tmpl_id.purchase_method = 'purchase'

        # SetUp product Standard
        # should have bought at 60 USD
        # actually invoiced at 70 EUR > 35 USD
        product_categ_standard = self.cat.copy({
            'property_cost_method': 'standard',
            'property_stock_account_input_categ_id': self.stock_input_account.id,
            'property_stock_account_output_categ_id': self.stock_output_account.id,
            'property_stock_valuation_account_id': self.stock_valuation_account.id,
            'property_stock_journal': self.stock_journal.id,
        })
        product_standard = self.product1_copy
        product_standard.write({
            'categ_id': product_categ_standard.id,
            'name': 'Standard Val',
            'standard_price': 60,
        })
        product_standard.product_tmpl_id.purchase_method = 'purchase'

        # SetUp currency and rates
        self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", (self.usd_currency.id, company.id))
        self.env['res.currency.rate'].search([]).unlink()
        self.env['res.currency.rate'].create({
            'name': date_po,
            'rate': 1.0,
            'currency_id': self.usd_currency.id,
            'company_id': company.id,
        })

        self.env['res.currency.rate'].create({
            'name': date_po,
            'rate': 1.5,
            'currency_id': self.eur_currency.id,
            'company_id': company.id,
        })

        self.env['res.currency.rate'].create({
            'name': date_invoice,
            'rate': 2,
            'currency_id': self.eur_currency.id,
            'company_id': company.id,
        })

        # To allow testing validation of PO
        def _today(*args, **kwargs):
            return date_po
        patchers = [
            patch('odoo.fields.Date.context_today', _today),
        ]

        for patcher in patchers:
            self.startPatcher(patcher)

        # Proceed
        po = self.env['purchase.order'].create({
            'currency_id': self.eur_currency.id,
            'partner_id': self.partner_id.id,
            'order_line': [
                (0, 0, {
                    'name': self.product1.name,
                    'product_id': self.product1.id,
                    'product_qty': 1.0,
                    'product_uom': self.product1.uom_po_id.id,
                    'price_unit': 100.0,
                    'date_planned': date_po,
                }),
                (0, 0, {
                    'name': product_standard.name,
                    'product_id': product_standard.id,
                    'product_qty': 1.0,
                    'product_uom': product_standard.uom_po_id.id,
                    'price_unit': 40.0,
                    'date_planned': date_po,
                }),
            ],
        })
        po.button_confirm()

        line_product_average = po.order_line.filtered(lambda l: l.product_id == self.product1)
        line_product_standard = po.order_line.filtered(lambda l: l.product_id == product_standard)

        inv = self.env['account.move'].with_context(default_move_type='in_invoice').create({
            'move_type': 'in_invoice',
            'invoice_date': date_invoice,
            'date': date_invoice,
            'currency_id': self.eur_currency.id,
            'partner_id': self.partner_id.id,
            'invoice_line_ids': [
                (0, 0, {
                    'name': self.product1.name,
                    'price_subtotal': 100.0,
                    'price_unit': 100.0,
                    'product_id': self.product1.id,
                    'purchase_line_id': line_product_average.id,
                    'quantity': 1.0,
                    'account_id': self.stock_input_account.id,
                    'tax_ids': [],
                }),
                (0, 0, {
                    'name': product_standard.name,
                    'price_subtotal': 70.0,
                    'price_unit': 70.0,
                    'product_id': product_standard.id,
                    'purchase_line_id': line_product_standard.id,
                    'quantity': 1.0,
                    'account_id': self.stock_input_account.id,
                    'tax_ids': [],
                })
            ]
        })

        inv.action_post()

        move_lines = inv.line_ids
        self.assertEqual(len(move_lines), 3)

        # Ensure no exchange difference move has been created
        self.assertTrue(all([not l.reconciled for l in move_lines]))

        # PAYABLE CHECK
        payable_line = move_lines.filtered(lambda l: l.account_id.account_type == 'liability_payable')
        self.assertEqual(payable_line.amount_currency, -170.0)
        self.assertAlmostEqual(payable_line.balance, -85.00)

        # PRODUCTS CHECKS

        # NO EXCHANGE DIFFERENCE (average)
        # We ordered for a value of 100 EUR
        # But by the time we are invoiced for it
        # the foreign currency appreciated from 1.5 to 2.0
        # We still have to pay 100 EUR, which now values at 50 USD
        product_lines = move_lines.filtered(lambda l: l.product_id == self.product1)

        # Stock-wise, we have been invoiced 100 EUR, and we ordered 100 EUR
        # there is no price difference
        # However, 100 EUR should be converted at the time of the invoice
        stock_lines = product_lines.filtered(lambda l: l.account_id == self.stock_input_account)
        self.assertAlmostEqual(sum(stock_lines.mapped('amount_currency')), 100.00)
        self.assertAlmostEqual(sum(stock_lines.mapped('balance')), 50.00)

        # PRICE DIFFERENCE (STANDARD)
        # We ordered a product that should have cost 60 USD (120 EUR)
        # However, we effectively got invoiced 70 EUR (35 USD)
        product_lines = move_lines.filtered(lambda l: l.product_id == product_standard)
        stock_lines = product_lines.filtered(lambda l: l.account_id == self.stock_input_account)
        self.assertAlmostEqual(sum(stock_lines.mapped('amount_currency')), 70.00)
        self.assertAlmostEqual(sum(stock_lines.mapped('balance')), 35.00)

        # TODO It should be evaluated during the receiption now
        # price_diff_line = product_lines.filtered(lambda l: l.account_id == self.stock_valuation_account)
        # self.assertEqual(price_diff_line.amount_currency, -50.00)
        # self.assertAlmostEqual(price_diff_line.balance, -25.00)

    def test_average_realtime_with_delivery_anglo_saxon_valuation_multicurrency_different_dates(self):
        """
        The PO and invoice are in the same foreign currency.
        The delivery occurs in between PO validation and invoicing
        The invoice is created at an even different date
        This should create a price difference entry.
        """
        company = self.env.user.company_id
        company.anglo_saxon_accounting = True
        company.currency_id = self.usd_currency
        self.product1.product_tmpl_id.categ_id.property_cost_method = 'average'
        self.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'

        date_po = '2019-01-01'
        date_delivery = '2019-01-08'
        date_invoice = '2019-01-16'

        product_avg = self.product1_copy
        product_avg.write({
            'purchase_method': 'purchase',
            'name': 'AVG',
            'standard_price': 60,
        })

        # SetUp currency and rates
        self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", (self.usd_currency.id, company.id))
        self.env['res.currency.rate'].search([]).unlink()
        self.env['res.currency.rate'].create({
            'name': date_po,
            'rate': 1.0,
            'currency_id': self.usd_currency.id,
            'company_id': company.id,
        })

        self.env['res.currency.rate'].create({
            'name': date_po,
            'rate': 1.5,
            'currency_id': self.eur_currency.id,
            'company_id': company.id,
        })

        self.env['res.currency.rate'].create({
            'name': date_delivery,
            'rate': 0.7,
            'currency_id': self.eur_currency.id,
            'company_id': company.id,
        })

        self.env['res.currency.rate'].create({
            'name': date_invoice,
            'rate': 2,
            'currency_id': self.eur_currency.id,
            'company_id': company.id,
        })

        # To allow testing validation of PO and Delivery
        today = date_po

        def _today(*args, **kwargs):
            return datetime.strptime(today, "%Y-%m-%d").date()

        def _now(*args, **kwargs):
            return datetime.strptime(today + ' 01:00:00', "%Y-%m-%d %H:%M:%S")

        patchers = [
            patch('odoo.fields.Date.context_today', _today),
            patch('odoo.fields.Datetime.now', _now),
        ]

        for patcher in patchers:
            self.startPatcher(patcher)

        # Proceed
        po = self.env['purchase.order'].create({
            'currency_id': self.eur_currency.id,
            'partner_id': self.partner_id.id,
            'order_line': [
                (0, 0, {
                    'name': product_avg.name,
                    'product_id': product_avg.id,
                    'product_qty': 1.0,
                    'product_uom': product_avg.uom_po_id.id,
                    'price_unit': 30.0,
                    'date_planned': date_po,
                })
            ],
        })
        po.button_confirm()

        line_product_avg = po.order_line.filtered(lambda l: l.product_id == product_avg)

        today = date_delivery
        picking = po.picking_ids
        (picking.move_ids
            .filtered(lambda l: l.purchase_line_id == line_product_avg)
            .write({'quantity': 1.0, 'picked': True}))

        picking.button_validate()
        # 5 Units received at rate 0.7 = 42.86
        self.assertAlmostEqual(product_avg.standard_price, 42.86)

        today = date_invoice
        inv = self.env['account.move'].with_context(default_move_type='in_invoice').create({
            'move_type': 'in_invoice',
            'invoice_date': date_invoice,
            'date': date_invoice,
            'currency_id': self.eur_currency.id,
            'partner_id': self.partner_id.id,
            'invoice_line_ids': [
                (0, 0, {
                    'name': product_avg.name,
                    'price_unit': 30.0,
                    'product_id': product_avg.id,
                    'purchase_line_id': line_product_avg.id,
                    'quantity': 1.0,
                    'account_id': self.stock_input_account.id,
                    'tax_ids': [],
                })
            ]
        })

        inv.action_post()

        self.assertRecordValues(inv.line_ids, [
            # pylint: disable=C0326
            {'balance': 15.0,   'amount_currency': 30.0,    'account_id': self.stock_input_account.id},
            {'balance': -15.0,  'amount_currency': -30.0,   'account_id': self.company_data['default_account_payable'].id},
        ])
        self.assertRecordValues(inv.line_ids.full_reconcile_id.reconciled_line_ids.sorted('balance'), [
            # pylint: disable=C0326
            {'balance': -42.86, 'amount_currency': -30.0,   'account_id': self.stock_input_account.id},
            {'balance': 15.0,   'amount_currency': 30.0,    'account_id': self.stock_input_account.id},
            {'balance': 27.86,  'amount_currency': 0.0,     'account_id': self.stock_input_account.id},
        ])
        input_aml = self.env['account.move.line'].search([('account_id', '=', self.stock_input_account.id)])
        self.assertEqual(len(input_aml), 3)

    def test_average_realtime_with_delivery_anglo_saxon_valuation_multicurrency_same_dates(self):
        """ Same than test_average_realtime_with_delivery_anglo_saxon_valuation_multicurrency_different_dates
        but the rates change happens
        """
        company = self.env.user.company_id
        company.anglo_saxon_accounting = True
        company.currency_id = self.usd_currency
        self.product1.product_tmpl_id.categ_id.property_cost_method = 'average'
        self.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'

        date = fields.Date.to_string(fields.Date.today())

        product_avg = self.product1_copy
        product_avg.write({
            'purchase_method': 'purchase',
            'name': 'AVG',
            'standard_price': 60,
        })

        # SetUp currency and rates
        self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", (self.usd_currency.id, company.id))
        self.env['res.currency.rate'].search([]).unlink()
        self.env['res.currency.rate'].create({
            'name': date,
            'rate': 1.0,
            'currency_id': self.usd_currency.id,
            'company_id': company.id,
        })

        eur_rate = self.env['res.currency.rate'].create({
            'name': date,
            'rate': 0.5,
            'currency_id': self.eur_currency.id,
            'company_id': company.id,
        })

        # Proceed
        po = self.env['purchase.order'].create({
            'currency_id': self.eur_currency.id,
            'partner_id': self.partner_id.id,
            'order_line': [
                (0, 0, {
                    'name': product_avg.name,
                    'product_id': product_avg.id,
                    'product_qty': 1.0,
                    'product_uom': product_avg.uom_po_id.id,
                    'price_unit': 30.0,
                    'date_planned': date,
                })
            ],
        })
        po.button_confirm()

        line_product_avg = po.order_line.filtered(lambda l: l.product_id == product_avg)
        picking = po.picking_ids
        (picking.move_ids
            .filtered(lambda l: l.purchase_line_id == line_product_avg)
            .write({'quantity': 1.0, 'picked': True}))

        picking.button_validate()
        # 5 Units received at rate 2 = 42.86
        self.assertAlmostEqual(product_avg.standard_price, 60)

        eur_rate.rate = 0.25

        inv = self.env['account.move'].with_context(default_move_type='in_invoice').create({
            'move_type': 'in_invoice',
            'invoice_date': date,
            'date': date,
            'currency_id': self.eur_currency.id,
            'partner_id': self.partner_id.id,
            'invoice_line_ids': [
                (0, 0, {
                    'name': product_avg.name,
                    'price_unit': 30.0,
                    'product_id': product_avg.id,
                    'purchase_line_id': line_product_avg.id,
                    'quantity': 1.0,
                    'account_id': self.stock_input_account.id,
                    'tax_ids': [],
                })
            ]
        })
        self.env['stock.move'].invalidate_model()
        inv.action_post()
        self.assertRecordValues(inv.line_ids, [
            # pylint: disable=C0326
            {'balance': 120.0,  'amount_currency': 30.0,    'account_id': self.stock_input_account.id},
            {'balance': -120.0, 'amount_currency': -30.0,   'account_id': self.company_data['default_account_payable'].id},
        ])
        self.assertRecordValues(inv.line_ids.full_reconcile_id.reconciled_line_ids.sorted('id'), [
            # pylint: disable=C0326
            {'balance': -60.0,  'amount_currency': -30.0,   'account_id': self.stock_input_account.id},
            {'balance': 120.0,  'amount_currency': 30.0,    'account_id': self.stock_input_account.id},
            {'balance': -60.0,  'amount_currency': 0.0,     'account_id': self.stock_input_account.id},
        ])
        input_aml = self.env['account.move.line'].search([('account_id', '=', self.stock_input_account.id)])
        self.assertEqual(len(input_aml), 3)

    def test_average_realtime_with_two_delivery_anglo_saxon_valuation_multicurrency_different_dates(self):
        """
        The PO and invoice are in the same foreign currency.
        The deliveries occur at different times and rates
        The invoice is created at an even different date
        This should create a price difference entry.
        """
        company = self.env.user.company_id
        company.anglo_saxon_accounting = True
        company.currency_id = self.usd_currency

        date_po = '2019-01-01'
        date_delivery = '2019-01-08'
        date_delivery1 = '2019-01-16'
        date_invoice = '2019-01-10'
        date_invoice1 = '2019-01-20'

        self.product1.categ_id.property_valuation = 'real_time'
        self.product1.categ_id.property_cost_method = 'average'
        product_avg = self.product1_copy
        product_avg.write({
            'purchase_method': 'purchase',
            'name': 'AVG',
            'standard_price': 0,
        })

        # SetUp currency and rates
        self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", (self.usd_currency.id, company.id))
        self.env['res.currency.rate'].search([]).unlink()
        self.env['res.currency.rate'].create([{
            'name': date_po,
            'rate': 1.0,
            'currency_id': self.usd_currency.id,
            'company_id': company.id,
        }, {
            'name': date_po,
            'rate': 1.5,
            'currency_id': self.eur_currency.id,
            'company_id': company.id,
        }, {
            'name': date_delivery,
            'rate': 0.7,
            'currency_id': self.eur_currency.id,
            'company_id': company.id,
        }, {
            'name': date_delivery1,
            'rate': 0.8,
            'currency_id': self.eur_currency.id,
            'company_id': company.id,
        }, {
            'name': date_invoice,
            'rate': 2,
            'currency_id': self.eur_currency.id,
            'company_id': company.id,
        }, {
            'name': date_invoice1,
            'rate': 2.2,
            'currency_id': self.eur_currency.id,
            'company_id': company.id,
        }])

        # Proceed
        with freeze_time(date_po):
            po = self.env['purchase.order'].create({
                'currency_id': self.eur_currency.id,
                'partner_id': self.partner_id.id,
                'date_order': date_po,
                'order_line': [
                    (0, 0, {
                        'name': product_avg.name,
                        'product_id': product_avg.id,
                        'product_qty': 10.0,
                        'product_uom': product_avg.uom_po_id.id,
                        'price_unit': 30.0,
                        'date_planned': date_po,
                    })
                ],
            })
            po.button_confirm()

        line_product_avg = po.order_line.filtered(lambda l: l.product_id == product_avg)

        with freeze_time(date_delivery):
            picking = po.picking_ids
            (picking.move_ids
                .filtered(lambda l: l.purchase_line_id == line_product_avg)
                .write({'quantity': 5.0, 'picked': True}))

            picking.button_validate()
            picking._action_done()  # Create Backorder
        # 5 Units received at rate 0.7 = 42.86
        self.assertAlmostEqual(product_avg.standard_price, 42.86)

        with freeze_time(date_invoice):
            inv = self.env['account.move'].with_context(default_move_type='in_invoice').create({
                'move_type': 'in_invoice',
                'invoice_date': date_invoice,
                'date': date_invoice,
                'currency_id': self.eur_currency.id,
                'partner_id': self.partner_id.id,
                'invoice_line_ids': [
                    (0, 0, {
                        'name': product_avg.name,
                        'price_unit': 20.0,
                        'product_id': product_avg.id,
                        'purchase_line_id': line_product_avg.id,
                        'quantity': 5.0,
                        'account_id': self.stock_input_account.id,
                        'tax_ids': [],
                    })
                ]
            })
            inv.action_post()
        # 5 Units invoiced at rate 2 instead of 0.7 and price unit 20 = 10
        self.assertAlmostEqual(product_avg.standard_price, 10)

        with freeze_time(date_delivery1):
            backorder_picking = self.env['stock.picking'].search([('backorder_id', '=', picking.id)])
            (backorder_picking.move_ids
                .filtered(lambda l: l.purchase_line_id == line_product_avg)
                .write({'quantity': 5.0, 'picked': True}))
            backorder_picking.button_validate()
        # 5 Units invoiced at rate 2 (10) + 5 Units received at rate 0.8 (37.50) = 23.75
        self.assertAlmostEqual(product_avg.standard_price, 23.75)

        with freeze_time(date_invoice1):
            inv1 = self.env['account.move'].with_context(default_move_type='in_invoice').create({
                'move_type': 'in_invoice',
                'invoice_date': date_invoice1,
                'date': date_invoice1,
                'currency_id': self.eur_currency.id,
                'partner_id': self.partner_id.id,
                'invoice_line_ids': [
                    (0, 0, {
                        'name': product_avg.name,
                        'price_unit': 40.0,
                        'product_id': product_avg.id,
                        'purchase_line_id': line_product_avg.id,
                        'quantity': 5.0,
                        'account_id': self.stock_input_account.id,
                        'tax_ids': [],
                    })
                ]
            })
            inv1.action_post()
        # 5 Units invoiced at rate 2 (10) + 5 Units invoiced at rate 2.2 and unit price 40 (18.18) = 14.09
        self.assertAlmostEqual(product_avg.standard_price, 14.09)
        ##########################
        #       Invoice 0        #
        ##########################

        self.assertRecordValues(inv.line_ids, [
            # pylint: disable=C0326
            {'balance': 50.0,   'amount_currency': 100.0,   'account_id': self.stock_input_account.id},
            {'balance': -50.0,  'amount_currency': -100.0,  'account_id': self.company_data['default_account_payable'].id},
        ])

        ##########################
        #       Invoice 1        #
        ##########################

        self.assertRecordValues(inv1.line_ids, [
            # pylint: disable=C0326
            {'balance': 90.91,  'amount_currency': 200.0,   'account_id': self.stock_input_account.id},
            {'balance': -90.91, 'amount_currency': -200.0,  'account_id': self.company_data['default_account_payable'].id},
        ])

        ##########################
        #    Reconcile           #
        ##########################
        self.assertTrue(inv.line_ids.full_reconcile_id)
        self.assertTrue(inv1.line_ids.full_reconcile_id)
        self.assertRecordValues(inv.line_ids.full_reconcile_id.reconciled_line_ids.sorted('id'), [
            # pylint: disable=C0326
            {'balance': -214.29,    'amount_currency': -150.0},
            {'balance': 50,         'amount_currency': 100},
            {'balance': 164.29,     'amount_currency': 0.0},
            {'balance': 0.00,       'amount_currency': 50}
        ])
        self.assertRecordValues(inv1.line_ids.full_reconcile_id.reconciled_line_ids.sorted('id'), [
            # pylint: disable=C0326
            {'balance': -187.5,     'amount_currency': -150.0},
            {'balance': 90.91,      'amount_currency': 200.0},
            {'balance': 96.59,      'amount_currency': 0.0},
            {'balance': 0.00,       'amount_currency': -50.0},
        ])

    def test_anglosaxon_valuation_price_total_diff_discount(self):
        """
        PO:  price unit: 110
        Inv: price unit: 100
             discount:    10
        """
        self.env.company.anglo_saxon_accounting = True
        self.product1.categ_id.property_cost_method = 'fifo'
        self.product1.categ_id.property_valuation = 'real_time'

        # Create PO
        po_form = Form(self.env['purchase.order'])
        po_form.partner_id = self.partner_id
        with po_form.order_line.new() as po_line:
            po_line.product_id = self.product1
            po_line.product_qty = 1
            po_line.price_unit = 110.0
        order = po_form.save()
        order.button_confirm()

        # Receive the goods
        receipt = order.picking_ids[0]
        receipt.move_ids.quantity = 1
        receipt.move_ids.picked = True
        receipt.button_validate()

        # Create an invoice with a different price and a discount
        invoice_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
        invoice_form.invoice_date = invoice_form.date
        invoice_form.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-order.id)
        with invoice_form.invoice_line_ids.edit(0) as line_form:
            line_form.price_unit = 100.0
            line_form.discount = 10.0
        invoice = invoice_form.save()
        invoice.action_post()

        # Check what was posted in the stock valuation account
        stock_valuation_aml = self.env['account.move.line'].search([('account_id', '=', self.stock_valuation_account.id)])
        self.assertEqual(
            len(stock_valuation_aml), 2,
            "Two lines for the stock valuation account: one from the receipt (debit 110) and one from the bill (credit 20)")
        self.assertAlmostEqual(sum(stock_valuation_aml.mapped('debit')), 110)
        self.assertAlmostEqual(sum(stock_valuation_aml.mapped('credit')), 20, msg="Credit of 20 because of the difference between the PO and its invoice")

        # Check what was posted in stock input account
        input_aml = self.env['account.move.line'].search([('account_id','=', self.stock_input_account.id)])
        self.assertEqual(len(input_aml), 3, "Only two lines should have been generated in stock input account: one when receiving the product, two when making the invoice.")
        self.assertAlmostEqual(sum(input_aml.mapped('debit')), 110, msg="Total debit value on stock input account should be equal to the original PO price of the product.")
        self.assertAlmostEqual(sum(input_aml.mapped('credit')), 110, msg="Total credit value on stock input account should be equal to the original PO price of the product.")

    def test_anglosaxon_valuation_discount(self):
        """
        PO:  price unit: 100
        Inv: price unit: 100
             discount:    10
        """
        self.env.company.anglo_saxon_accounting = True
        self.product1.categ_id.property_cost_method = 'fifo'
        self.product1.categ_id.property_valuation = 'real_time'

        # Create PO
        po_form = Form(self.env['purchase.order'])
        po_form.partner_id = self.partner_id
        with po_form.order_line.new() as po_line:
            po_line.product_id = self.product1
            po_line.product_qty = 1
            po_line.price_unit = 100.0
        order = po_form.save()
        order.button_confirm()

        # Receive the goods
        receipt = order.picking_ids[0]
        receipt.move_ids.quantity = 1
        receipt.move_ids.picked = True
        receipt.button_validate()

        # Create an invoice with a different price and a discount
        invoice_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
        invoice_form.invoice_date = invoice_form.date
        invoice_form.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-order.id)
        with invoice_form.invoice_line_ids.edit(0) as line_form:
            line_form.tax_ids.clear()
            line_form.discount = 10.0
        invoice = invoice_form.save()
        invoice.action_post()

        # Check what was posted in the stock valuation account
        stock_valuation_aml = self.env['account.move.line'].search([('account_id', '=', self.stock_valuation_account.id)])
        self.assertEqual(len(stock_valuation_aml), 2, "Only one line should have been generated in the price difference account.")
        self.assertAlmostEqual(sum(stock_valuation_aml.mapped('debit')), 100)
        self.assertAlmostEqual(sum(stock_valuation_aml.mapped('credit')), 10, msg="Credit of 10 because of the 10% discount")

        # Check what was posted in stock input account
        input_aml = self.env['account.move.line'].search([('account_id', '=', self.stock_input_account.id)])
        self.assertEqual(len(input_aml), 3, "Three lines generated in stock input account: one when receiving the product, two when making the invoice.")
        self.assertAlmostEqual(sum(input_aml.mapped('debit')), 100, msg="Total debit value on stock input account should be equal to the original PO price of the product.")
        self.assertAlmostEqual(sum(input_aml.mapped('credit')), 100, msg="Total credit value on stock input account should be equal to the original PO price of the product.")

    def test_anglosaxon_valuation_price_unit_diff_discount(self):
        """
        PO:  price unit:  90
        Inv: price unit: 100
             discount:    10
        """
        self.env.company.anglo_saxon_accounting = True
        self.product1.categ_id.property_cost_method = 'fifo'
        self.product1.categ_id.property_valuation = 'real_time'

        # Create PO
        po_form = Form(self.env['purchase.order'])
        po_form.partner_id = self.partner_id
        with po_form.order_line.new() as po_line:
            po_line.product_id = self.product1
            po_line.product_qty = 1
            po_line.price_unit = 90.0
        order = po_form.save()
        order.button_confirm()

        # Receive the goods
        receipt = order.picking_ids[0]
        receipt.move_ids.quantity = 1
        receipt.move_ids.picked = True
        receipt.button_validate()

        # Create an invoice with a different price and a discount
        invoice_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
        invoice_form.invoice_date = invoice_form.date
        invoice_form.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-order.id)
        with invoice_form.invoice_line_ids.edit(0) as line_form:
            line_form.price_unit = 100.0
            line_form.discount = 10.0
        invoice = invoice_form.save()
        invoice.action_post()

        # Check if something was posted in the price difference account
        price_diff_aml = self.env['account.move.line'].search([('account_id', '=', self.stock_valuation_account.id)])
        self.assertEqual(price_diff_aml.debit, 90, "Should have only one line in the stock valuation account, created by the receipt.")

        # Check what was posted in stock input account
        input_aml = self.env['account.move.line'].search([('account_id', '=', self.stock_input_account.id)])
        self.assertEqual(len(input_aml), 2, "Only two lines should have been generated in stock input account: one when receiving the product, one when making the invoice.")
        self.assertAlmostEqual(sum(input_aml.mapped('debit')), 90, "Total debit value on stock input account should be equal to the original PO price of the product.")
        self.assertAlmostEqual(sum(input_aml.mapped('credit')), 90, "Total credit value on stock input account should be equal to the original PO price of the product.")

    def test_anglosaxon_valuation_price_unit_diff_avco(self):
        """
        Inv: price unit: 100
        """
        self.env.company.anglo_saxon_accounting = True
        self.product1.categ_id.property_cost_method = 'average'
        self.product1.categ_id.property_valuation = 'real_time'
        self.product1.standard_price = 1.01

        invoice = self.env['account.move'].create({
            'move_type': 'in_invoice',
            'invoice_date': '2022-03-31',
            'partner_id': self.partner_id.id,
            'invoice_line_ids': [
                (0, 0, {'product_id': self.product1.id, 'quantity': 10.50, 'price_unit': 1.01, 'tax_ids': self.tax_purchase_a.ids})
            ]
        })

        # Check if something was posted in the stock valuation account.
        stock_val_aml = invoice.line_ids.filtered(lambda l: l.account_id == self.stock_valuation_account)
        self.assertEqual(len(stock_val_aml), 0, "No line should have been generated in the stock valuation account.")

    def test_price_diff_with_partial_bills_and_delivered_qties(self):
        """
        Fifo + Real time.
        Default UoM of the product is Unit.
        Company in USD. 1 USD = 2 EUR.
        Receive 10 Hundred @ $50:
            Receive 7 Hundred (R1)
            Receive 3 Hundred (R2)
        Deliver 5 Hundred
        Bill
            1 Hundred @ 120€ -> already out
            3 Hundred @ 120€ -> already out
            2 Hundred @ 120€ -> one is out, the other is in the stock
            4 Hundred @ 120€ -> nothing out
            When billing:
            - The already-delivered qty should not generate any SVL for the
            price difference and we should directly post some COGS entries
            - The in-stock qty should generate an SVL for the price difference,
            and we should post the journal entries related to that SVL
        Deliver 2 Hundred
            The SVL should include:
                - 2 x 50 (cost by hundred)
                - 2 x 10 (the price diff from step "Bill 2 Hundred @ 60" and "Bill
                          4 Hundred @ 60")
        On top of that, make sure that everything will still work if the option
        "Lock Posted Entries with Hash" is enabled on the stock journal
        """
        expected_svl_values = [] # USD
        warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
        stock_location = warehouse.lot_stock_id
        customer_location = self.env.ref('stock.stock_location_customers')

        eur_curr = self.env.ref('base.EUR')
        self.env['res.currency.rate'].create({
            'name': fields.Date.today(),
            'company_id': self.env.company.id,
            'currency_id': eur_curr.id,
            'rate': 2,
        })

        grp_uom = self.env.ref('uom.group_uom')
        self.env.user.write({'groups_id': [(4, grp_uom.id)]})
        uom_unit = self.env.ref('uom.product_uom_unit')
        uom_hundred = self.env['uom.uom'].create({
            'name': '100 x U',
            'category_id': uom_unit.category_id.id,
            'ratio': 100.0,
            'uom_type': 'bigger',
            'rounding': uom_unit.rounding,
        })
        self.product1.write({
            'uom_id': uom_unit.id,
            'uom_po_id': uom_unit.id,
        })

        self.product1.categ_id.property_cost_method = 'fifo'
        self.product1.categ_id.property_valuation = 'real_time'
        self.product1.categ_id.property_stock_journal.restrict_mode_hash_table = True

        po_form = Form(self.env['purchase.order'])
        po_form.partner_id = self.partner_id
        with po_form.order_line.new() as po_line:
            po_line.product_id = self.product1
            po_line.product_qty = 10
            po_line.product_uom = uom_hundred
            po_line.price_unit = 50.0
        po = po_form.save()
        po.button_confirm()

        # Receive 7 Hundred
        receipt01 = po.picking_ids[0]
        receipt01.move_ids.move_line_ids.quantity = 700
        receipt01.move_ids.picked = True
        action = receipt01.button_validate()
        backorder_wizard = Form(self.env['stock.backorder.confirmation'].with_context(action['context'])).save()
        backorder_wizard.process()

        expected_svl_values += [7 * 50]
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)

        # Receive 3 Hundred
        receipt02 = receipt01.backorder_ids
        receipt02.move_ids.picked = True
        receipt02.button_validate()

        expected_svl_values += [3 * 50]
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)

        # Delivery 5 Hundred
        delivery01 = self.env['stock.picking'].create({
            'location_id': stock_location.id,
            'location_dest_id': customer_location.id,
            'picking_type_id': warehouse.out_type_id.id,
            'move_ids': [(0, 0, {
                'name': self.product1.name,
                'product_id': self.product1.id,
                'product_uom_qty': 5,
                'product_uom': uom_hundred.id,
                'location_id': stock_location.id,
                'location_dest_id': customer_location.id,
            })],
            'state': 'draft',
        })
        delivery01.action_confirm()
        delivery01.move_ids.picked = True
        delivery01.button_validate()

        expected_svl_values += [-5 * 50]
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)

        # We will create a price diff SVL only for the remaining quantities not yet billed
        # On the bill, price unit is 120€, i.e. $60 -> price diff equal to $10
        expense_account = self.company_data['default_account_expense']
        valuation_amls = self.env['account.move.line'].search([('account_id', '=', self.stock_valuation_account.id)])
        expense_amls = self.env['account.move.line'].search([('account_id', '=', expense_account.id)])
        input_amls = self.env['account.move.line'].search([('account_id', '=', self.stock_input_account.id)])
        bills = self.env['account.move']
        # pylint: disable=bad-whitespace
        for qty,    new_svl_expected,       expected_valuations,    expected_expenses in [
            (1,     [],                     [],                     [10.0]),    # 1 hundred already out
            (3,     [],                     [],                     [30.0]),    # 3 hundred already out
            (2,     [1 * 10.0],             [10.0],                 [10.0]),    # 1 hundred already out and 1 hundred in stock (from R1)
            (4,     [1 * 10.0, 3 * 10.0],   [3 * 10.0, 1 * 10.0],   []),        # 4 hundred in stock, 1 from R1 and 3 from R2
        ]:
            bill_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
            bill_form.invoice_date = bill_form.date
            bill_form.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-po.id)
            bill = bill_form.save()
            bill.invoice_line_ids.quantity = qty
            bill.invoice_line_ids.price_unit = 120.0
            bill.invoice_line_ids.product_uom_id = uom_hundred
            bill.currency_id = eur_curr
            bill.action_post()

            bills |= bill
            err_msg = 'Incorrect while billing %s hundred' % qty

            # stock side
            expected_svl_values += new_svl_expected
            self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values, err_msg)

            # account side
            new_valuation_amls = self.env['account.move.line'].search([('account_id', '=', self.stock_valuation_account.id), ('id', 'not in', valuation_amls.ids)])
            new_expense_amls = self.env['account.move.line'].search([('account_id', '=', expense_account.id), ('id', 'not in', expense_amls.ids)])
            new_input_amls = self.env['account.move.line'].search([('account_id', '=', self.stock_input_account.id), ('id', 'not in', input_amls.ids)])
            valuation_amls |= new_valuation_amls
            expense_amls |= new_expense_amls
            input_amls |= new_input_amls

            self.assertEqual(new_valuation_amls.mapped('debit'), expected_valuations, err_msg)
            self.assertEqual(new_expense_amls.mapped('debit'), expected_expenses, err_msg)
            self.assertEqual(new_input_amls.filtered(lambda aml: aml.credit > 0).mapped('credit'), expected_expenses + expected_valuations, err_msg)
            self.assertEqual(new_input_amls.filtered(lambda aml: aml.debit > 0).debit, qty * 60, err_msg)

        # All AML of Stock Interim Receipt should be reconciled
        input_amls = bills.line_ids.filtered(lambda aml: aml.account_id == self.stock_input_account)
        full_reconcile = input_amls[0].full_reconcile_id
        self.assertTrue(full_reconcile)
        self.assertTrue(all(aml.full_reconcile_id == full_reconcile for aml in input_amls))

        # Delivery 2 Hundred
        delivery02 = self.env['stock.picking'].create({
            'location_id': stock_location.id,
            'location_dest_id': customer_location.id,
            'picking_type_id': warehouse.out_type_id.id,
            'move_ids': [(0, 0, {
                'name': self.product1.name,
                'product_id': self.product1.id,
                'product_uom_qty': 2,
                'product_uom': uom_hundred.id,
                'location_id': stock_location.id,
                'location_dest_id': customer_location.id,
            })],
            'state': 'draft',
        })
        delivery02.action_confirm()
        delivery02.move_ids.picked = True
        delivery02.button_validate()

        expected_svl_values += [-2 * 50 + -2 * 10]
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)

        svl_r01, svl_r02, _svl_d01, svl_diff_01, svl_diff_02, svl_diff_03, _svl_d02 = self.product1.stock_valuation_layer_ids
        self.assertEqual(svl_diff_01.stock_valuation_layer_id, svl_r01)
        self.assertEqual(svl_diff_02.stock_valuation_layer_id, svl_r01)
        self.assertEqual(svl_diff_03.stock_valuation_layer_id, svl_r02)

    def test_partial_bills_and_reconciliation(self):
        """
        Fifo, Auto
        Receive 5
        Deliver 5
        Bill 1 (with price diff)
        Bill 4 (with price diff)
        The lines in stock input account should be reconciled
        """
        self.product1.categ_id.property_cost_method = 'fifo'
        self.product1.categ_id.property_valuation = 'real_time'

        warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
        stock_location = warehouse.lot_stock_id
        customer_location = self.env.ref('stock.stock_location_customers')

        po_form = Form(self.env['purchase.order'])
        po_form.partner_id = self.partner_id
        with po_form.order_line.new() as po_line:
            po_line.product_id = self.product1
            po_line.product_qty = 5
            po_line.price_unit = 50.0
        po = po_form.save()
        po.button_confirm()

        receipt = po.picking_ids[0]
        receipt.move_ids.picked = True
        receipt.button_validate()

        delivery = self.env['stock.picking'].create({
            'location_id': stock_location.id,
            'location_dest_id': customer_location.id,
            'picking_type_id': warehouse.out_type_id.id,
            'move_ids': [(0, 0, {
                'name': self.product1.name,
                'product_id': self.product1.id,
                'product_uom_qty': 5,
                'location_id': stock_location.id,
                'location_dest_id': customer_location.id,
            })],
            'state': 'draft',
        })
        delivery.action_confirm()
        delivery.move_ids.picked = True
        delivery.button_validate()

        bill01_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
        bill01_form.invoice_date = bill01_form.date
        bill01_form.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-po.id)
        bill01 = bill01_form.save()
        bill01.invoice_line_ids.quantity = 1
        bill01.invoice_line_ids.price_unit = 60
        bill01.action_post()

        bill02_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
        bill02_form.invoice_date = bill02_form.date
        bill02_form.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-po.id)
        bill02 = bill02_form.save()
        bill02.invoice_line_ids.quantity = 4
        bill02.invoice_line_ids.price_unit = 60
        bill02.action_post()

        input_amls = (bill01 + bill02).line_ids.filtered(lambda aml: aml.account_id == self.stock_input_account)
        full_reconcile = input_amls[0].full_reconcile_id
        self.assertTrue(full_reconcile)
        self.assertTrue(all(aml.full_reconcile_id == full_reconcile for aml in input_amls))

    def test_pdiff_and_credit_notes(self):
        """
        Auto FIFO
        PO 12 @ 10
        Receive with backorders: 4, 3 and then 5
            Will generate 3 SVL
        Bill:
            BILL01: 3 @ 12
            BILL02: 2 @ 11
            BILL03: 1 @ 15
            BILL04: 4 @ 9
            BILL05: 2 @ 10
            -: Refund 1 from BILL01
            -: Refund all from BILL02
            -: Refund 2 from BILL04
            -: Refund 1 from BILL05
            BILL06: 6 @ 18
        """
        self.product1.categ_id.property_cost_method = 'fifo'
        self.product1.categ_id.property_valuation = 'real_time'

        po_form = Form(self.env['purchase.order'])
        po_form.partner_id = self.partner_id
        with po_form.order_line.new() as po_line:
            po_line.product_id = self.product1
            po_line.product_qty = 12
            po_line.price_unit = 10.0
        po = po_form.save()
        po.button_confirm()

        receipt01 = po.picking_ids
        receipt01.move_ids.move_line_ids.quantity = 4
        receipt01.move_ids.picked = True
        action = receipt01.button_validate()
        backorder_wizard = Form(self.env['stock.backorder.confirmation'].with_context(action['context'])).save()
        backorder_wizard.process()

        receipt02 = receipt01.backorder_ids
        receipt02.move_ids.move_line_ids.quantity = 3
        receipt02.move_ids.picked = True
        action = receipt02.button_validate()
        backorder_wizard = Form(self.env['stock.backorder.confirmation'].with_context(action['context'])).save()
        backorder_wizard.process()

        receipt03 = receipt02.backorder_ids
        receipt03.move_ids.move_line_ids.quantity = 5
        receipt03.move_ids.picked = True
        receipt03.button_validate()

        expected_svl_values = [40, 30, 50]
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('remaining_value'), expected_svl_values)
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)

        # pylint: disable=bad-whitespace
        for qty,    price,  expected_svl_values,                                expected_svl_remaining_values in [
            (3.0,   12.0,   [40.0, 30.0, 50.0, 6.0],                            [46.0, 30.0, 50.0, 0.0]),
            (2.0,   11.0,   [40.0, 30.0, 50.0, 6.0, 1.0, 1.0],                  [47.0, 31.0, 50.0, 0.0, 0.0, 0.0]),
            (1.0,   15.0,   [40.0, 30.0, 50.0, 6.0, 1.0, 1.0, 5.0],             [47.0, 36.0, 50.0, 0.0, 0.0, 0.0, 0.0]),
            (4.0,   9.0,    [40.0, 30.0, 50.0, 6.0, 1.0, 1.0, 5.0, -1.0, -3.0], [47.0, 35.0, 47.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
            (2.0,   10.0,   [40.0, 30.0, 50.0, 6.0, 1.0, 1.0, 5.0, -1.0, -3.0], [47.0, 35.0, 47.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
        ]:
            self._bill(po, qty, price)
            self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values, 'Err while invoicing %s @ %s' % (qty, price))
            self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('remaining_value'), expected_svl_remaining_values, 'Err while invoicing %s @ %s' % (qty, price))

        bill01, bill02, _bill03, bill04, bill05 = po.invoice_ids.sorted('id')

        self._refund(bill01, 1.0)
        expected_svl_values += [-2.0]
        expected_svl_remaining_values += [0.0]
        # should impact the first layer
        expected_svl_remaining_values[0] -= 2.0
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('remaining_value'), expected_svl_remaining_values)

        self._refund(bill02)
        expected_svl_values += [-1.0, -1.0]
        expected_svl_remaining_values += [0.0, 0.0]
        # should impact the two first layers
        expected_svl_remaining_values[0] -= 1.0
        expected_svl_remaining_values[1] -= 1.0
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('remaining_value'), expected_svl_remaining_values)

        self._refund(bill04, 2.0)
        expected_svl_values += [1.0, 1.0]
        expected_svl_remaining_values += [0.0, 0.0]
        # should impact the two last layers
        expected_svl_remaining_values[1] += 1.0
        expected_svl_remaining_values[2] += 1.0
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('remaining_value'), expected_svl_remaining_values)

        self._refund(bill05, 1.0)
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('remaining_value'), expected_svl_remaining_values)

        self._bill(po, price=18.0)
        expected_svl_values += [16.0, 16.0, 16.0]
        expected_svl_remaining_values += [0.0, 0.0, 0.0]
        # should impact all layers
        expected_svl_remaining_values[0] += 16.0
        expected_svl_remaining_values[1] += 16.0
        expected_svl_remaining_values[2] += 16.0
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('remaining_value'), expected_svl_remaining_values)

        accounts = self.product1.product_tmpl_id._get_product_accounts()
        stock_in_amls = self.env['account.move.line'].search([('account_id', '=', accounts['stock_input'].id)], order='id')
        self.assertRecordValues(stock_in_amls, [
            # Receipts
            {'debit': 0.0, 'credit': 40.0},
            {'debit': 0.0, 'credit': 30.0},
            {'debit': 0.0, 'credit': 50.0},
            # Bill01 3 @ 12
            {'debit': 36.0, 'credit': 0.0},
            {'debit': 0.0, 'credit': 6.0},
            # Bill02 2 @ 11
            {'debit': 22.0, 'credit': 0.0},
            {'debit': 0.0, 'credit': 1.0},
            {'debit': 0.0, 'credit': 1.0},
            # Bill03 1 @ 15
            {'debit': 15.0, 'credit': 0.0},
            {'debit': 0.0, 'credit': 5.0},
            # Bill04 4 @ 9
            {'debit': 36.0, 'credit': 0.0},
            {'debit': 1.0, 'credit': 0.0},
            {'debit': 3.0, 'credit': 0.0},
            # Bill05 2 @ 10
            {'debit': 20.0, 'credit': 0.0},
            # Refund 1 from BILL01
            {'debit': 0.0, 'credit': 12.0},
            {'debit': 2.0, 'credit': 0.0},
            # Refund all from BILL02
            {'debit': 0.0, 'credit': 22.0},
            {'debit': 1.0, 'credit': 0.0},
            {'debit': 1.0, 'credit': 0.0},
            # Refund 2 from BILL04
            {'debit': 0.0, 'credit': 18.0},
            {'debit': 0.0, 'credit': 1.0},
            {'debit': 0.0, 'credit': 1.0},
            # Refund 1 from BILL05
            {'debit': 0.0, 'credit': 10.0},
            # BILL06: 6 @ 18
            {'debit': 108.0, 'credit': 0.0},
            {'debit': 0.0, 'credit': 16.0},
            {'debit': 0.0, 'credit': 16.0},
            {'debit': 0.0, 'credit': 16.0},
        ])
        self.assertTrue(all(aml.full_reconcile_id for aml in stock_in_amls))

    def test_pdiff_with_credit_notes_and_delivered_qties(self):
        """
        Auto FIFO
        IN 10 @ 10
        Bill 10 @ 12
        OUT 3
        Full Refund
        Bill 10 @ 9
        OUT 1
        Full Refund
        Bill 10 @ 10
        """
        self.product1.categ_id.property_cost_method = 'fifo'
        self.product1.categ_id.property_valuation = 'real_time'
        expected_svl_values = []
        expected_svl_remaining_values = []

        warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
        stock_location = warehouse.lot_stock_id
        customer_location = self.env.ref('stock.stock_location_customers')

        po_form = Form(self.env['purchase.order'])
        po_form.partner_id = self.partner_id
        with po_form.order_line.new() as po_line:
            po_line.product_id = self.product1
            po_line.product_qty = 10
            po_line.price_unit = 10.0
        po = po_form.save()
        po.button_confirm()

        receipt = po.picking_ids
        receipt.move_ids.move_line_ids.quantity = 10
        receipt.move_ids.picked = True
        receipt.button_validate()
        expected_svl_values += [100.0]
        expected_svl_remaining_values += [100.0]
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('remaining_value'), expected_svl_remaining_values)

        bill01 = self._bill(po, price=12)
        expected_svl_values += [20.0]
        expected_svl_remaining_values += [0.0]
        expected_svl_remaining_values[0] += 20.0  # should impact the layer of the receipt
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('remaining_value'), expected_svl_remaining_values)

        delivery = self.env['stock.picking'].create({
            'location_id': stock_location.id,
            'location_dest_id': customer_location.id,
            'picking_type_id': warehouse.out_type_id.id,
            'move_ids': [(0, 0, {
                'name': self.product1.name,
                'product_id': self.product1.id,
                'product_uom_qty': 3,
                'product_uom': self.product1.uom_id.id,
                'location_id': stock_location.id,
                'location_dest_id': customer_location.id,
            })],
        })
        delivery.action_confirm()
        delivery.move_ids.quantity = 3.0
        delivery.move_ids.picked = True
        delivery.button_validate()
        expected_svl_values += [-36.0]
        expected_svl_remaining_values += [0.0]
        expected_svl_remaining_values[0] -= 36.0  # should impact the layer of the receipt
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('remaining_value'), expected_svl_remaining_values)

        self._refund(bill01)
        expected_svl_values += [-14.0]
        expected_svl_remaining_values += [0.0]
        expected_svl_remaining_values[0] -= 14.0  # should impact the layer of the receipt
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('remaining_value'), expected_svl_remaining_values)

        bill02 = self._bill(po, price=9)
        expected_svl_values += [-7.0]
        expected_svl_remaining_values += [0.0]
        expected_svl_remaining_values[0] -= 7.0  # should impact the layer of the receipt
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('remaining_value'), expected_svl_remaining_values)

        delivery = self.env['stock.picking'].create({
            'location_id': stock_location.id,
            'location_dest_id': customer_location.id,
            'picking_type_id': warehouse.out_type_id.id,
            'move_ids': [(0, 0, {
                'name': self.product1.name,
                'product_id': self.product1.id,
                'product_uom_qty': 1,
                'product_uom': self.product1.uom_id.id,
                'location_id': stock_location.id,
                'location_dest_id': customer_location.id,
            })],
        })
        delivery.action_confirm()
        delivery.move_ids.quantity = 1.0
        delivery.move_ids.picked = True
        delivery.button_validate()
        expected_svl_values += [-9.0]
        expected_svl_remaining_values += [0.0]
        expected_svl_remaining_values[0] -= 9.0  # should impact the layer of the receipt
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('remaining_value'), expected_svl_remaining_values)

        ctx = {'active_ids': bill02.ids, 'active_model': 'account.move'}
        credit_note_wizard = self.env['account.move.reversal'].with_context(ctx).create({
            'journal_id': bill02.journal_id.id,
        })
        res_id = credit_note_wizard.modify_moves()['res_id']
        bill03 = self.env['account.move'].browse(res_id)
        bill03.invoice_date = fields.Date.today()
        bill03.invoice_line_ids.price_unit = 10.0
        bill03.action_post()
        # Impact of the refund (bill03 should not impact the SVLs since the unit price is the one of the POL)
        expected_svl_values += [6.0]
        expected_svl_remaining_values += [0.0]
        expected_svl_remaining_values[0] += 6.0  # should impact the layer of the receipt
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('remaining_value'), expected_svl_remaining_values)

        accounts = self.product1.product_tmpl_id._get_product_accounts()
        stock_in_amls = self.env['account.move.line'].search([('account_id', '=', accounts['stock_input'].id)], order='id')
        self.assertRecordValues(stock_in_amls, [
            # IN 10 @ 10
            {'debit': 0.0, 'credit': 100.0},
            # Bill 10 @ 12
            {'debit': 120.0, 'credit': 0.0},
            {'debit': 0.0, 'credit': 20.0},
            # (OUT 3)
            # Refund: here, we skip the 3 products delivered in the meantime,
            #   i.e. we only compensate the on-hand quantity (hence the $14
            #   instead of $20)
            {'debit': 0.0, 'credit': 120.0},
            {'debit': 14.0, 'credit': 0.0},
            # Bill 10 @ 9
            {'debit': 90.0, 'credit': 0.0},
            {'debit': 3.0, 'credit': 0.0},
            {'debit': 7.0, 'credit': 0.0},
            # (OUT 1)
            # Refund: again, we skip the product delivered in the meantime.
            #   We have $3 which is the reversing entry of the inital bill
            #   and 6$ from the stock valuation correction
            {'debit': 0.0, 'credit': 90.0},
            {'debit': 0.0, 'credit': 3.0},
            {'debit': 0.0, 'credit': 6.0},
            # Bill 10 @ 10
            {'debit': 100.0, 'credit': 0},
        ])

        self.assertEqual(sum(stock_in_amls.mapped('debit')) - sum(stock_in_amls.mapped('credit')), -5,
                         "There should be a difference because of the skipped products while posting the refunds (see "
                         "comments in above `assertRecordValues`). The value is the sum of the price differences of each "
                         "delivery: 3 * $2 + 1 * $-1). The user will have to manually add some account entries to "
                         "balance the accounts")

    def test_pdiff_with_returns_and_credit_notes(self):
        """
        Auto FIFO
        IN 10 @ 10
        Return 3
        IN (Return) 3
        Bill 10 @ 12
            This step will impact 7 products of the first layer and 3 products
            of the last one (i.e. the second IN)
        Return 1
        Refund 1 (from PO)
        Return 5
        Refund 5 (from initial bill)
        """
        self.product1.categ_id.property_cost_method = 'fifo'
        self.product1.categ_id.property_valuation = 'real_time'
        accounts = self.product1.product_tmpl_id._get_product_accounts()
        expected_svl_values = []
        expected_svl_remaining_values = []

        po_form = Form(self.env['purchase.order'])
        po_form.partner_id = self.partner_id
        with po_form.order_line.new() as po_line:
            po_line.product_id = self.product1
            po_line.product_qty = 10
            po_line.price_unit = 10.0
        po = po_form.save()
        po.button_confirm()

        receipt = po.picking_ids
        receipt.move_ids.move_line_ids.quantity = 10
        receipt.move_ids.picked = True
        receipt.button_validate()
        expected_svl_values += [100.0]
        expected_svl_remaining_values += [100.0]
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('remaining_value'), expected_svl_remaining_values)

        return01 = self._return(receipt, qty=3)
        expected_svl_values += [-30.0]
        expected_svl_remaining_values += [0.0]
        expected_svl_remaining_values[0] -= 30
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('remaining_value'), expected_svl_remaining_values)

        self._return(return01)
        expected_svl_values += [30.0]
        expected_svl_remaining_values += [30.0]
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('remaining_value'), expected_svl_remaining_values)

        bill = self._bill(po, price=12)
        # two new layers because we have 7 remaining products in the first in-layer and 3 in the second one
        expected_svl_values += [14.0, 6.0]
        expected_svl_remaining_values += [0.0, 0.0]
        expected_svl_remaining_values[0] += 14.0
        # `expected_svl_remaining_values[1]` is the return, it does not change
        expected_svl_remaining_values[2] += 6.0
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('remaining_value'), expected_svl_remaining_values)
        stock_in_amls = self.env['account.move.line'].search([('account_id', '=', accounts['stock_input'].id)])
        self.assertTrue(stock_in_amls)
        self.assertTrue(all(aml.full_reconcile_id for aml in stock_in_amls))

        self._return(receipt, qty=1)
        expected_svl_values += [-12.0]
        expected_svl_remaining_values += [0.0]
        expected_svl_remaining_values[0] -= 12.0
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('remaining_value'), expected_svl_remaining_values)

        refund = self._bill(po, price=12)
        self.assertEqual(refund.move_type, 'in_refund')
        stock_in_amls = self.env['account.move.line'].search([('account_id', '=', accounts['stock_input'].id)])
        self.assertTrue(stock_in_amls)
        self.assertTrue(all(aml.full_reconcile_id for aml in stock_in_amls))

        self._return(receipt, qty=5)
        expected_svl_values += [-60.0]
        expected_svl_remaining_values += [0.0]
        expected_svl_remaining_values[0] -= 60.0
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('remaining_value'), expected_svl_remaining_values)

        self._refund(bill, qty=5)
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)
        self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('remaining_value'), expected_svl_remaining_values)
        stock_in_amls = self.env['account.move.line'].search([('account_id', '=', accounts['stock_input'].id)], order='id')
        self.assertRecordValues(stock_in_amls, [
            # IN 10 @ 10
            {'debit': 0.0, 'credit': 100.0},
            # Return 3
            {'debit': 30.0, 'credit': 0.0},
            # IN (Return) 3
            {'debit': 0.0, 'credit': 30.0},
            # Bill 10 @ 12
            {'debit': 120, 'credit': 0.0},
            {'debit': 0.0, 'credit': 14.0},
            {'debit': 0.0, 'credit': 6.0},
            # Return 1
            {'debit': 12.0, 'credit': 0.0},
            # Refund 1
            {'debit': 0, 'credit': 12.0},
            # Return 5
            {'debit': 60.0, 'credit': 0.0},
            # Refund 5
            {'debit': 0.0, 'credit': 60.0},
        ])
        self.assertTrue(all(aml.full_reconcile_id for aml in stock_in_amls))

    def _test_pdiff_and_order_between_bills_common(self):
        self.product1.categ_id.property_cost_method = 'fifo'
        self.product1.categ_id.property_valuation = 'real_time'

        po_form = Form(self.env['purchase.order'])
        po_form.partner_id = self.partner_id
        with po_form.order_line.new() as po_line:
            po_line.product_id = self.product1
            po_line.product_qty = 2
            po_line.price_unit = 10.0
        po = po_form.save()
        po.button_confirm()

        receipt01 = po.picking_ids
        receipt01.move_ids.move_line_ids.quantity = 1
        receipt01.move_ids.picked = True
        action = receipt01.button_validate()
        backorder_wizard = Form(self.env['stock.backorder.confirmation'].with_context(action['context'])).save()
        backorder_wizard.process()

        receipt02 = receipt01.backorder_ids
        receipt02.move_ids.move_line_ids.quantity = 1
        receipt02.move_ids.picked = True
        receipt02.button_validate()

        bill01 = self._bill(po, 1.0, 11)

        ctx = {'active_ids': bill01.ids, 'active_model': 'account.move'}
        credit_note_wizard = self.env['account.move.reversal'].with_context(ctx).create({
            'journal_id': bill01.journal_id.id,
        })
        refund = self.env['account.move'].browse(credit_note_wizard.refund_moves()['res_id'])

        action = po.action_create_invoice()
        bill02 = self.env["account.move"].browse(action["res_id"])
        bill02.invoice_date = fields.Date.today()
        bill02.invoice_line_ids.quantity = 1.0
        bill02.invoice_line_ids.price_unit = 12

        return po, refund, bill02

    def test_pdiff_and_aml_labels(self):
        """
        When posting the bill, if an AML has a pdiff, it should not change any
        label of the bill
        """
        self.product1.type = 'consu'
        self.product1.categ_id.property_cost_method = 'fifo'
        self.product1.categ_id.property_valuation = 'real_time'

        po_form = Form(self.env['purchase.order'])
        po_form.partner_id = self.partner_id
        with po_form.order_line.new() as po_line:
            po_line.product_id = self.product1
            po_line.product_qty = 1
            po_line.price_unit = 10.0
        with po_form.order_line.new() as po_line:
            po_line.product_id = self.product_b
            po_line.product_qty = 1
            po_line.price_unit = 10.0
        po = po_form.save()
        po.button_confirm()

        receipt01 = po.picking_ids
        receipt01.move_ids.move_line_ids.quantity = 1
        receipt01.button_validate()

        action = po.action_create_invoice()
        bill = self.env["account.move"].browse(action["res_id"])
        bill.invoice_date = fields.Date.today()
        label01, label02 = bill.invoice_line_ids.mapped('name')
        self.assertTrue(label01)
        self.assertTrue(label02)

        bill.invoice_line_ids.price_unit = 11.0
        bill.action_post()
        self.assertEqual(bill.invoice_line_ids.mapped('name'), [label01, label02])

    def test_pdiff_and_order_between_bills_01(self):
        """
        Auto fifo
        IN 1 @ 10 -> SVL01
        IN 1 @ 10 -> SVL02
        BILL01 1 @ 11
            Should impact SVL01
        Create draft Refund
        Create draft BILL02 1 @ 12
        Post Refund
        Post BILL02
            Should impact SVL01
        Bill03 1 @ 13
            Should impact SVL02
        """
        po, refund, bill02 = self._test_pdiff_and_order_between_bills_common()
        refund.action_post()
        bill02.action_post()
        self._bill(po, price=13)

        svls = self.product1.stock_valuation_layer_ids
        self.assertEqual(svls.mapped('remaining_value'), [12.0, 13.0, 0.0, 0.0, 0.0, 0.0])
        self.assertEqual(svls.mapped('value'), [10.0, 10.0, 1.0, -1.0, 2.0, 3.0])

    def test_pdiff_and_order_between_bills_02(self):
        """
        Auto fifo
        IN 1 @ 10 -> SVL01
        IN 1 @ 10 -> SVL02
        BILL01 1 @ 11
            Should impact SVL01
        Create draft Refund
        Create draft BILL02 1 @ 12
        Post BILL02
            Should impact SVL02
        Post Refund
        Bill03 1 @ 13
            Should impact SVL01
        """
        po, refund, bill02 = self._test_pdiff_and_order_between_bills_common()
        bill02.action_post()
        refund.action_post()
        self._bill(po, price=13)

        svls = self.product1.stock_valuation_layer_ids
        self.assertEqual(svls.mapped('remaining_value'), [13.0, 12.0, 0.0, 0.0, 0.0, 0.0])
        self.assertEqual(svls.mapped('value'), [10.0, 10.0, 1.0, 2.0, -1.0, 3.0])

    def test_pdiff_multi_curr_and_rates(self):
        """
        Company in USD.
        Today: 100 EUR = 150 USD
        One day ago: 100 EUR = 130 USD
        Two days ago: 100 EUR = 125 USD
        Buy and receive one auto-AVCO product at 100 EUR. Bill it with:
        - Bill date: two days ago (125 USD)
        - Accounting date: one day ago (130 USD)
        The value at bill date should be used for both bill value and price
        diff value.
        """
        usd_currency = self.env.ref('base.USD')
        eur_currency = self.env.ref('base.EUR')

        today = fields.Date.today()
        one_day_ago = today - timedelta(days=1)
        two_days_ago = today - timedelta(days=2)

        self.env.company.currency_id = usd_currency.id

        self.env['res.currency.rate'].search([]).unlink()
        self.env['res.currency.rate'].create([{
            'name': day.strftime('%Y-%m-%d'),
            'rate': 1 / rate,
            'currency_id': eur_currency.id,
            'company_id': self.env.company.id,
        } for (day, rate) in [
            (today, 1.5),
            (one_day_ago, 1.3),
            (two_days_ago, 1.25),
        ]])

        self.product1.product_tmpl_id.categ_id.property_cost_method = 'average'

        po = self.env['purchase.order'].create({
            'partner_id': self.partner_id.id,
            'currency_id': eur_currency.id,
            'order_line': [
                (0, 0, {
                    'name': self.product1.name,
                    'product_id': self.product1.id,
                    'product_qty': 1.0,
                    'product_uom': self.product1.uom_po_id.id,
                    'price_unit': 100.0,
                    'taxes_id': False,
                }),
            ],
        })
        po.button_confirm()

        receipt = po.picking_ids
        receipt.move_ids.move_line_ids.quantity = 1.0
        receipt.button_validate()

        layer = receipt.move_ids.stock_valuation_layer_ids
        self.assertEqual(layer.value, 150)

        action = po.action_create_invoice()
        bill = self.env["account.move"].browse(action["res_id"])
        bill.invoice_date = two_days_ago
        bill.date = one_day_ago
        bill.action_post()

        pdiff_layer = layer.stock_valuation_layer_ids
        self.assertEqual(pdiff_layer.value, -25)
        self.assertEqual(layer.remaining_value, 125)

        in_stock_amls = self.env['account.move.line'].search([
            ('product_id', '=', self.product1.id),
            ('account_id', '=', self.stock_input_account.id),
        ], order='id')
        self.assertRecordValues(in_stock_amls, [
            # pylint: disable=bad-whitespace
            {'date': today,         'debit': 0,     'credit': 150,  'reconciled': True},
            {'date': one_day_ago,   'debit': 125,   'credit': 0,    'reconciled': True},
            {'date': one_day_ago,   'debit': 25,    'credit': 0,    'reconciled': True},
        ])

    def test_pdiff_lot_valuation(self):
        """
        use a product valuated by lot.
        Receipt some lots in the same purchase order, validate the picking
        create the bill with a price different from the PO. Check every layers
        for the lots have their own price difference correction layer.
        """

        self.cat.property_cost_method = 'average'
        product = self.env['product.product'].create({
            'name': 'product1',
            'is_storable': True,
            'tracking': 'serial',
            'categ_id': self.cat.id,
            'lot_valuated': True,
        })

        po = self.env['purchase.order'].create({
            'partner_id': self.partner_id.id,
            'order_line': [
                (0, 0, {
                    'name': product.name,
                    'product_id': product.id,
                    'product_qty': 3.0,
                    'product_uom': product.uom_po_id.id,
                    'price_unit': 100.0,
                    'taxes_id': False,
                }),
            ],
        })
        po.button_confirm()

        receipt = po.picking_ids
        i = 1
        for line in receipt.move_ids.move_line_ids:
            line.write({
                'lot_name': 'lot_' + str(i),
                'quantity': 1,
            })
            i += 1
        receipt.move_ids.picked = True
        receipt.button_validate()
        lots = receipt.move_line_ids.lot_id
        self.assertEqual(receipt.state, 'done')

        for lot in lots:
            self.assertEqual(lot.standard_price, 100)

        layers = receipt.move_ids.stock_valuation_layer_ids
        self.assertEqual(layers.mapped('value'), [100, 100, 100])

        action = po.action_create_invoice()
        bill = self.env["account.move"].browse(action["res_id"])
        bill.line_ids.price_unit = 150
        bill.invoice_date = fields.Date.today()
        bill.action_post()
        for lot in lots:
            self.assertEqual(lot.standard_price, 150)

        pdiff_layers = layers.stock_valuation_layer_ids
        self.assertRecordValues(pdiff_layers, [
            # pylint: disable=bad-whitespace
            {'quantity': 0, 'lot_id': lots[0].id, 'value': 50},
            {'quantity': 0, 'lot_id': lots[1].id, 'value': 50},
            {'quantity': 0, 'lot_id': lots[2].id, 'value': 50},
        ])

    def test_purchase_with_backorders_and_return_and_price_changes(self):
        """
        When you have multiples receipts associated to a Purchase Order, with 1 bill for each receipt,
            then each bill has an impact on its own receipt only, hence if I modify the price on Bill01,
            it will not have an effect on Receipt02.
        However, if we create a return for a portion of a receipt,
            the invoiced_qty will be higher than the received_qty. This could be iterpreted has the bill
            being done before the receipt, which is not the case.
        In this test, we ensure that if the Control Policy is 'On received quantities' (procure_method = 'receive'),
            we keep using the purchase price for the svl unit_cost even when invoiced_qty > received_qty.
        """
        self.product1.categ_id.property_cost_method = 'average'
        self.product1.categ_id.property_valuation = 'real_time'
        self.product1.purchase_method = 'receive'  # ControlPolicy

        po_form = Form(self.env['purchase.order'])
        po_form.partner_id = self.partner_id
        with po_form.order_line.new() as po_line:
            po_line.product_id = self.product1
            po_line.product_qty = 100
            po_line.price_unit = 10.0
        po = po_form.save()
        po.button_confirm()

        def _validate_backorder(po, qty):
            picking = po.picking_ids.filtered(lambda p: p.state not in ['done', 'draft', 'cancel']).ensure_one()
            picking.move_ids.move_line_ids.quantity = qty
            picking.button_validate()
            # Validate picking with backorder
            res_dict = picking.button_validate()
            wizard = self.env[(res_dict.get('res_model'))].browse(res_dict.get('res_id')).with_context(res_dict['context'])
            wizard.process()
            return picking

        receipt01 = _validate_backorder(po, 30)
        self.assertEqual(receipt01.move_ids.stock_valuation_layer_ids.ensure_one().value, 300.0)
        bill01 = self._bill(po, price=12)
        self.assertEqual(bill01.invoice_line_ids.stock_valuation_layer_ids.ensure_one().value, 60.0)

        receipt02 = _validate_backorder(po, 30)
        # Even though Bill01 updated the price for Receipt01, the layers of Receipt02 are not impacted.
        self.assertEqual(receipt02.move_ids.stock_valuation_layer_ids.ensure_one().value, 300.0)
        bill02 = self._bill(po, price=13)
        self.assertEqual(bill02.invoice_line_ids.stock_valuation_layer_ids.ensure_one().value, 90.0)

        # With the return, the invoiced qty > received qty,
        # this must NOT be interpreted as the invoice done before the picking (purchase_method = 'purchase')
        self._return(receipt02, qty=10)

        receipt03 = _validate_backorder(po, 30)
        # Like Receipt02 layers, Receipt03 layers should not be impacted by the previous price changes.
        self.assertEqual(receipt03.move_ids.stock_valuation_layer_ids.ensure_one().value, 300.0)

    def test_invoice_on_ordered_qty_with_backorder_and_different_currency_automated(self):
        """Create a PO with currency different from the company currency. Set the
        product to be invoiced on ordered quantities. Receive partially the products
        and create a backorder. Create an invoice for the ordered quantity. Then
        receive the backorder. Check if the valuation layer is correctly created.
        """
        usd_currency = self.env.ref('base.USD')
        self.env.company.currency_id = usd_currency.id
        self.product1.categ_id.property_cost_method = 'fifo'
        self.product1.categ_id.property_valuation = 'real_time'
        self.product1.purchase_method = 'purchase'

        price_unit_EUR = 100
        price_unit_USD = self.env.ref('base.EUR')._convert(price_unit_EUR, usd_currency, self.env.company, fields.Date.today(), round=False)
        po = self.env['purchase.order'].create({
            'partner_id': self.partner_id.id,
            'currency_id': self.env.ref('base.EUR').id,
            'order_line': [
                (0, 0, {
                    'name': self.product1.name,
                    'product_id': self.product1.id,
                    'product_qty': 12.0,
                    'product_uom': self.product1.uom_po_id.id,
                    'price_unit': 100.0,
                    'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
                }),
            ],
        })
        po.button_confirm()
        picking = po.picking_ids[0]
        move = picking.move_ids[0]
        move.quantity = 10
        move.picked = True
        res_dict = picking.button_validate()
        self.assertEqual(res_dict['res_model'], 'stock.backorder.confirmation')
        wizard = self.env[(res_dict.get('res_model'))].browse(res_dict.get('res_id')).with_context(res_dict['context'])
        wizard.process()
        self.assertAlmostEqual(move.stock_valuation_layer_ids.unit_cost, price_unit_USD)

        po.action_create_invoice()

        picking2 = po.picking_ids.filtered(lambda p: p.backorder_id)
        move2 = picking2.move_ids[0]
        move2.quantity = 2
        move2.picked = True
        picking2.button_validate()
        self.assertAlmostEqual(move2.stock_valuation_layer_ids.unit_cost, price_unit_USD)

    def test_invoice_on_ordered_qty_with_backorder_and_different_currency_manual(self):
        """Same test as test_invoice_on_ordered_qty_with_backorder_and_different_currency_automated with manual_periodic valuation
        Ensure that the absence of account_move_id on the layers does not generate an Exception
        """
        usd_currency = self.env.ref('base.USD')
        self.env.company.currency_id = usd_currency.id
        self.product1.categ_id.property_cost_method = 'fifo'
        self.product1.categ_id.property_valuation = 'manual_periodic'
        self.product1.purchase_method = 'purchase'

        price_unit_EUR = 100
        price_unit_USD = self.env.ref('base.EUR')._convert(price_unit_EUR, usd_currency, self.env.company, fields.Date.today(), round=False)
        po = self.env['purchase.order'].create({
            'partner_id': self.partner_id.id,
            'currency_id': self.env.ref('base.EUR').id,
            'order_line': [
                (0, 0, {
                    'name': self.product1.name,
                    'product_id': self.product1.id,
                    'product_qty': 12.0,
                    'product_uom': self.product1.uom_po_id.id,
                    'price_unit': 100.0,
                    'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
                }),
            ],
        })
        po.button_confirm()
        picking = po.picking_ids[0]
        move = picking.move_ids[0]
        move.quantity = 10
        move.picked = True
        res_dict = picking.button_validate()
        self.assertEqual(res_dict['res_model'], 'stock.backorder.confirmation')
        wizard = self.env[(res_dict.get('res_model'))].browse(res_dict.get('res_id')).with_context(res_dict['context'])
        wizard.process()
        self.assertAlmostEqual(move.stock_valuation_layer_ids.unit_cost, price_unit_USD)

        po.action_create_invoice()

        picking2 = po.picking_ids.filtered(lambda p: p.backorder_id)
        move2 = picking2.move_ids[0]
        move2.quantity = 2
        move2.picked = True
        picking2.button_validate()
        self.assertAlmostEqual(move2.stock_valuation_layer_ids.unit_cost, price_unit_USD)

    def test_pdiff_date_usererror(self):
        """
        Test pdiff operations complete without errors in case we don't have
        the bill date. A UserError is raised as usual.
        """
        self.product1.product_tmpl_id.categ_id.property_cost_method = 'average'

        po = self.env['purchase.order'].create({
            'partner_id': self.partner_id.id,
            'order_line': [
                (0, 0, {
                    'name': self.product1.name,
                    'product_id': self.product1.id,
                    'product_qty': 1.0,
                    'product_uom': self.product1.uom_po_id.id,
                    'price_unit': 100.0,
                    'taxes_id': False,
                }),
            ],
        })
        po.button_confirm()

        receipt = po.picking_ids
        receipt.move_ids.move_line_ids.quantity = 1.0
        receipt.button_validate()

        action = po.action_create_invoice()
        bill = self.env["account.move"].browse(action["res_id"])
        with self.assertRaises(exceptions.UserError):
            bill.action_post()

    def test_bill_date_exchange_rate_for_price_diff_amls(self):
        """Ensure sure that the amls for price difference uses the bill date exchange rate. They originally used today's rate
        which meant that their value depended on the day the bill was posted (as it is when the price diff amls are created).
        """
        company = self.env.user.company_id
        company.anglo_saxon_accounting = True
        company.currency_id = self.usd_currency

        self.product1.categ_id.property_cost_method = 'fifo'
        self.product1.categ_id.property_valuation = 'real_time'

        po_date = '2023-10-01'
        bill_date = '2023-11-01'
        today = fields.Date.today()

        po_rate = 2.0
        bill_rate = 3.0
        today_rate = 4.0

        self.env['res.currency.rate'].search([]).unlink()
        self.env['res.currency.rate'].create([
            {
                'name': po_date,
                'rate': po_rate,
                'currency_id': self.eur_currency.id,
                'company_id': company.id,
            },
            {
                'name': bill_date,
                'rate': bill_rate,
                'currency_id': self.eur_currency.id,
                'company_id': company.id,
            },
            {
                'name': today,
                'rate': today_rate,
                'currency_id': self.eur_currency.id,
                'company_id': company.id,
            },
        ])

        with freeze_time(po_date):
            purchase_price = 90
            po = self.env['purchase.order'].create({
                'partner_id': self.partner_id.id,
                'currency_id': self.eur_currency.id,
                'order_line': [
                    Command.create({
                        'product_id': self.product1.id,
                        'product_qty': 1.0,
                        'price_unit': purchase_price,
                        'taxes_id': False,
                    }),
                ]
            })
            po.button_confirm()

            receipt = po.picking_ids
            receipt.move_line_ids.quantity = 1
            receipt.button_validate()

        warehouse = self.env['stock.warehouse'].search([('company_id', '=', company.id)], limit=1)
        stock_location = warehouse.lot_stock_id
        customer_location = self.env.ref('stock.stock_location_customers')
        delivery = self.env['stock.picking'].create({
            'location_id': stock_location.id,
            'location_dest_id': customer_location.id,
            'picking_type_id': warehouse.out_type_id.id,
            'move_ids': [
                Command.create({
                    'name': self.product1.name,
                    'product_id': self.product1.id,
                    'product_uom_qty': 1.0,
                    'location_id': stock_location.id,
                    'location_dest_id': customer_location.id,
                })
            ]
        })
        delivery.action_confirm()
        delivery.move_ids.quantity = 1.0
        delivery.button_validate()

        po.action_create_invoice()
        bill = po.invoice_ids
        bill.invoice_date = bill_date
        bill.action_post()

        product_accounts = self.product1.product_tmpl_id.get_product_accounts()
        payable_id = self.company_data['default_account_payable'].id
        stock_in_id = product_accounts['stock_input'].id
        expense_id = product_accounts['expense'].id
        self.assertRecordValues(bill.line_ids, [
            # pylint: disable=bad-whitespace
            {'debit': 30,   'credit': 0,    'account_id': stock_in_id,  'reconciled': True,     'amount_currency': 90},
            {'debit': 0,    'credit': 30,   'account_id': payable_id,   'reconciled': False,    'amount_currency': -90},
            {'debit': 0,    'credit': 15,   'account_id': expense_id,   'reconciled': False,    'amount_currency': 0},
            {'debit': 15,   'credit': 0,    'account_id': stock_in_id,  'reconciled': True,     'amount_currency': 0},
        ])

        stock_amls = self.env['account.move.line'].search([('account_id', '=', stock_in_id)])
        self.assertRecordValues(stock_amls, [
            # pylint: disable=bad-whitespace
            {'debit': 30,   'credit': 0,    'account_id': stock_in_id,  'reconciled': True, 'amount_currency': 90},
            {'debit': 15,   'credit': 0,    'account_id': stock_in_id,  'reconciled': True, 'amount_currency': 0},
            {'debit': 0,    'credit': 45,   'account_id': stock_in_id,  'reconciled': True, 'amount_currency': -90},
        ])

    def test_invoice_first_receipt_later_with_multicurrency_different_dates(self):
        """Ensure sure that use currency rate at bill date rather than the current date when invoice before receipt"""
        company = self.env.user.company_id
        company.anglo_saxon_accounting = False
        company.currency_id = self.usd_currency

        self.product1.is_storable = True
        self.product1.purchase_method = 'purchase'

        self.product1.with_company(company).categ_id.property_cost_method = 'fifo'
        self.product1.with_company(company).categ_id.property_valuation = 'real_time'


        po_date = '2023-10-01'
        bill_date = '2023-10-15'
        receipt_date = '2023-10-31'

        po_rate = 0.8
        bill_rate = 2.0
        receipt_rate = 2.2

        self.env['res.currency.rate'].search([]).unlink()
        self.env['res.currency.rate'].create([
            {
                'name': po_date,
                'rate': po_rate,
                'currency_id': self.eur_currency.id,
                'company_id': company.id,
            },
            {
                'name': bill_date,
                'rate': bill_rate,
                'currency_id': self.eur_currency.id,
                'company_id': company.id,
            },
            {
                'name': receipt_date,
                'rate': receipt_rate,
                'currency_id': self.eur_currency.id,
                'company_id': company.id,
            },
        ])

        with freeze_time(po_date):
            purchase_price = 100
            po = self.env['purchase.order'].create({
                'partner_id': self.partner_id.id,
                'currency_id': self.eur_currency.id,
                'order_line': [
                    Command.create({
                        'product_id': self.product1.id,
                        'product_qty': 1.0,
                        'price_unit': purchase_price,
                        'taxes_id': False,
                    }),
                ]
            })
            po.button_confirm()

        with freeze_time(bill_date):
            po.action_create_invoice()
            bill = po.invoice_ids
            bill.invoice_date = bill_date
            bill.action_post()

        with freeze_time(receipt_date):
            receipt = po.picking_ids
            receipt.move_ids.write({'quantity': 1.0})
            receipt.button_validate()

        product_accounts = self.product1.product_tmpl_id.get_product_accounts()
        payable_id = self.company_data['default_account_payable'].id
        stock_in_id = product_accounts['stock_input'].id
        expense_id = product_accounts['expense'].id
        stock_valuation = product_accounts['stock_valuation'].id

        # 1 Units invoiced at rate 2 and unit price 100 = 50
        self.assertRecordValues(bill.line_ids, [
            # pylint: disable=bad-whitespace
            {'debit': 50.0,    'credit': 0,    'account_id': expense_id,   'reconciled': False,    'amount_currency':  100.0},
            {'debit': 0,        'credit': 50.0,  'account_id': payable_id,   'reconciled': False,    'amount_currency': -100.0},
        ])

        layer_receipt = receipt.move_ids.stock_valuation_layer_ids

        self.assertRecordValues(layer_receipt.account_move_id.line_ids, [
            # pylint: disable=bad-whitespace
            {'debit': 0,   'credit': 50.0,    'account_id': stock_in_id,  'reconciled': False, 'amount_currency': -110.0},
            {'debit': 50.0,   'credit': 0,    'account_id': stock_valuation,  'reconciled': False, 'amount_currency': 110.0},
        ])

    def test_analytic_distribution_propagation_with_exchange_difference(self):
        # Create 2 rates in order to generate an exchange difference later.
        eur = self.env.ref('base.EUR')
        eur.write({
            'rate_ids': [
                Command.clear(),
                Command.create({
                    'name': fields.Date.from_string('2023-01-01'),
                    'company_rate': 2.0,
                }),
                Command.create({
                    'name': fields.Date.from_string('2023-12-01'),
                    'company_rate': 3.0,
                }),
            ],
            'active': True,
        })

        # Create a mandatory analytic account.
        analytic_plan = self.env['account.analytic.plan'].create({
            'name': 'Analytic Plan',
            'default_applicability': 'mandatory',
        })
        analytic_account = self.env['account.analytic.account'].create({
            'name': 'Analytic Account',
            'plan_id': analytic_plan.id},
        )

        # Create a storable product with FIFO costing method and automated inventory valuation.
        analytic_product_category = self.env['product.category'].create({
            'name': 'Analytic Product Category',
            'property_cost_method': 'fifo',
            'property_valuation': 'real_time',
        })
        analytic_product = self.env['product.product'].create({
            'name': 'Analytic Product',
            'is_storable': True,
            'categ_id': analytic_product_category.id,
            'lst_price': 100.0,
            'standard_price': 25.0,
        })

        # Create and confirm a Purchase Order using aforementioned product and currency.
        purchase_order = self.env['purchase.order'].create({
            'date_order': fields.Date.from_string('2023-12-04'),
            'currency_id': eur.id,
            'partner_id': self.partner_a.id,
            'order_line': [
                Command.create({
                    'product_id': analytic_product.id,
                    'product_qty': 10.0,
                    'analytic_distribution': {analytic_account.id: 100},
                }),
            ],
        })
        purchase_order.button_confirm()

        # Make sure a stock move has been created to replenish the product.
        self.assertEqual(len(purchase_order.picking_ids.move_ids), 1)

        stock_move = purchase_order.picking_ids.move_ids
        stock_move.quantity = stock_move.product_uom_qty

        purchase_order.picking_ids.button_validate()
        purchase_order.action_create_invoice()

        # Make sure a first Journal Entry has been created (to account for the stock move).
        self.assertEqual(len(stock_move.account_move_ids), 1)
        stock_account_move = stock_move.account_move_ids

        # Make sure the Vendor Bill has been created,
        # and confirm it at an earlier date (to generate the exchange difference).
        self.assertEqual(len(purchase_order.invoice_ids), 1)

        vendor_bill = purchase_order.invoice_ids
        vendor_bill.invoice_date = fields.Date.from_string('2023-11-01')
        vendor_bill.action_post()

        # Make sure a second Journal Entry has been created (to account for the exchange difference).
        self.assertEqual(len(stock_move.account_move_ids), 2)
        exchange_account_move = stock_move.account_move_ids - stock_account_move

        # Make sure both exchange Journal Items have the correct analytic distribution.
        self.assertEqual(len(exchange_account_move.line_ids), 2)
        for line in exchange_account_move.line_ids:
            self.assertTrue(line.analytic_distribution)
            self.assertEqual(line.analytic_distribution[str(analytic_account.id)], 100)

    def test_curr_rates_and_out_qty(self):
        """
        Company in USD
        Yesterday:  1000 EUR = 4335.1 USD
        Today:      1000 EUR = 4348.0 USD

        Yesterday: Buy and receive one auto-AVCO product at 1000 EUR
        Today:
        - Deliver the product
        - Bill the PO

        When posting the bill, we should generate an exchange diff compensation
        """
        usd_currency = self.env.ref('base.USD')
        eur_currency = self.env.ref('base.EUR')

        warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
        customer_location = self.env.ref('stock.stock_location_customers')
        stock_location = warehouse.lot_stock_id

        today = fields.Date.today()
        yesterday = today - timedelta(days=1)

        self.env.company.currency_id = usd_currency.id

        self.env['res.currency.rate'].search([]).unlink()
        self.env['res.currency.rate'].create([{
            'name': day.strftime('%Y-%m-%d'),
            'rate': 1 / rate,
            'currency_id': eur_currency.id,
            'company_id': self.env.company.id,
        } for (day, rate) in [
            (yesterday, 4.3351),
            (today, 4.3480),
        ]])

        self.product1.product_tmpl_id.categ_id.property_cost_method = 'average'

        with freeze_time(yesterday):
            po = self.env['purchase.order'].create({
                'partner_id': self.partner_id.id,
                'currency_id': eur_currency.id,
                'order_line': [
                    (0, 0, {
                        'name': self.product1.name,
                        'product_id': self.product1.id,
                        'product_qty': 1.0,
                        'product_uom': self.product1.uom_po_id.id,
                        'price_unit': 1000.0,
                        'taxes_id': False,
                    }),
                ],
            })
            po.button_confirm()

            receipt = po.picking_ids
            receipt.move_ids.move_line_ids.quantity = 1.0
            receipt.button_validate()

        delivery = self.env['stock.picking'].create({
            'location_id': stock_location.id,
            'location_dest_id': customer_location.id,
            'picking_type_id': warehouse.out_type_id.id,
            'move_ids': [(0, 0, {
                'name': self.product1.name,
                'product_id': self.product1.id,
                'product_uom_qty': 1.0,
                'location_id': stock_location.id,
                'location_dest_id': customer_location.id,
            })]
        })
        delivery.action_confirm()
        delivery.move_ids.quantity = 1.0
        delivery.button_validate()

        action = po.action_create_invoice()
        bill = self.env["account.move"].browse(action["res_id"])
        bill.invoice_date = bill.date = today
        bill.action_post()

        in_stock_amls = self.env['account.move.line'].search([('account_id', '=', self.stock_input_account.id)], order='id')
        self.assertRecordValues(in_stock_amls, [
            # pylint: disable=bad-whitespace
            {'debit': 0,        'credit': 4335.1,   'reconciled': True},
            {'debit': 4348,     'credit': 0,        'reconciled': True},
            {'debit': 0,        'credit': 12.9,     'reconciled': True},
        ])

    def test_bill_with_zero_qty(self):
        """
        FIFO standard
        Receive two different product
        Bill them, but:
            Set the quantity of the first AML to zero
        Bill again the PO (for the "canceled" line in the first bill)
        """
        product1 = self.product1
        product2 = self.product1_copy

        self.cat.property_valuation = 'real_time'

        po_form = Form(self.env['purchase.order'])
        po_form.partner_id = self.partner_id
        with po_form.order_line.new() as po_line:
            po_line.product_id = product1
            po_line.product_qty = 1
            po_line.price_unit = 10.0
        with po_form.order_line.new() as po_line:
            po_line.product_id = product2
            po_line.product_qty = 1
            po_line.price_unit = 20.0
        po = po_form.save()
        po.button_confirm()

        receipt = po.picking_ids
        receipt.move_ids.move_line_ids.quantity = 1
        receipt.button_validate()

        action = po.action_create_invoice()
        bill01 = self.env["account.move"].browse(action["res_id"])
        bill01.invoice_date = fields.Date.today()
        bill01.invoice_line_ids.filtered(lambda l: l.product_id == product2).quantity = 0
        bill01.action_post()

        self.assertEqual(bill01.state, 'posted')
        self.assertRecordValues(po.order_line, [
            {'product_id': product1.id, 'qty_invoiced': 1.0},
            {'product_id': product2.id, 'qty_invoiced': 0.0},
        ])

        bill02 = self._bill(po)
        self.assertEqual(bill02.state, 'posted')
        self.assertRecordValues(po.order_line, [
            {'product_id': product1.id, 'qty_invoiced': 1.0},
            {'product_id': product2.id, 'qty_invoiced': 1.0},
        ])

        stock_in_amls = self.env['account.move.line'].search([('account_id', '=', self.stock_input_account.id), ('balance', '!=', 0)], order='id')
        self.assertRecordValues(stock_in_amls, [
            {'product_id': product1.id, 'debit': 10.0, 'credit': 0.0},
            {'product_id': product1.id, 'debit': 0.0, 'credit': 10.0},
            {'product_id': product2.id, 'debit': 20.0, 'credit': 0.0},
            {'product_id': product2.id, 'debit': 0.0, 'credit': 20.0},
        ])
        self.assertTrue(all(aml.full_reconcile_id for aml in stock_in_amls))

    def _test_fifo_and_returns_common(self):
        """
        FIFO auto
        Receive & Bill 1 @ 10
        """
        self.product1.categ_id.property_cost_method = 'fifo'
        self.product1.categ_id.property_valuation = 'real_time'

        po_form = Form(self.env['purchase.order'])
        po_form.partner_id = self.partner_id
        with po_form.order_line.new() as po_line:
            po_line.product_id = self.product1
            po_line.product_qty = 1
            po_line.price_unit = 10.0
        po = po_form.save()
        po.button_confirm()

        receipt = po.picking_ids
        receipt.move_ids.move_line_ids.quantity = 1
        receipt.button_validate()

        self._bill(po)

    def test_fifo_return_and_receive_all_on_backorder(self):
        """
        FIFO auto
        Receive & Bill 1 @ 10
        PO 4 @ 25
        Receive one with backorder
        Return it
        Receive 4 thanks to the backorder
        Bill them
        """
        self._test_fifo_and_returns_common()

        po_form = Form(self.env['purchase.order'])
        po_form.partner_id = self.partner_id
        with po_form.order_line.new() as po_line:
            po_line.product_id = self.product1
            po_line.product_qty = 4
            po_line.price_unit = 25.0
        po = po_form.save()
        po.button_confirm()

        receipt01 = po.picking_ids
        receipt01.move_ids.quantity = 1
        action = receipt01.button_validate()
        backorder_wizard = Form(self.env['stock.backorder.confirmation'].with_context(action['context'])).save()
        backorder_wizard.process()

        self._return(receipt01)

        receipt02 = receipt01.backorder_ids
        receipt02.move_ids.quantity = 4
        receipt02.button_validate()

        self._bill(po)

        in_stock_amls = self.env['account.move.line'].search([('account_id', '=', self.stock_input_account.id)], order='id')
        self.assertRecordValues(in_stock_amls, [
            # Receive and bill 1 @ 10
            {'debit': 0.0, 'credit': 10.0, 'reconciled': True},
            {'debit': 10.0, 'credit': 0.0, 'reconciled': True},
            # Receive 1 @ 25
            {'debit': 0.0, 'credit': 25.0, 'reconciled': True},
            # Return it (10 with valo, 15 with expense)
            {'debit': 10.0, 'credit': 0.0, 'reconciled': True},
            {'debit': 15.0, 'credit': 0.0, 'reconciled': True},
            # Receive all on the backorder (-> all based on PO price, we will not get the value of the returned one)
            {'debit': 0.0, 'credit': 100.0, 'reconciled': True},
            # Bill it
            {'debit': 100.0, 'credit': 0.0, 'reconciled': True},
        ])
        self.assertTrue(all(aml.full_reconcile_id for aml in in_stock_amls))

    def test_fifo_return_twice_and_bill(self):
        """
        FIFO auto
        Receive & Bill 1 @ 10
        Receive 1 @ 25
        Return
        Receive it again
        Bill
        """
        self._test_fifo_and_returns_common()

        po_form = Form(self.env['purchase.order'])
        po_form.partner_id = self.partner_id
        with po_form.order_line.new() as po_line:
            po_line.product_id = self.product1
            po_line.product_qty = 1
            po_line.price_unit = 25.0
        po = po_form.save()
        po.button_confirm()

        receipt01 = po.picking_ids
        receipt01.move_ids.quantity = 1
        receipt01.button_validate()

        receipt01_return = self._return(receipt01)
        self._return(receipt01_return)
        self._bill(po)

        in_stock_amls = self.env['account.move.line'].search([('account_id', '=', self.stock_input_account.id)], order='id')
        self.assertRecordValues(in_stock_amls, [
            # Receive and bill 1 @ 10
            {'debit': 0.0, 'credit': 10.0, 'reconciled': True},
            {'debit': 10.0, 'credit': 0.0, 'reconciled': True},
            # Receive 1 @ 25
            {'debit': 0.0, 'credit': 25.0, 'reconciled': True},
            # Return it (10 with valo, 15 with expense)
            {'debit': 10.0, 'credit': 0.0, 'reconciled': True},
            {'debit': 15.0, 'credit': 0.0, 'reconciled': True},
            # Receive it again
            # The "return of a return" ignores the POL price and uses the value of the returned product
            # So, same: 10 with valo, 15 with expense
            {'debit': 0.0, 'credit': 10.0, 'reconciled': True},
            {'debit': 0.0, 'credit': 15.0, 'reconciled': True},
            # Bill it
            {'debit': 25.0, 'credit': 0.0, 'reconciled': True},
        ])
        self.assertTrue(all(aml.full_reconcile_id for aml in in_stock_amls))

    def test_fifo_bill_return_refund(self):
        """
        FIFO auto
        Receive & Bill 1 @ 10
        Receive 1 @ 25
        Bill
        Return
        Refund
        """
        self._test_fifo_and_returns_common()

        po_form = Form(self.env['purchase.order'])
        po_form.partner_id = self.partner_id
        with po_form.order_line.new() as po_line:
            po_line.product_id = self.product1
            po_line.product_qty = 1
            po_line.price_unit = 25.0
        po = po_form.save()
        po.button_confirm()

        receipt01 = po.picking_ids
        receipt01.move_ids.quantity = 1
        receipt01.button_validate()

        self._bill(po)
        self._return(receipt01)
        self._bill(po)  # Refund

        in_stock_amls = self.env['account.move.line'].search([('account_id', '=', self.stock_input_account.id)], order='id')
        self.assertRecordValues(in_stock_amls, [
            # Receive and bill 1 @ 10
            {'debit': 0.0, 'credit': 10.0, 'reconciled': True},
            {'debit': 10.0, 'credit': 0.0, 'reconciled': True},
            # Receive 1 @ 25
            {'debit': 0.0, 'credit': 25.0, 'reconciled': True},
            # Bill
            {'debit': 25.0, 'credit': 0.0, 'reconciled': True},
            # Return (10 with valo, 15 with expense)
            {'debit': 10.0, 'credit': 0.0, 'reconciled': True},
            {'debit': 15.0, 'credit': 0.0, 'reconciled': True},
            # Refund
            {'debit': 0.0, 'credit': 25.0, 'reconciled': True},
        ])
        self.assertTrue(all(aml.full_reconcile_id for aml in in_stock_amls))

    def test_incoming_with_negative_qty(self):
        """
                FIFO/AVCO Auto
                Purchase one Product with negative qty
                Conform PO,
                It will create outgoing shipment
                        this transfer is neither returned nor received but it will be a delivery(outgoing).
                """
        product1 = self.product1
        self.cat.property_valuation = 'real_time'
        shipping_partner = self.env["res.partner"].create({
            'name': "Shipping Partner",
            'street': "234 W 18th Ave",
            'city': "Columbus",
            'state_id': self.env.ref("base.state_us_30").id,  # Ohio
            'country_id': self.env.ref("base.us").id,
            'zip': "43210",
        })
        po_form = Form(self.env['purchase.order'])
        po_form.partner_id = self.partner_id
        with po_form.order_line.new() as po_line:
            po_line.product_id = product1
            po_line.product_qty = -2
            po_line.price_unit = 10.0
        po = po_form.save()
        po.button_confirm()
        delivery = po.picking_ids
        # it is negative qty transfer so Odoo will create delivery instead of receipt.
        delivery.partner_id = shipping_partner
        move_line_vals = delivery.move_ids._prepare_move_line_vals()
        move_line = self.env['stock.move.line'].create(move_line_vals)
        move_line.quantity = 2.
        delivery.button_validate()
        self.assertEqual(delivery.state, 'done')

    def test_return_a_return_avco_prod_with_exchange_diff(self):
        """ When there is some return of a return, we expect `_generate_price_difference_vals` to
        assume any pdiff existing in the relevant transfers' SVL records has already been
        compensated for. This should remain true in the case where the underlying purchase order
        has some currency exchange diff.
        """
        self.product1.categ_id.property_cost_method = 'average'
        avco_prod = self.product1
        (self.env.ref('base.EUR') + self.env.ref('base.CHF')).active = True
        euro_id = self.env.ref('base.EUR').id
        franc_id = self.env.ref('base.CHF').id
        self.env['res.currency.rate'].create([
            {'currency_id': euro_id, 'rate': 0.95},
            {'currency_id': franc_id, 'rate': 0.8},
        ])
        purchase_order = self.env['purchase.order'].create({
            'partner_id': self.partner_a.id,
            'currency_id': euro_id,
            'order_line': [Command.create({
                'product_id': avco_prod.id,
                'product_uom_qty': 5,
                'price_unit': 10,
            })],
        })
        purchase_order.button_confirm()
        receipt1 = purchase_order.picking_ids
        receipt1.button_validate()

        purchase_order = self.env['purchase.order'].create({
            'partner_id': self.partner_b.id,
            'currency_id': franc_id,
            'order_line': [Command.create({
                'product_id': avco_prod.id,
                'product_uom_qty': 5,
            })],
        })
        purchase_order.button_confirm()
        receipt2 = purchase_order.picking_ids
        receipt2.button_validate()

        receipt2_return1 = self._return(receipt2)
        # return the initial return
        self._return(receipt2_return1)
        pre_bill_cost = avco_prod.standard_price
        purchase_order.action_create_invoice()
        bill = purchase_order.invoice_ids
        bill.invoice_date = fields.Date.today()
        bill.action_post()
        self.assertEqual(avco_prod.standard_price, pre_bill_cost)

    def test_manual_non_standard_cost_bill_post(self):
        """ With manual valuation (+ continental accounting), receiving some product with a
        non-standard cost method, consuming the available qty, and then invoicing that product at
        different `price_unit` than the receipt should not create pdiff AccountMoveLines.
        """
        self.env.company.anglo_saxon_accounting = False
        self.product1.categ_id.write({
            'property_valuation': 'manual_periodic',
            'property_cost_method': 'average',
        })
        product = self.product1
        purchase_order = self.env['purchase.order'].create({
            'partner_id': self.partner_a.id,
            'order_line': [Command.create({
                'product_id': product.id,
                'product_qty': 10,
                'price_unit': 100,
            })],
        })
        purchase_order.button_confirm()
        purchase_order.picking_ids.button_validate()
        with Form(self.env['stock.scrap']) as scrap_form:
            scrap_form.product_id = product
            scrap_form.scrap_qty = 10
            scrap = scrap_form.save()
        scrap.action_validate()
        purchase_order.action_create_invoice()
        bill = purchase_order.invoice_ids
        bill.invoice_line_ids.price_unit = 120
        bill.invoice_date = fields.Date.today()
        bill.action_post()
        expense_account, tax_paid_account, account_payable_account = (
            self.company_data['default_account_expense'],
            self.company_data['default_account_tax_purchase'],
            self.company_data['default_account_payable'],
        )
        self.assertRecordValues(
            bill.line_ids,
            [
                {'account_id': expense_account.id,           'debit': 1200.0,   'credit': 0.0},
                {'account_id': tax_paid_account.id,          'debit': 180.0,    'credit': 0.0},
                {'account_id': account_payable_account.id,   'debit': 0.0,      'credit': 1380.0},
            ]
        )
