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

from odoo.addons.stock_account.tests.common import TestStockValuationCommon
from odoo.exceptions import UserError
from odoo.tests import Form, tagged
from odoo import fields, Command


@tagged("post_install", "-at_install")
class TestAccountMove(TestStockValuationCommon):
    def test_standard_perpetual_01_mc_01(self):
        product = self.product_standard_auto
        self._use_multi_currencies([('2017-01-01', 2.0)])
        rate = self.other_currency.rate_ids.rate

        move_form = Form(self.env["account.move"].with_context(default_move_type="out_invoice"))
        move_form.partner_id = self.partner
        move_form.currency_id = self.other_currency
        with move_form.invoice_line_ids.new() as line_form:
            line_form.product_id = product
            line_form.tax_ids.clear()
        invoice = move_form.save()

        self.assertAlmostEqual(product.lst_price * rate, invoice.amount_total)
        self.assertEqual(len(invoice.mapped("line_ids")), 2)
        self.assertEqual(len(invoice.mapped("line_ids.currency_id")), 1)

        invoice._post()

        self.assertAlmostEqual(product.lst_price * rate, invoice.amount_total)
        self.assertAlmostEqual(product.lst_price * rate, invoice.amount_residual)
        self.assertEqual(len(invoice.mapped("line_ids")), 4)
        self.assertEqual(len(invoice.mapped("line_ids").filtered(lambda l: l.display_type == 'cogs')), 2)
        self.assertEqual(len(invoice.mapped("line_ids.currency_id")), 2)

    def test_fifo_perpetual_01_mc_01(self):
        product = self.product_fifo_auto
        self._use_multi_currencies([('2017-01-01', 2.0)])
        rate = self.other_currency.rate_ids.rate

        move_form = Form(self.env["account.move"].with_context(default_move_type="out_invoice"))
        move_form.partner_id = self.partner
        move_form.currency_id = self.other_currency
        with move_form.invoice_line_ids.new() as line_form:
            line_form.product_id = product
            line_form.tax_ids.clear()
        invoice = move_form.save()

        self.assertAlmostEqual(product.lst_price * rate, invoice.amount_total)
        self.assertEqual(len(invoice.mapped("line_ids")), 2)
        self.assertEqual(len(invoice.mapped("line_ids.currency_id")), 1)

        invoice._post()
        self.assertAlmostEqual(product.lst_price * rate, invoice.amount_total)
        self.assertAlmostEqual(product.lst_price * rate, invoice.amount_residual)
        self.assertEqual(len(invoice.mapped("line_ids")), 4)
        self.assertEqual(len(invoice.mapped("line_ids").filtered(lambda l: l.display_type == 'cogs')), 2)
        self.assertEqual(len(invoice.mapped("line_ids.currency_id")), 2)

    def test_average_perpetual_01_mc_01(self):
        product = self.product_avco_auto
        self._use_multi_currencies([('2017-01-01', 2.0)])
        rate = self.other_currency.rate_ids.rate

        move_form = Form(self.env["account.move"].with_context(default_move_type="out_invoice"))
        move_form.partner_id = self.partner
        move_form.currency_id = self.other_currency
        with move_form.invoice_line_ids.new() as line_form:
            line_form.product_id = product
            line_form.tax_ids.clear()
        invoice = move_form.save()

        self.assertAlmostEqual(product.lst_price * rate, invoice.amount_total)
        self.assertEqual(len(invoice.mapped("line_ids")), 2)
        self.assertEqual(len(invoice.mapped("line_ids.currency_id")), 1)

        invoice._post()

        self.assertAlmostEqual(product.lst_price * rate, invoice.amount_total)
        self.assertAlmostEqual(product.lst_price * rate, invoice.amount_residual)
        self.assertEqual(len(invoice.mapped("line_ids")), 4)
        self.assertEqual(len(invoice.mapped("line_ids").filtered(lambda l: l.display_type == 'cogs')), 2)
        self.assertEqual(len(invoice.mapped("line_ids.currency_id")), 2)

    def test_storno_accounting(self):
        """Storno accounting uses negative numbers on debit/credit to cancel other moves.
        This test checks that we do the same for the anglosaxon lines when storno is enabled.
        """
        self._use_multi_currencies([('2017-01-01', 2.0)])

        product = self.product_standard_auto
        self.env.company.account_storno = True
        self.env.company.anglo_saxon_accounting = True

        move = self.env['account.move'].create({
            'move_type': 'out_refund',
            'invoice_date': fields.Date.from_string('2019-01-01'),
            'partner_id': self.partner.id,
            'currency_id': self.other_currency.id,
            'invoice_line_ids': [
                (0, None, {'product_id': product.id}),
            ]
        })
        move.action_post()

        stock_output_line = move.line_ids.filtered(lambda l: l.account_id == self.account_stock_valuation)
        self.assertEqual(stock_output_line.debit, 0)
        self.assertEqual(stock_output_line.credit, -10)

        expense_line = move.line_ids.filtered(lambda l: l.account_id == product._get_product_accounts()['expense'])
        self.assertEqual(expense_line.debit, -10)
        self.assertEqual(expense_line.credit, 0)

    def test_standard_manual_tax_edit(self):
        ''' Test manually editing tax amount, cogs creation should not reset tax amount '''
        product = self.product_standard_auto
        product.lst_price = 100
        move_form = Form(self.env["account.move"].with_context(default_move_type="out_invoice"))
        move_form.partner_id = self.partner
        self.company.income_account_id.write({
            'tax_ids': [(6, 0, [self.env.company.account_sale_tax_id.id])]
        })
        with move_form.invoice_line_ids.new() as line_form:
            line_form.product_id = product
        invoice = move_form.save()

        self.assertEqual(invoice.amount_total, 115)
        self.assertEqual(invoice.amount_untaxed, 100)
        self.assertEqual(invoice.amount_tax, 15)

        # simulate manual tax edit via widget
        tax_totals = invoice.tax_totals
        tax_totals['subtotals'][0]['tax_groups'][0]['tax_amount_currency'] = 14.0
        invoice.tax_totals = tax_totals

        self.assertEqual(len(invoice.mapped("line_ids")), 3)
        self.assertEqual(invoice.amount_total, 114)
        self.assertEqual(invoice.amount_untaxed, 100)
        self.assertEqual(invoice.amount_tax, 14)

        invoice._post()

        self.assertEqual(len(invoice.mapped("line_ids")), 5)
        self.assertEqual(invoice.amount_total, 114)
        self.assertEqual(invoice.amount_untaxed, 100)
        self.assertEqual(invoice.amount_tax, 14)

    def test_basic_bill(self):
        """
        When billing a storable product with a basic category (manual
        valuation), the account used should be the expenses one. This test
        checks the flow with two companies:
        - One that existed before the installation of `stock_account` (to test
        the post-install hook)
        - One created after the module installation
        """
        self.env.user.company_ids |= self.other_company

        for company in (self.company | self.other_company):
            bill_form = Form(self.env['account.move'].with_company(company.id).with_context(default_move_type='in_invoice'))
            bill_form.partner_id = self.partner
            bill_form.invoice_date = fields.Date.today()
            with bill_form.invoice_line_ids.new() as line:
                line.product_id = self.product_standard
                line.price_unit = 100
            bill = bill_form.save()
            bill.action_post()

            product_accounts = self.product_standard.product_tmpl_id.with_company(company.id).get_product_accounts()
            self.assertEqual(bill.invoice_line_ids.account_id, product_accounts['expense'])

    def test_cogs_analytic_accounting(self):
        """Check analytic distribution is correctly propagated to COGS lines"""
        product = self.product_standard_auto
        default_plan = self.env['account.analytic.plan'].create({
            'name': 'Default',
        })
        analytic_account = self.env['account.analytic.account'].create({
            'name': 'Account 1',
            'plan_id': default_plan.id,
            'company_id': False,
        })

        move = self.env['account.move'].create({
            'move_type': 'out_refund',
            'invoice_date': fields.Date.from_string('2019-01-01'),
            'partner_id': self.partner.id,
            'invoice_line_ids': [
                Command.create({
                    'product_id': product.id,
                    'analytic_distribution': {
                        analytic_account.id: 100,
                    },
                }),
            ]
        })
        move.action_post()

        cogs_line = move.line_ids.filtered(lambda l: l.account_id == product._get_product_accounts()['expense'])
        self.assertEqual(cogs_line.analytic_distribution, {str(analytic_account.id): 100})

    def test_cogs_account_branch_company(self):
        """Check branch company accounts are selected"""
        product = self.product_standard_auto
        branch = self.branch
        test_account = self.env['account.account'].with_company(branch.id).create({
            'name': 'STCK Test Account',
            'code': '100119',
            'reconcile': True,
            'account_type': 'asset_current',
        })
        self.category_standard_auto.with_company(branch.id).property_valuation = "real_time"
        self.category_standard_auto.with_company(branch.id).property_stock_valuation_account_id = test_account

        bill = self.env['account.move'].with_company(branch.id).with_context(default_move_type='in_invoice').create({
            'partner_id': self.partner.id,
            'invoice_date': fields.Date.today(),
            'company_id': branch.id,
            'invoice_line_ids': [
                Command.create({
                    'product_id': product.id,
                    'price_unit': 100,
                }),
            ],
        })
        self.assertEqual(bill.invoice_line_ids.account_id, test_account)

    def test_apply_inventory_adjustment_on_multiple_quants_simultaneously(self):
        product = self.product_standard_auto
        product_b = product.copy()
        products = product + product_b

        self._use_inventory_location_accounting()

        self.env['stock.quant']._update_available_quantity(product, self.stock_location, 5)
        self.env['stock.quant']._update_available_quantity(product_b, self.stock_location, 15)

        quants = products.stock_quant_ids
        quants.inventory_quantity = 10.0
        wizard = self.env['stock.inventory.adjustment.name'].create({'quant_ids': quants})
        wizard.action_apply()
        inv_adjustment_journal_items = self.env['account.move.line'].search([('product_id', 'in', products.ids)], order='id asc', limit=4)
        prod_a_accounts = product.product_tmpl_id.get_product_accounts()
        prod_b_accounts = product_b.product_tmpl_id.get_product_accounts()
        self.assertRecordValues(
            inv_adjustment_journal_items,
            [
                {'account_id': self.account_inventory.id, 'product_id': product.id},
                {'account_id': prod_a_accounts['stock_valuation'].id, 'product_id': product.id},
                {'account_id': prod_b_accounts['stock_valuation'].id, 'product_id': product_b.id},
                {'account_id': self.account_inventory.id, 'product_id': product_b.id},
            ]
        )

    @freeze_time("2020-01-22")
    def test_backdate_picking_with_lock_date(self):
        """
        Check that pickings can not be backdate or validated prior to the
        fiscal and hard lock date.
        """
        self.env['account.lock_exception'].search([]).sudo().unlink()
        lock_date = fields.Date.from_string('2011-01-01')
        prior_to_lock_date = fields.Datetime.add(lock_date, days=-1)
        post_to_lock_date = fields.Datetime.add(lock_date, days=+1)
        self.env['stock.quant']._update_available_quantity(self.product_standard, self.stock_location, 10)
        receipts = receipt, receipt_done = self.env['stock.picking'].create([
            {
                'location_id': self.supplier_location.id,
                'location_dest_id': self.stock_location.id,
                'picking_type_id': self.picking_type_in.id,
                'owner_id': self.env.company.partner_id.id,
                'move_ids': [Command.create({
                    'product_id': self.product_standard.id,
                    'location_id': self.supplier_location.id,
                    'location_dest_id': self.stock_location.id,
                    'product_uom_qty': 1.0,
                })]
            } for _ in range(2)
        ])
        receipts.action_confirm()
        receipt_done.button_validate()
        # Check that the purchase, sale and tax lock dates do not impose any restrictions
        self.env.company.write({
            'sale_lock_date': lock_date,
            'purchase_lock_date': lock_date,
            'tax_lock_date': lock_date,
        })
        # Receipts can be backdated
        receipt.scheduled_date = prior_to_lock_date
        receipt_done.date_done = prior_to_lock_date

        # Check that the fiscal year lock date imposes restrictions
        self.env.company.write({
            'sale_lock_date': False,
            'purchase_lock_date': False,
            'tax_lock_date': False,
            'fiscalyear_lock_date': lock_date,
        })
        # Receipts can not be backdated prior to lock date
        receipt.scheduled_date = post_to_lock_date
        receipt_done.date_done = post_to_lock_date
        # Lock dates should not affect un-validated scheduled_date
        receipt.scheduled_date = prior_to_lock_date
        with self.assertRaises(UserError):
            receipt_done.date_done = prior_to_lock_date

        # Check that the hard lock date imposes restrictions
        self.env.company.write({
            'fiscalyear_lock_date': False,
            'hard_lock_date': lock_date,
        })
        # Receipts can not be backdated prior to lock date
        receipt.scheduled_date = post_to_lock_date
        receipt_done.date_done = post_to_lock_date
        # Lock dates should not affect un-validated scheduled_date
        receipt.scheduled_date = prior_to_lock_date
        with self.assertRaises(UserError):
            receipt_done.date_done = prior_to_lock_date

    def test_invoice_with_journal_item_without_label(self):
        """Test posting an invoice whose invoice lines have no label.
        The 'name' field is optional on account.move.line and should be
        handled safely when generating accounting entries.
        """
        move = self.env['account.move'].create({
            'move_type': 'out_invoice',
            'partner_id': self.partner.id,
            'invoice_line_ids': [
                Command.create({
                    'product_id': self.product_standard.id,
                    'name': False,
                }),
            ],
        })
        move.action_post()
        # name should remain falsy on the invoice line
        self.assertFalse(move.invoice_line_ids.name)
        # ensure the invoice is posted successfully
        self.assertEqual(move.state, 'posted')
