# -*- coding: utf-8 -*-
# pylint: disable=C0326
import datetime

from odoo import Command, fields
from odoo.tests import tagged
from odoo.addons.account.tests.common import AccountTestInvoicingCommon

from freezegun import freeze_time


@tagged('post_install', '-at_install')
class TestDeferredManagement(AccountTestInvoicingCommon):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.expense_accounts = [cls.env['account.account'].create({
            'name': f'Expense {i}',
            'code': f'EXP{i}',
            'account_type': 'expense',
        }) for i in range(3)]
        cls.revenue_accounts = [cls.env['account.account'].create({
            'name': f'Revenue {i}',
            'code': f'REV{i}',
            'account_type': 'income',
        }) for i in range(3)]

        cls.company.deferred_expense_journal_id = cls.company_data['default_journal_misc'].id
        cls.company.deferred_revenue_journal_id = cls.company_data['default_journal_misc'].id
        cls.company.deferred_expense_account_id = cls.company_data['default_account_deferred_expense'].id
        cls.company.deferred_revenue_account_id = cls.company_data['default_account_deferred_revenue'].id

        cls.expense_lines = [
            [cls.expense_accounts[0], 1000, '2023-01-01', '2023-04-30'],  # 4 full months (=250/month)
            [cls.expense_accounts[0], 1050, '2023-01-16', '2023-04-30'],  # 3 full months + 15 days (=300/month)
            [cls.expense_accounts[1], 1225, '2023-01-01', '2023-04-15'],  # 3 full months + 15 days (=350/month)
            [cls.expense_accounts[2], 1680, '2023-01-21', '2023-04-14'],  # 2 full months + 10 days + 14 days (=600/month)
            [cls.expense_accounts[2],  225, '2023-04-01', '2023-04-15'],  # 15 days (=450/month)
        ]
        cls.revenue_lines = [
            [cls.revenue_accounts[0], 1000, '2023-01-01', '2023-04-30'],  # 4 full months (=250/month)
            [cls.revenue_accounts[0], 1050, '2023-01-16', '2023-04-30'],  # 3 full months + 15 days (=300/month)
            [cls.revenue_accounts[1], 1225, '2023-01-01', '2023-04-15'],  # 3 full months + 15 days (=350/month)
            [cls.revenue_accounts[2], 1680, '2023-01-21', '2023-04-14'],  # 2 full months + 10 days + 14 days (=600/month)
            [cls.revenue_accounts[2],  225, '2023-04-01', '2023-04-15'],  # 15 days (=450/month)
        ]

    def create_invoice(self, move_type, invoice_lines, date=None, post=True):
        journal = self.company_data['default_journal_purchase'] if move_type == 'in_invoice' else self.company_data['default_journal_sale']
        move = self.env['account.move'].create({
            'move_type': move_type,
            'partner_id': self.partner_a.id,
            'date': date or '2023-01-01',
            'invoice_date': date or '2023-01-01',
            'journal_id': journal.id,
            'invoice_line_ids': [
                Command.create({
                    'product_id': self.product_a.id,
                    'quantity': 1,
                    'account_id': account.id,
                    'price_unit': price_unit,
                    'deferred_start_date': start_date,
                    'deferred_end_date': end_date,
                }) for account, price_unit, start_date, end_date in invoice_lines
            ]
        })
        if post:
            move.action_post()
        return move

    def test_deferred_management_get_diff_dates(self):
        def assert_get_diff_dates(start, end, expected):
            diff = self.env['account.move']._get_deferred_diff_dates(fields.Date.to_date(start), fields.Date.to_date(end))
            self.assertAlmostEqual(diff, expected, 3)

        assert_get_diff_dates('2023-01-01', '2023-01-01', 0)
        assert_get_diff_dates('2023-01-01', '2023-01-02', 1/30)
        assert_get_diff_dates('2023-01-01', '2023-01-20', 19/30)
        assert_get_diff_dates('2023-01-01', '2023-01-31', 29/30)
        assert_get_diff_dates('2023-01-01', '2023-01-30', 29/30)
        assert_get_diff_dates('2023-01-01', '2023-02-01', 1)
        assert_get_diff_dates('2023-01-01', '2023-02-28', 1 + 29/30)
        assert_get_diff_dates('2023-02-01', '2023-02-28', 29/30)
        assert_get_diff_dates('2023-02-10', '2023-02-28', 20/30)
        assert_get_diff_dates('2023-01-01', '2023-02-15', 1 + 14/30)
        assert_get_diff_dates('2023-01-01', '2023-03-31', 2 + 29/30)
        assert_get_diff_dates('2023-01-01', '2023-04-01', 3)
        assert_get_diff_dates('2023-01-01', '2023-04-30', 3 + 29/30)
        assert_get_diff_dates('2023-01-10', '2023-04-30', 3 + 20/30)
        assert_get_diff_dates('2023-01-10', '2023-04-09', 2 + 29/30)
        assert_get_diff_dates('2023-01-10', '2023-04-10', 3)
        assert_get_diff_dates('2023-01-10', '2023-04-11', 3 + 1/30)
        assert_get_diff_dates('2023-02-20', '2023-04-10', 1 + 20/30)
        assert_get_diff_dates('2023-01-31', '2023-04-30', 3)
        assert_get_diff_dates('2023-02-28', '2023-04-10', 1 + 10/30)
        assert_get_diff_dates('2023-03-01', '2023-04-10', 1 + 9/30)
        assert_get_diff_dates('2023-04-10', '2023-03-01', 1 + 9/30)
        assert_get_diff_dates('2023-01-01', '2023-12-31', 11 + 29/30)
        assert_get_diff_dates('2023-01-01', '2024-01-01', 12)
        assert_get_diff_dates('2023-01-01', '2024-07-01', 18)
        assert_get_diff_dates('2023-01-01', '2024-07-10', 18 + 9/30)

    def test_get_ends_of_month(self):
        def assertEndsOfMonths(start_date, end_date, expected):
            self.assertEqual(
                self.env['account.move.line']._get_deferred_ends_of_month(
                    fields.Date.to_date(start_date),
                    fields.Date.to_date(end_date)
                ),
                [fields.Date.to_date(date) for date in expected]
            )

        assertEndsOfMonths('2023-01-01', '2023-01-01', ['2023-01-31'])
        assertEndsOfMonths('2023-01-01', '2023-01-02', ['2023-01-31'])
        assertEndsOfMonths('2023-01-01', '2023-01-20', ['2023-01-31'])
        assertEndsOfMonths('2023-01-01', '2023-01-30', ['2023-01-31'])
        assertEndsOfMonths('2023-01-01', '2023-01-31', ['2023-01-31'])
        assertEndsOfMonths('2023-01-01', '2023-02-01', ['2023-01-31', '2023-02-28'])
        assertEndsOfMonths('2023-01-01', '2023-02-28', ['2023-01-31', '2023-02-28'])
        assertEndsOfMonths('2023-02-01', '2023-02-28', ['2023-02-28'])
        assertEndsOfMonths('2023-02-10', '2023-02-28', ['2023-02-28'])
        assertEndsOfMonths('2023-01-01', '2023-02-15', ['2023-01-31', '2023-02-28'])
        assertEndsOfMonths('2023-01-01', '2023-03-31', ['2023-01-31', '2023-02-28', '2023-03-31'])
        assertEndsOfMonths('2023-01-01', '2023-04-01', ['2023-01-31', '2023-02-28', '2023-03-31', '2023-04-30'])
        assertEndsOfMonths('2023-01-01', '2023-04-30', ['2023-01-31', '2023-02-28', '2023-03-31', '2023-04-30'])
        assertEndsOfMonths('2023-01-10', '2023-04-30', ['2023-01-31', '2023-02-28', '2023-03-31', '2023-04-30'])
        assertEndsOfMonths('2023-01-10', '2023-04-09', ['2023-01-31', '2023-02-28', '2023-03-31', '2023-04-30'])

    def test_deferred_abnormal_dates(self):
        """
        Test that we correctly detect abnormal dates.
        In the deferred computations, we always assume that both the start and end date are inclusive
        E.g: 1st January -> 31st December is *exactly* 1 year = 12 months
        However, the user may instead put 1st January -> 1st January of next year which is then
        12 months + 1/30 month = 12.03 months which may result in odd amounts when deferrals are created.
        This is what we call abnormal dates.
        Other cases were the number of months is not round should not be handled and are not considered abnormal.
        """
        move = self.create_invoice('in_invoice', [
            [self.expense_accounts[0], 0, '2023-01-01', '2023-12-30'],
            [self.expense_accounts[0], 1, '2023-01-01', '2023-12-31'],
            [self.expense_accounts[0], 2, '2023-01-01', '2024-01-01'],
            [self.expense_accounts[0], 3, '2023-01-01', '2024-01-02'],
            [self.expense_accounts[0], 4, '2023-01-01', '2024-01-31'],
            [self.expense_accounts[0], 5, '2023-01-01', '2024-02-01'],
            [self.expense_accounts[0], 6, '2023-01-02', '2024-02-01'],
            [self.expense_accounts[0], 7, '2023-01-02', '2024-02-02'],
            [self.expense_accounts[0], 8, '2023-01-31', '2024-01-30'],
            [self.expense_accounts[0], 9, '2023-01-31', '2024-02-28'],  # 29 days in Feb 2024
            # Following one is abnormal because we have a full months in February (= 30 accounting days) + 1 day in January
            [self.expense_accounts[0], 10, '2023-01-31', '2024-02-29'],
            [self.expense_accounts[0], 11, '2023-02-01', '2024-02-29'],
        ], post=True)
        lines = move.invoice_line_ids.sorted('price_unit')
        self.assertFalse(lines[0].has_abnormal_deferred_dates)
        self.assertFalse(lines[1].has_abnormal_deferred_dates)
        self.assertTrue(lines[2].has_abnormal_deferred_dates)
        self.assertFalse(lines[3].has_abnormal_deferred_dates)
        self.assertFalse(lines[4].has_abnormal_deferred_dates)
        self.assertTrue(lines[5].has_abnormal_deferred_dates)
        self.assertFalse(lines[6].has_abnormal_deferred_dates)
        self.assertTrue(lines[7].has_abnormal_deferred_dates)
        self.assertFalse(lines[8].has_abnormal_deferred_dates)
        self.assertFalse(lines[9].has_abnormal_deferred_dates)
        self.assertTrue(lines[10].has_abnormal_deferred_dates)
        self.assertFalse(lines[11].has_abnormal_deferred_dates)

    def test_deferred_expense_generate_entries_method(self):
        # The deferred entries are NOT generated when the invoice is validated if the method is set to 'manual'.
        self.company.generate_deferred_expense_entries_method = 'manual'
        move2 = self.create_invoice('in_invoice', [self.expense_lines[0]], post=True)
        self.assertEqual(len(move2.deferred_move_ids), 0)

        # Test that the deferred entries are generated when the invoice is validated.
        self.company.generate_deferred_expense_entries_method = 'on_validation'
        move = self.create_invoice('in_invoice', [self.expense_lines[0]], post=True)
        self.assertEqual(len(move.deferred_move_ids), 5)  # 1 for the invoice deferred + 4 for the deferred entries
        # See test_deferred_expense_credit_note for the values

    def test_deferred_expense_reset_to_draft(self):
        """
        Test that the deferred entries are deleted/reverted when the invoice is reset to draft.
        """
        move = self.create_invoice('in_invoice', [(self.expense_accounts[0], 1680, '2023-01-21', '2023-04-14')], date='2023-03-15')
        self.assertEqual(len(move.deferred_move_ids), 5)
        move.button_draft()
        self.assertFalse(move.deferred_move_ids)

        # With a lock date, we should reverse the moves that cannot be deleted
        move.action_post()  # Post the move to create the deferred entries with 'on_validation' method
        self.assertEqual(len(move.deferred_move_ids), 5)
        move.company_id.fiscalyear_lock_date = fields.Date.to_date('2023-02-15')
        move.button_draft()
        # January deferred entry is in lock period, so it is reversed, not deleted, thus we have one deferred entry and its revert
        self.assertEqual(len(move.deferred_move_ids), 2)
        self.assertEqual(move.deferred_move_ids[0].date, fields.Date.to_date('2023-02-28'))
        self.assertEqual(move.deferred_move_ids[1].date, fields.Date.to_date('2023-01-31'))

        # If we repost the move, it should be allowed
        move.action_post()
        self.assertEqual(len(move.deferred_move_ids), 2 + 5)

    def assert_invoice_lines(self, move, expected_values, source_account, deferred_account):
        deferred_moves = move.deferred_move_ids.sorted('date')
        for deferred_move, expected_value in zip(deferred_moves, expected_values):
            expected_date, expense_line_debit, expense_line_credit, deferred_line_debit, deferred_line_credit = expected_value
            self.assertRecordValues(deferred_move, [{
                'state': 'posted',
                'move_type': 'entry',
                'partner_id': self.partner_a.id,
                'date': fields.Date.to_date(expected_date),
            }])
            expense_line = deferred_move.line_ids.filtered(lambda line: line.account_id == source_account)
            self.assertRecordValues(expense_line, [
                {'debit': expense_line_debit, 'credit': expense_line_credit, 'partner_id': self.partner_a.id},
            ])
            deferred_line = deferred_move.line_ids.filtered(lambda line: line.account_id == deferred_account)
            self.assertEqual(deferred_line.debit, deferred_line_debit)
            self.assertEqual(deferred_line.credit, deferred_line_credit)

    def test_default_tax_on_account_not_on_deferred_entries(self):
        """
        Test that the default taxes on an account are not calculated on deferral entries, since this would impact the
        tax report.
        """
        revenue_account_with_taxes = self.env['account.account'].create({
            'name': 'Revenue with Taxes',
            'code': 'REVWTAXES',
            'account_type': 'income',
            'tax_ids': [Command.set(self.tax_sale_a.ids)]
        })

        move = self.create_invoice(
            'out_invoice',
            [[revenue_account_with_taxes, 1000, '2023-01-01', '2023-04-30']],
            date='2022-12-10'
        )

        expected_line_values = [
            # Date         [Line expense] [Line deferred]
            ('2022-12-10',  1000,      0,      0,   1000),
            ('2023-01-31',     0,    250,    250,      0),
            ('2023-02-28',     0,    250,    250,      0),
            ('2023-03-31',     0,    250,    250,      0),
        ]

        self.assert_invoice_lines(
            move,
            expected_line_values,
            revenue_account_with_taxes,
            self.company_data['default_account_deferred_revenue']
        )

        for deferred_move in move.deferred_move_ids:
            # There are no extra lines besides the two lines we checked before
            self.assertEqual(len(deferred_move.line_ids), 2)


    def test_deferred_values(self):
        """
        Test that the debit/credit values are correctly computed, even after a credit note is issued.
        """

        expected_line_values1 = [
            # Date         [Line expense] [Line deferred]
            ('2022-12-10',     0,   1000,    1000,     0),
            ('2023-01-31',   250,      0,       0,   250),
            ('2023-02-28',   250,      0,       0,   250),
            ('2023-03-31',   250,      0,       0,   250),
        ]
        expected_line_values2 = [
            # Date         [Line expense] [Line deferred]
            ('2022-12-10',  1000,      0,      0,   1000),
            ('2023-01-31',     0,    250,    250,      0),
            ('2023-02-28',     0,    250,    250,      0),
            ('2023-03-31',     0,    250,    250,      0),
        ]

        # Vendor bill and credit note
        move = self.create_invoice('in_invoice', [self.expense_lines[0]], post=True, date='2022-12-10')
        self.assert_invoice_lines(move, expected_line_values1, self.expense_accounts[0], self.company_data['default_account_deferred_expense'])
        reverse_move = move._reverse_moves()
        self.assert_invoice_lines(reverse_move, expected_line_values2, self.expense_accounts[0], self.company_data['default_account_deferred_expense'])

        # Customer invoice and credit note
        move2 = self.create_invoice('out_invoice', [self.revenue_lines[0]], post=True, date='2022-12-10')
        self.assert_invoice_lines(move2, expected_line_values2, self.revenue_accounts[0], self.company_data['default_account_deferred_revenue'])
        reverse_move2 = move2._reverse_moves()
        self.assert_invoice_lines(reverse_move2, expected_line_values1, self.revenue_accounts[0], self.company_data['default_account_deferred_revenue'])

    def test_deferred_values_rounding(self):
        """
        Test that the debit/credit values are correctly computed when values are rounded
        """

        # Vendor Bill
        expense_line = [self.expense_accounts[0], 500, '2020-08-07', '2020-12-07']
        expected_line_values = [
            # Date         [Line expense] [Line deferred]
            ('2020-08-07',      0, 500,   500,      0),
            ('2020-08-31',  99.17,   0,     0,  99.17),
            ('2020-09-30', 123.97,   0,     0, 123.97),
            ('2020-10-31', 123.97,   0,     0, 123.97),
            ('2020-11-30', 123.97,   0,     0, 123.97),
            ('2020-12-07',  28.92,   0,     0,  28.92),
        ]
        self.assertEqual(self.company.currency_id.round(sum(x[1] for x in expected_line_values)), 500)
        move = self.create_invoice('in_invoice', [expense_line], date='2020-08-07')
        self.assert_invoice_lines(move, expected_line_values, self.expense_accounts[0], self.company_data['default_account_deferred_expense'])

        # Customer invoice
        revenue_line = [self.revenue_accounts[0], 500, '2020-08-07', '2020-12-07']
        expected_line_values = [
            # Date         [Line expense] [Line deferred]
            ('2020-08-07', 500,      0,      0,       500),
            ('2020-08-31',   0,  99.17,  99.17,         0),
            ('2020-09-30',   0, 123.97, 123.97,         0),
            ('2020-10-31',   0, 123.97, 123.97,         0),
            ('2020-11-30',   0, 123.97, 123.97,         0),
            ('2020-12-07',   0,  28.92,  28.92,         0),
        ]
        self.assertEqual(self.company.currency_id.round(sum(x[2] for x in expected_line_values)), 500)
        move = self.create_invoice('out_invoice', [revenue_line], post=True, date='2020-08-07')
        self.assert_invoice_lines(move, expected_line_values, self.revenue_accounts[0], self.company_data['default_account_deferred_revenue'])

    def test_deferred_expense_avoid_useless_deferred_entries(self):
        """
        If we have an invoice with a start date in the beginning of the month, and an end date in the end of the month,
        we should not create the deferred entries because the original invoice will be totally deferred
        on the last day of the month, but the full amount will be accounted for on the same day too, thus
        cancelling each other. Therefore we should not create the deferred entries.
        """
        move = self.create_invoice('in_invoice', [(self.expense_accounts[0], 1680, '2023-01-01', '2023-01-31')], date='2023-01-01')
        self.assertEqual(len(move.deferred_move_ids), 0)

    def test_deferred_expense_single_period_entries(self):
        """
        If we have an invoice covering only one period, we should only avoid creating deferral entries when the
        accounting date is the same as the period for the deferral. Otherwise we should still generate a deferral entry.
        """
        self.company.deferred_expense_amount_computation_method = 'month'
        move = self.create_invoice('in_invoice', [(self.expense_accounts[0], 1680, '2023-02-01', '2023-02-28')])
        self.assertRecordValues(move.deferred_move_ids, [
            {'date': fields.Date.to_date('2023-01-01')},
            {'date': fields.Date.to_date('2023-02-28')},
        ])

    def test_taxes_deferred_after_date_added(self):
        """
        Test that applicable taxes get deferred also when the dates of the base line are filled in after a first save.
        """

        expected_line_values = [
            # Date         [Line expense] [Line deferred]
            ('2022-12-10',     0,   1000,    1000,     0),
            ('2022-12-10',     0,    100,     100,     0),
            ('2023-01-31',   250,      0,       0,   250),
            ('2023-01-31',    25,      0,       0,    25),
            ('2023-02-28',   250,      0,       0,   250),
            ('2023-02-28',    25,      0,       0,    25),
            ('2023-03-31',   250,      0,       0,   250),
            ('2023-03-31',    25,      0,       0,    25),
        ]

        partially_deductible_tax = self.env['account.tax'].create({
            'name': 'Partially deductible Tax',
            'amount': 20,
            'amount_type': 'percent',
            'type_tax_use': 'purchase',
            'invoice_repartition_line_ids': [
                Command.create({'repartition_type': 'base'}),
                Command.create({
                    'factor_percent': 50,
                    'repartition_type': 'tax',
                    'use_in_tax_closing': False
                }),
                Command.create({
                    'factor_percent': 50,
                    'repartition_type': 'tax',
                    'account_id': self.company_data['default_account_tax_purchase'].id,
                    'use_in_tax_closing': True
                }),
            ],
            'refund_repartition_line_ids': [
                Command.create({'repartition_type': 'base'}),
                Command.create({
                    'factor_percent': 50,
                    'repartition_type': 'tax',
                    'use_in_tax_closing': False
                }),
                Command.create({
                    'factor_percent': 50,
                    'repartition_type': 'tax',
                    'account_id': self.company_data['default_account_tax_purchase'].id,
                    'use_in_tax_closing': True
                }),
            ],
        })

        move = self.env['account.move'].create({
            'move_type': 'in_invoice',
            'partner_id': self.partner_a.id,
            'date': '2022-12-10',
            'invoice_date': '2022-12-10',
            'journal_id': self.company_data['default_journal_purchase'].id,
            'invoice_line_ids': [
                Command.create({
                    'quantity': 1,
                    'account_id': self.expense_lines[0][0].id,
                    'price_unit': self.expense_lines[0][1],
                    'tax_ids': [Command.set(partially_deductible_tax.ids)],
                })
            ]
        })

        move.invoice_line_ids.write({
            'deferred_start_date': self.expense_lines[0][2],
            'deferred_end_date': self.expense_lines[0][3],
        })

        move.action_post()

        self.assert_invoice_lines(move, expected_line_values, self.expense_accounts[0], self.company_data['default_account_deferred_expense'])

    def test_deferred_tax_key(self):
        """
        Test that the deferred tax key is correctly computed.
        and is the same between _compute_tax_key and _compute_all_tax
        """
        lines = [
            [self.expense_accounts[0], 1000, '2023-01-01', '2023-04-30'],
            [self.expense_accounts[0], 1000, False, False],
        ]
        move = self.create_invoice('in_invoice', lines, post=True)
        original_amount_total = move.amount_total
        self.assertEqual(len(move.line_ids.filtered(lambda l: l.display_type == 'tax')), 1)
        move.button_draft()
        move.action_post()
        # The number of tax lines shouldn't change, nor the total amount
        self.assertEqual(len(move.line_ids.filtered(lambda l: l.display_type == 'tax')), 1)
        self.assertEqual(move.amount_total, original_amount_total)

    def test_compute_empty_start_date(self):
        """
        Test that the deferred start date is computed when empty and posting the move.
        """
        lines = [[self.expense_accounts[0], 1000, False, '2023-04-30']]
        move = self.create_invoice('in_invoice', lines, post=False)

        # We don't have a deferred date in the beginning
        self.assertFalse(move.line_ids[0].deferred_start_date)

        move.action_post()
        # Deferred start date is set after post
        self.assertEqual(move.line_ids[0].deferred_start_date, datetime.date(2023, 1, 1))

        move.button_draft()
        move.line_ids[0].deferred_start_date = False
        move.invoice_date = '2023-02-01'
        # Start date is set when changing invoice date
        self.assertEqual(move.line_ids[0].deferred_start_date, datetime.date(2023, 2, 1))

        move.line_ids[0].deferred_start_date = False
        move.line_ids[0].deferred_end_date = '2023-05-31'
        # Start date is set when changing deferred end date
        self.assertEqual(move.line_ids[0].deferred_start_date, datetime.date(2023, 2, 1))

    def test_deferred_on_accounting_date(self):
        """
        When we are in `on_validation` mode, the deferral of the total amount should happen on the
        accounting date of the move.
        """
        move = self.create_invoice(
            'in_invoice',
            [(self.expense_accounts[0], 1680, '2023-01-01', '2023-02-28')],
            date='2023-01-10',
            post=False
        )
        move.date = '2023-01-15'
        move.action_post()
        self.assertRecordValues(move.deferred_move_ids, [
            {'date': fields.Date.to_date('2023-01-15')},
            {'date': fields.Date.to_date('2023-01-31')},
            {'date': fields.Date.to_date('2023-02-28')},
        ])

    def test_deferred_entries_not_created_on_future_invoice(self):
        """Test that we don't create deferred entries on a future posted invoice"""
        tomorrow = fields.Date.to_date(fields.Date.today()) + datetime.timedelta(days=1)
        move = self.create_invoice(
            'out_invoice',
            [(self.expense_accounts[0], 1680, tomorrow, tomorrow + datetime.timedelta(days=100))],
            date=tomorrow,
            post=False
        )
        move.auto_post = "at_date"
        move._post()
        self.assertFalse(move.deferred_move_ids)

        with freeze_time(tomorrow):
            self.env.ref('account.ir_cron_auto_post_draft_entry').method_direct_trigger()
            self.assertEqual(move.state, 'posted')
            self.assertTrue(move.deferred_move_ids)

    def test_deferred_entries_created_on_auto_post_invoice(self):
        """Test that deferred entries are created on an invoice with auto_post set to 'at_date'"""
        yesterday = fields.Date.to_date(fields.Date.today()) - datetime.timedelta(days=1)
        move = self.create_invoice(
            'out_invoice',
            [(self.expense_accounts[0], 1680, yesterday, yesterday + datetime.timedelta(days=45))],
            date=yesterday,
            post=False
        )
        move.auto_post = "at_date"
        move._post()
        self.assertEqual(move.state, 'posted')
        self.assertTrue(move.deferred_move_ids)

    def test_deferred_compute_method_full_months(self):
        """
        Test that the deferred amount is correctly computed when the new full_months method computation is used
        """
        self.company.deferred_expense_amount_computation_method = 'full_months'

        dates = (('2024-06-05', '2025-06-04'), ('2024-06-30', '2025-06-29'))
        for (date_from, date_to) in dates:
            move = self.create_invoice('in_invoice', [(self.expense_accounts[0], 12000, date_from, date_to)], date='2024-06-05')
            self.assertRecordValues(move.deferred_move_ids.sorted('date'), [
                {'date': fields.Date.to_date('2024-06-05'), 'amount_total': 12000},
                {'date': fields.Date.to_date('2024-06-30'), 'amount_total': 1000},
                {'date': fields.Date.to_date('2024-07-31'), 'amount_total': 1000},
                {'date': fields.Date.to_date('2024-08-31'), 'amount_total': 1000},
                {'date': fields.Date.to_date('2024-09-30'), 'amount_total': 1000},
                {'date': fields.Date.to_date('2024-10-31'), 'amount_total': 1000},
                {'date': fields.Date.to_date('2024-11-30'), 'amount_total': 1000},
                {'date': fields.Date.to_date('2024-12-31'), 'amount_total': 1000},
                {'date': fields.Date.to_date('2025-01-31'), 'amount_total': 1000},
                {'date': fields.Date.to_date('2025-02-28'), 'amount_total': 1000},
                {'date': fields.Date.to_date('2025-03-31'), 'amount_total': 1000},
                {'date': fields.Date.to_date('2025-04-30'), 'amount_total': 1000},
                {'date': fields.Date.to_date('2025-05-31'), 'amount_total': 1000},
                # 0 for June 2025, so no move created
            ])

        # Start of month <=> Equal per month method
        move = self.create_invoice('in_invoice', [(self.expense_accounts[0], 12000, '2024-07-01', '2025-06-30')], date='2024-07-01')
        self.assertRecordValues(move.deferred_move_ids.sorted(lambda m: (m.date, m.amount_total)), [
            {'date': fields.Date.to_date('2024-07-01'), 'amount_total': 12000},
            {'date': fields.Date.to_date('2024-07-31'), 'amount_total': 1000},
            {'date': fields.Date.to_date('2024-08-31'), 'amount_total': 1000},
            {'date': fields.Date.to_date('2024-09-30'), 'amount_total': 1000},
            {'date': fields.Date.to_date('2024-10-31'), 'amount_total': 1000},
            {'date': fields.Date.to_date('2024-11-30'), 'amount_total': 1000},
            {'date': fields.Date.to_date('2024-12-31'), 'amount_total': 1000},
            {'date': fields.Date.to_date('2025-01-31'), 'amount_total': 1000},
            {'date': fields.Date.to_date('2025-02-28'), 'amount_total': 1000},
            {'date': fields.Date.to_date('2025-03-31'), 'amount_total': 1000},
            {'date': fields.Date.to_date('2025-04-30'), 'amount_total': 1000},
            {'date': fields.Date.to_date('2025-05-31'), 'amount_total': 1000},
            {'date': fields.Date.to_date('2025-06-30'), 'amount_total': 1000},
        ])

        # Nothing to defer, everything is in the same month
        move = self.create_invoice('in_invoice', [(self.expense_accounts[0], 12000, '2024-01-01', '2024-01-16')], date='2024-01-01')
        self.assertFalse(move.deferred_move_ids)

        # Round period of 2 months -> Divide by 2
        move = self.create_invoice('in_invoice', [(self.expense_accounts[0], 12000, '2024-01-01', '2024-02-29')], date='2024-01-01')
        self.assertRecordValues(move.deferred_move_ids.sorted(lambda m: (m.date, m.amount_total)), [
            {'date': fields.Date.to_date('2024-01-01'), 'amount_total': 12000},
            {'date': fields.Date.to_date('2024-01-31'), 'amount_total': 6000},
            {'date': fields.Date.to_date('2024-02-29'), 'amount_total': 6000},
        ])

        # Round period of 2 months -> Divide by 2
        move = self.create_invoice('in_invoice', [(self.expense_accounts[0], 12000, '2024-01-15', '2024-03-14')], date='2024-01-01')
        self.assertRecordValues(move.deferred_move_ids.sorted(lambda m: (m.date, m.amount_total)), [
            {'date': fields.Date.to_date('2024-01-01'), 'amount_total': 12000},
            {'date': fields.Date.to_date('2024-01-31'), 'amount_total': 6000},
            {'date': fields.Date.to_date('2024-02-29'), 'amount_total': 6000},
        ])

        # Period of exactly one month: full amount should be in Jan. So we revert 1st Jan, and account for 31st Jan <=> don't generate anything
        move = self.create_invoice('in_invoice', [(self.expense_accounts[0], 12000, '2024-01-15', '2024-02-14')], date='2024-01-01')
        self.assertFalse(move.deferred_move_ids)

        # Not-round period of 1.5 month with only one end of month in January (same explanation as above)
        move = self.create_invoice('in_invoice', [(self.expense_accounts[0], 12000, '2024-01-01', '2024-02-15')], date='2024-01-01')
        self.assertFalse(move.deferred_move_ids)

        # Not-round period of 1.5+ month with only one end of month in January (same explanation as above)
        move = self.create_invoice('in_invoice', [(self.expense_accounts[0], 12000, '2024-01-05', '2024-02-15')], date='2024-01-01')
        self.assertFalse(move.deferred_move_ids)

        # Period of exactly one month: full amount should be in Feb. So we revert 1st Jan, and account for all on 29th Feb.
        # Deferrals are in different months for this case, so we should the deferrals should be generated.
        move = self.create_invoice('in_invoice', [(self.expense_accounts[0], 12000, '2024-02-15', '2024-03-14')], date='2024-01-01')
        self.assertRecordValues(move.deferred_move_ids.sorted(lambda m: (m.date, m.amount_total)), [
            {'date': fields.Date.to_date('2024-01-01'), 'amount_total': 12000},
            {'date': fields.Date.to_date('2024-02-29'), 'amount_total': 12000},
        ])

        # Not-round period of 1.5+ month: full amount should be in Feb. So we revert 1st Jan, and account for all on 29th Feb.
        # Deferrals are in different months for this case, so we should the deferrals should be generated.
        move = self.create_invoice('in_invoice', [(self.expense_accounts[0], 12000, '2024-02-05', '2024-03-15')], date='2024-01-01')
        self.assertRecordValues(move.deferred_move_ids.sorted(lambda m: (m.date, m.amount_total)), [
            {'date': fields.Date.to_date('2024-01-01'), 'amount_total': 12000},
            {'date': fields.Date.to_date('2024-02-29'), 'amount_total': 12000},
        ])

        # Not-round period of 1.5 month with 2 ends of months, so divide balance by 2
        move = self.create_invoice('in_invoice', [(self.expense_accounts[0], 12000, '2024-01-16', '2024-02-29')], date='2024-01-01')
        self.assertRecordValues(move.deferred_move_ids.sorted(lambda m: (m.date, m.amount_total)), [
            {'date': fields.Date.to_date('2024-01-01'), 'amount_total': 12000},
            {'date': fields.Date.to_date('2024-01-31'), 'amount_total': 6000},
            {'date': fields.Date.to_date('2024-02-29'), 'amount_total': 6000},
        ])

        # Not-round period of 2.5 month, with 3 ends of months, so divide balance by 3
        move = self.create_invoice('in_invoice', [(self.expense_accounts[0], 12000, '2024-01-16', '2024-03-31')], date='2024-01-01')
        self.assertRecordValues(move.deferred_move_ids.sorted(lambda m: (m.date, m.amount_total)), [
            {'date': fields.Date.to_date('2024-01-01'), 'amount_total': 12000},
            {'date': fields.Date.to_date('2024-01-31'), 'amount_total': 4000},
            {'date': fields.Date.to_date('2024-02-29'), 'amount_total': 4000},
            {'date': fields.Date.to_date('2024-03-31'), 'amount_total': 4000},
        ])
