Files
enterprise-addons/account_reports/tests/test_budget.py
T

870 lines
40 KiB
Python

from odoo import Command, fields
from odoo.tests import tagged
from odoo.tools import date_utils
from odoo.addons.account_reports.tests.common import TestAccountReportsCommon
@tagged('post_install', '-at_install')
class TestBudgetReport(TestAccountReportsCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.company_data['company'].totals_below_sections = False
cls.account_1 = cls.company_data['default_account_revenue']
cls.account_2 = cls.copy_account(cls.account_1)
cls.account_3 = cls.copy_account(cls.account_1)
cls.account_4 = cls.copy_account(cls.account_1)
# Create a test report
cls.report = cls.env['account.report'].create({
'name': "Budget Test",
'filter_date_range': True,
'filter_budgets': True,
'filter_period_comparison': True,
'column_ids': [
Command.create({
'name': "Balance",
'expression_label': 'balance',
}),
],
'line_ids': [
Command.create({
'name': 'line_domain',
'groupby': 'account_id',
'foldable': False,
'expression_ids': [
Command.create({
'label': 'balance',
'formula': "[('account_id.account_type', '=', 'income')]",
'subformula': 'sum',
'engine': 'domain',
}),
],
}),
Command.create({
'name': 'line_account_codes',
'groupby': 'account_id',
'foldable': False,
'expression_ids': [
Command.create({
'label': 'balance',
'formula': cls.account_1.code[:3],
'engine': 'account_codes',
}),
],
}),
],
})
# Create budgets
cls.budget_1 = cls._create_budget(
{
cls.account_1.id: 1000,
cls.account_3.id: 100,
cls.account_4.id: 10,
},
'2020-01-01',
'2020-01-01',
)
cls.budget_2 = cls._create_budget(
{
cls.account_2.id: 10,
},
'2020-01-01',
'2020-01-01',
)
@classmethod
def _create_budget(cls, amount_per_account_ids=None, date_from=None, date_to=None):
date_from, date_to = fields.Date.to_date(date_from), fields.Date.to_date(date_to)
items = []
for account_id, amount in amount_per_account_ids.items():
for item_date in date_utils.date_range(date_from, date_to):
items.append(Command.create({
'amount': amount,
'account_id': account_id,
'date': date_utils.start_of(item_date, 'month'),
}))
return cls.env['account.report.budget'].create({
'name': 'Budget',
'item_ids': items,
})
@classmethod
def _create_moves(cls, amount_per_account_ids=None, date_from=None, date_to=None, to_post=True):
date_from, date_to = fields.Date.to_date(date_from), fields.Date.to_date(date_to)
moves_to_create = []
for move_date in date_utils.date_range(date_from, date_to):
move_to_create = {
'date': move_date,
'journal_id': cls.company_data['default_journal_misc'].id,
'line_ids': [],
}
for account_id, amount in amount_per_account_ids.items():
move_to_create['line_ids'].extend([
Command.create({
'debit': amount,
'account_id': account_id,
}),
Command.create({
'credit': amount,
'account_id': cls.company_data['default_account_assets'].id,
})
])
moves_to_create.append(move_to_create)
moves = cls.env['account.move'].create(moves_to_create)
if to_post:
moves.action_post()
return moves
def test_reports_single_budget(self):
self._create_moves(
{
self.account_1.id: 100,
self.account_2.id: 200,
self.account_3.id: 300,
},
'2020-01-01',
'2020-01-01',
)
options = self._generate_options(
self.report,
'2020-01-01',
'2020-12-31',
default_options={'budgets': [{'id': self.budget_1.id, 'selected': True}]},
)
lines = self.report._get_lines(options)
self.assertLinesValues(
lines,
[ 0, 1, 2, 3],
[
('line_domain', 600, 1110, '54.1%'),
(self.account_1.display_name, 100, 1000, '10.0%'),
(self.account_2.display_name, 200, 0, 'n/a'),
(self.account_3.display_name, 300, 100, '300.0%'),
(self.account_4.display_name, 0, 10, '0.0%'),
('line_account_codes', 600, 1110, '54.1%'),
(self.account_1.display_name, 100, 1000, '10.0%'),
(self.account_2.display_name, 200, 0, 'n/a'),
(self.account_3.display_name, 300, 100, '300.0%'),
(self.account_4.display_name, 0, 10, '0.0%'),
],
options,
)
def test_reports_multiple_budgets(self):
""" This test verifies the report when we have several budgets selected.
The report should have 2 columns per budget, the budget itself and
the comparison column.
"""
self._create_moves(
{
self.account_1.id: 100,
self.account_2.id: 200,
self.account_3.id: 300,
},
'2020-01-01',
'2020-01-01',
)
options = self._generate_options(
self.report,
'2020-01-01',
'2020-12-31',
default_options={'budgets': [{'id': self.budget_1.id, 'selected': True}, {'id': self.budget_2.id, 'selected': True}]},
)
lines = self.report._get_lines(options)
self.assertLinesValues(
lines,
[ 0, 1, 2, 3, 4, 5],
[
('line_domain', 600, 1110, '54.1%', 10, '6000.0%'),
(self.account_1.display_name, 100, 1000, '10.0%', 0, 'n/a'),
(self.account_2.display_name, 200, 0, 'n/a', 10, '2000.0%'),
(self.account_3.display_name, 300, 100, '300.0%', 0, 'n/a'),
(self.account_4.display_name, 0, 10, '0.0%', 0, 'n/a'),
('line_account_codes', 600, 1110, '54.1%', 10, '6000.0%'),
(self.account_1.display_name, 100, 1000, '10.0%', 0, 'n/a'),
(self.account_2.display_name, 200, 0, 'n/a', 10, '2000.0%'),
(self.account_3.display_name, 300, 100, '300.0%', 0, 'n/a'),
(self.account_4.display_name, 0, 10, '0.0%', 0, 'n/a'),
],
options,
)
def test_reports_budget_with_comparison_period(self):
budget_2024 = self._create_budget({self.account_1.id: 200}, '2024-01-01', '2024-12-31')
moves = self._create_moves({self.account_1.id: 200}, '2024-01-01', '2024-12-31', to_post=False)
moves += self._create_moves({self.account_1.id: 600}, '2024-06-22', '2024-06-22', to_post=False)
moves.action_post()
options = self._generate_options(
self.report,
'2024-06-01',
'2024-06-30',
default_options={
'budgets': [{'id': budget_2024.id, 'selected': True}],
'comparison': {'filter': 'previous_period', 'number_period': 1},
},
)
self.assertLinesValues(
self.report._get_lines(options),
[ 0, 1, 2, 3, 4, 5, 6],
[
('line_domain', 800, 200, '400.0%', 200, 200, '100.0%'),
(self.account_1.display_name, 800, 200, '400.0%', 200, 200, '100.0%'),
('line_account_codes', 800, 200, '400.0%', 200, 200, '100.0%'),
(self.account_1.display_name, 800, 200, '400.0%', 200, 200, '100.0%'),
],
options,
)
def test_reports_budget_with_comparison_period_and_multiple_budgets(self):
budget_2024 = self._create_budget({self.account_1.id: 200}, '2024-01-01', '2024-12-31')
budget_2023 = self._create_budget({self.account_1.id: 400}, '2023-01-01', '2023-12-31')
moves = self._create_moves({self.account_1.id: 200}, '2024-01-01', '2024-12-31', to_post=False)
moves += self._create_moves({self.account_1.id: 400}, '2023-01-01', '2023-12-31', to_post=False)
moves.action_post()
options = self._generate_options(
self.report,
'2024-01-01',
'2024-12-31',
default_options={
'budgets': [{'id': budget_2024.id, 'selected': True}, {'id': budget_2023.id, 'selected': True}],
'comparison': {'filter': 'previous_period', 'number_period': 1},
},
)
self.assertLinesValues(
self.report._get_lines(options),
[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
# [ Period 2024 + Budget 2024 + % + Budget 2023 + %] [ Period 2023 + Budget 2024 + % + Budget 2023 + %]
[
('line_domain', 2400, 2400, '100.0%', 0, 'n/a', 4800, 0, 'n/a', 4800, '100.0%'),
(self.account_1.display_name, 2400, 2400, '100.0%', 0, 'n/a', 4800, 0, 'n/a', 4800, '100.0%'),
('line_account_codes', 2400, 2400, '100.0%', 0, 'n/a', 4800, 0, 'n/a', 4800, '100.0%'),
(self.account_1.display_name, 2400, 2400, '100.0%', 0, 'n/a', 4800, 0, 'n/a', 4800, '100.0%'),
],
options,
)
def test_report_budget_items_with_different_date_filters(self):
""" The aim of this test is checking that we get the correct budget items
according to the selected dates.
"""
budget_2024 = self._create_budget({self.account_1.id: 300}, '2024-01-01', '2024-12-31')
self._create_moves({self.account_1.id: 200}, '2024-01-01', '2024-12-31')
# Case when we display a whole year in the report
options = self._generate_options(
self.report,
'2024-01-01',
'2024-12-31',
default_options={'budgets': [{'id': budget_2024.id, 'selected': True}]},
)
self.assertLinesValues(
self.report._get_lines(options),
[ 0, 1, 2],
[
('line_domain', 2400, 3600),
(self.account_1.display_name, 2400, 3600),
('line_account_codes', 2400, 3600),
(self.account_1.display_name, 2400, 3600),
],
options,
)
# Case when we display a whole quarter
options = self._generate_options(
self.report,
'2024-01-01',
'2024-03-31',
default_options={'budgets': [{'id': budget_2024.id, 'selected': True}]},
)
self.assertLinesValues(
self.report._get_lines(options),
[ 0, 1, 2],
[
('line_domain', 600, 900),
(self.account_1.display_name, 600, 900),
('line_account_codes', 600, 900),
(self.account_1.display_name, 600, 900),
],
options,
)
# Case when we display a whole month
options = self._generate_options(
self.report,
'2024-03-01',
'2024-03-31',
default_options={'budgets': [{'id': budget_2024.id, 'selected': True}]},
)
self.assertLinesValues(
self.report._get_lines(options),
[ 0, 1, 2],
[
('line_domain', 200, 300),
(self.account_1.display_name, 200, 300),
('line_account_codes', 200, 300),
(self.account_1.display_name, 200, 300),
],
options,
)
def test_report_budget_edit_items(self):
""" This test verifies the way we're modifying budget items.
The system calculates the difference between the old and new values
and distributes this difference proportionally over the number of
months in the selected period.
The test checks the following scenarios:
1. Adding a rounded value (1200) for an entire year (12 months).
2. Setting the value to 300 for 3 months (no change expected).
3. Setting the value to 900 for 3 months, resulting in three new values of 200 each.
4. Reducing the value for a whole year to a non-rounded value and checking if the
remainder is correctly applied to the last period.
"""
def set_budget_item(value, account_id):
budget_2024._create_or_update_budget_items(
value_to_set=value,
account_id=account_id,
rounding=self.env.company.currency_id.decimal_places,
date_from=options['date']['date_from'],
date_to=options['date']['date_to'],
)
budget_2024 = self.env['account.report.budget'].create({'name': "Budget 2024"})
self._create_moves({self.account_1.id: 200}, '2024-01-01', '2024-12-31')
options = self._generate_options(
self.report,
'2024-01-01',
'2024-12-31',
default_options={'budgets': [{'id': budget_2024.id, 'selected': True}]},
)
set_budget_item(1200, self.account_1.id)
expected_items = [
{'amount': 100.0, 'date': fields.Date.to_date('2024-01-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2024-02-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2024-03-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2024-04-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2024-05-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2024-06-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2024-07-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2024-08-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2024-09-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2024-10-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2024-11-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2024-12-01')},
]
self.assertRecordValues(
budget_2024.item_ids,
expected_items,
)
options = self._generate_options(
self.report,
'2024-01-01',
'2024-03-31',
default_options={'budgets': [{'id': budget_2024.id, 'selected': True}]},
)
set_budget_item(300, self.account_1.id)
# Nothing should be added as we don't change anything
self.assertRecordValues(
budget_2024.item_ids,
expected_items,
)
set_budget_item(900, self.account_1.id)
expected_items = [
{'amount': 300.0, 'date': fields.Date.to_date('2024-01-01')},
{'amount': 300.0, 'date': fields.Date.to_date('2024-02-01')},
{'amount': 300.0, 'date': fields.Date.to_date('2024-03-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2024-04-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2024-05-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2024-06-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2024-07-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2024-08-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2024-09-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2024-10-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2024-11-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2024-12-01')},
]
self.assertRecordValues(
budget_2024.item_ids,
expected_items,
)
options = self._generate_options(
self.report,
'2024-01-01',
'2024-12-31',
default_options={'budgets': [{'id': budget_2024.id, 'selected': True}]},
)
self.assertLinesValues(
self.report._get_lines(options),
[ 0, 1, 2],
[
('line_domain', 2400, 1800),
(self.account_1.display_name, 2400, 1800),
('line_account_codes', 2400, 1800),
(self.account_1.display_name, 2400, 1800),
],
options,
)
# Test the case with a remainder
set_budget_item(1000, self.account_1.id)
expected_items = [
{'amount': 300.0 - 66.66, 'date': fields.Date.to_date('2024-01-01')},
{'amount': 300.0 - 66.66, 'date': fields.Date.to_date('2024-02-01')},
{'amount': 300.0 - 66.66, 'date': fields.Date.to_date('2024-03-01')},
{'amount': 100.0 - 66.66, 'date': fields.Date.to_date('2024-04-01')},
{'amount': 100.0 - 66.66, 'date': fields.Date.to_date('2024-05-01')},
{'amount': 100.0 - 66.66, 'date': fields.Date.to_date('2024-06-01')},
{'amount': 100.0 - 66.66, 'date': fields.Date.to_date('2024-07-01')},
{'amount': 100.0 - 66.66, 'date': fields.Date.to_date('2024-08-01')},
{'amount': 100.0 - 66.66, 'date': fields.Date.to_date('2024-09-01')},
{'amount': 100.0 - 66.66, 'date': fields.Date.to_date('2024-10-01')},
{'amount': 100.0 - 66.66, 'date': fields.Date.to_date('2024-11-01')},
{'amount': 100.0 - 66.74, 'date': fields.Date.to_date('2024-12-01')},
]
self.assertRecordValues(
budget_2024.item_ids,
expected_items,
)
def test_report_budget_show_all_accounts_filter(self):
""" The aim of this test is checking that the show all accounts filter
is returning all the income accounts (as our test report is working with
income accounts only).
"""
self.env.companies = self.env.company
budget_2024 = self._create_budget({self.account_1.id: 300}, '2024-01-01', '2024-12-31')
options = self._generate_options(
self.report,
'2024-01-01',
'2024-12-31',
default_options={
'budgets': [{'id': budget_2024.id, 'selected': True}],
'show_all_accounts': True,
},
)
expected_lines = [
('line_domain', 0, 3600),
(self.account_1.display_name, 0, 3600),
]
expected_lines += [
(account['display_name'], 0, 0)
for account in self.env['account.account'].search_read([
('id', '!=', self.account_1.id),
('account_type', '=', 'income'),
('company_ids', 'in', self.report.get_report_company_ids(options)),
], ['display_name'], order='code')
]
expected_lines += [
('line_account_codes', 0, 3600),
(self.account_1.display_name, 0, 3600),
]
expected_lines += [
(account['display_name'], 0, 0)
for account in self.env['account.account'].search_read([
('id', '!=', self.account_1.id),
('code', '=like', f'{self.account_1.code[:3]}%'),
('company_ids', 'in', self.report.get_report_company_ids(options)),
], ['display_name'], order='code')
]
self.assertLinesValues(
self.report._get_lines(options),
[0, 1, 2],
expected_lines,
options,
)
def test_financial_budget_with_analytic_groupby(self):
""" Ensure that when budgets and analytics groupby filters are both active, then their headers are
displayed on the same level, and values in the report are properly computed"""
budget_a = self.env['account.report.budget'].create([{'name': "Budget A"}])
budget_b = self.env['account.report.budget'].create([{'name': "Budget B"}])
budget_a._create_or_update_budget_items(
value_to_set=2400, # 100 each month
account_id=self.account_1.id,
rounding=self.env.company.currency_id.decimal_places,
date_from='2024-01-01',
date_to='2025-12-31',
)
budget_b._create_or_update_budget_items(
value_to_set=3000, # 125 each month
account_id=self.account_1.id,
rounding=self.env.company.currency_id.decimal_places,
date_from='2024-01-01',
date_to='2025-12-31',
)
self.env.user.group_ids += self.env.ref('analytic.group_analytic_accounting')
self.report.write({'filter_analytic_groupby': True})
analytic_plan_a = self.env['account.analytic.plan'].create([{
'name': 'Plan A',
}])
analytic_account_1 = self.env['account.analytic.account'].create([{
'name': 'Account A1',
'plan_id': analytic_plan_a.id
}])
analytic_account_2 = self.env['account.analytic.account'].create([{
'name': 'Account A2',
'plan_id': analytic_plan_a.id
}])
moves_2024 = self._create_moves({self.account_1.id: 100}, '2024-01-01', '2024-12-31', to_post=False)
moves_2025 = self._create_moves({self.account_1.id: 150}, '2025-01-01', '2025-12-31', to_post=False)
for move_line in (moves_2024 + moves_2025).line_ids:
move_line.analytic_distribution = {str(analytic_account_1.id): 75.0, str(analytic_account_2.id): 25.0}
(moves_2024 + moves_2025).action_post()
options = self._generate_options(
self.report,
'2025-01-01',
'2025-12-31',
default_options={
'comparison': {'filter': 'previous_period', 'number_period': 1},
'analytic_accounts_groupby': [analytic_account_1.id, analytic_account_2.id],
'budgets': [{'id': budget_a.id, 'selected': True}, {'id': budget_b.id, 'selected': True}],
}
)
self.assertEqual(
[header['name'] for header in options['column_headers'][1]],
['Account A1', 'Account A2', 'Period Total', 'Budget A', '%', 'Budget B', '%']
)
self.assertLinesValues(
self.report._get_lines(options),
# [ 2025 ] [ 2024 ]
# [ A1 ] [ A2 ] [ Total ] [ Budget A ] [ % ] [ Budget B ] [ % ] [ A1 ] [ A2 ] [ Total ] [ Budget A ] [ % ] [ Budget B ] [ % ]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14],
[
('line_domain', 1350, 450, 1800, 1200, '150.0%', 1500, '120.0%', 900, 300, 1200, 1200, '100.0%', 1500, '80.0%'),
(self.account_1.display_name, 1350, 450, 1800, 1200, '150.0%', 1500, '120.0%', 900, 300, 1200, 1200, '100.0%', 1500, '80.0%'),
('line_account_codes', 1350, 450, 1800, 1200, '150.0%', 1500, '120.0%', 900, 300, 1200, 1200, '100.0%', 1500, '80.0%'),
(self.account_1.display_name, 1350, 450, 1800, 1200, '150.0%', 1500, '120.0%', 900, 300, 1200, 1200, '100.0%', 1500, '80.0%'),
],
options,
)
def test_financial_budget_with_several_columns(self):
""" Ensure that financial budget feature works properly on reports with several columns,
and that percentage column of budget is hidden is the case where multiple monetary columns exist """
self.report.write({
'column_ids': [
Command.create({
'name': "Column test",
'expression_label': 'column_test',
}),
],
})
self._create_moves(
{
self.account_1.id: 100,
self.account_2.id: 200,
self.account_3.id: 300,
},
'2020-01-01',
'2020-01-01',
)
options = self._generate_options(
self.report,
'2020-01-01',
'2020-12-31',
default_options={
'comparison': {'filter': 'previous_period', 'number_period': 1},
'budgets': [{'id': self.budget_1.id, 'selected': True}],
}
)
# Ensure level header colspan is 3 for top header, 2 for the columns + 1 selected budget
self.assertEqual(
self.report._get_column_headers_render_data(options),
{'level_colspan': [3, 2], 'level_repetitions': [1, 2], 'custom_subheaders': []}
)
self.assertLinesValues(
self.report._get_lines(options),
# [ 2020 ] [ 2019 ]
# [ col 1 ] [ col 2 ] [ budget 1 ] [ col 1 ] [ col 2 ] [ budget 1 ]
[0, 1, 2, 3, 4, 5, 6],
[
('line_domain', 600, '', 1110, 0, '', 0),
(self.account_1.display_name, 100, '', 1000, 0, '', 0),
(self.account_2.display_name, 200, '', 0, 0, '', 0),
(self.account_3.display_name, 300, '', 100, 0, '', 0),
(self.account_4.display_name, 0, '', 10, 0, '', 0),
('line_account_codes', 600, '', 1110, 0, '', 0),
(self.account_1.display_name, 100, '', 1000, 0, '', 0),
(self.account_2.display_name, 200, '', 0, 0, '', 0),
(self.account_3.display_name, 300, '', 100, 0, '', 0),
(self.account_4.display_name, 0, '', 10, 0, '', 0),
],
options,
)
def test_financial_budget_with_horizontal_and_analytic_groupbys(self):
""" Ensure that financial budgets are properly computed when combined with horizontal groupby filters and analytic filters """
budget = self.env['account.report.budget'].create([{'name': "Budget test"}])
budget._create_or_update_budget_items(
value_to_set=1200, # 100 each month
account_id=self.account_1.id,
rounding=self.env.company.currency_id.decimal_places,
date_from='2025-01-01',
date_to='2025-12-31',
)
self.env.user.group_ids += self.env.ref('analytic.group_analytic_accounting')
self.report.write({'filter_analytic_groupby': True})
analytic_plan = self.env['account.analytic.plan'].create([{
'name': 'Plan test',
}])
analytic_account_1 = self.env['account.analytic.account'].create([{
'name': 'Account A1',
'plan_id': analytic_plan.id
}])
analytic_account_2 = self.env['account.analytic.account'].create([{
'name': 'Account A2',
'plan_id': analytic_plan.id
}])
moves_2025 = self._create_moves({self.account_1.id: 150}, '2025-01-01', '2025-12-31', to_post=False)
for move_line in moves_2025.line_ids:
move_line.partner_id = self.partner_a
move_line.analytic_distribution = {str(analytic_account_1.id): 50.0, str(analytic_account_2.id): 25.0}
moves_2025.action_post()
move_partner_b = self._create_moves({self.account_1.id: 300}, '2025-01-01', '2025-01-31', to_post=False)
move_partner_b.line_ids.partner_id = self.partner_b
move_partner_b.action_post()
horizontal_group = self.env['account.report.horizontal.group'].create([{
'name': 'Horizontal Group',
'rule_ids': [
Command.create({
'field_name': 'partner_id',
'domain': f"[('id', 'in', {(self.partner_a + self.partner_b).ids})]",
}),
],
}])
self.report.horizontal_group_ids |= horizontal_group
options = self._generate_options(
self.report,
'2025-01-01',
'2025-12-31',
default_options={
'analytic_accounts_groupby': [analytic_account_1.id, analytic_account_2.id],
'selected_horizontal_group_id': horizontal_group.id,
'budgets': [{'id': budget.id, 'selected': True}],
}
)
self.assertEqual(
[
[header['name'] for header in header_level]
for header_level in options['column_headers']
],
[
['2025'],
['partner_a', 'partner_b', 'Period Total', 'Budget test', '%'],
['Account A1', 'Account A2', 'Total'],
]
)
self.assertEqual(
self.report._get_column_headers_render_data(options),
{'level_colspan': [9, 3, 1], 'level_repetitions': [1, 1, 2], 'custom_subheaders': []}
)
self.assertLinesValues(
self.report._get_lines(options),
# [ 2025 ]
# [ Partner A ] [ Partner B ] [ Period Total ] [ Budget ] [ % ]
# [ A1 ] [ A2 ] [ Total ] [ A1 ] [ A2 ] [ Total ] [ ] [ ] [ ]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
[
('line_domain', 900, 450, 1800, 0, 0, 300, 2100, 1200, '175.0%'),
(self.account_1.display_name, 900, 450, 1800, 0, 0, 300, 2100, 1200, '175.0%'),
('line_account_codes', 900, 450, 1800, 0, 0, 300, 2100, 1200, '175.0%'),
(self.account_1.display_name, 900, 450, 1800, 0, 0, 300, 2100, 1200, '175.0%'),
],
options,
)
def test_hierarchy_with_budget(self):
""" Check that the report lines are correct with a budget and the option "Hierarchy and subtotals" is ticked"""
self.env['account.group'].create({
'name': 'Sales',
'code_prefix_start': '1',
'code_prefix_end': '9',
})
move = self.env['account.move'].create({
'date': '2020-02-02',
'line_ids': [
Command.create({
'account_id': self.company_data['default_account_revenue'].id,
'name': 'name',
})
],
})
move.action_post()
move.line_ids.flush_recordset()
profit_and_loss_report = self.env.ref('account_reports.profit_and_loss')
line_id = self._get_basic_line_dict_id_from_report_line_ref('account_reports.account_financial_report_revenue0')
options = self._generate_options(profit_and_loss_report, '2020-02-01', '2024-12-28', default_options={'budgets': [{'id': self.budget_1.id, 'selected': True}]})
options['unfolded_lines'] = [line_id]
options['hierarchy'] = True
options['unfold_all'] = True
lines = profit_and_loss_report._get_lines(options)
unfolded_lines = profit_and_loss_report._get_unfolded_lines(lines, line_id)
unfolded_lines = [{'name': line['name'], 'level': line['level']} for line in unfolded_lines]
self.assertEqual(
unfolded_lines,
[
{'level': 1, 'name': 'Revenue'},
{'level': 2, 'name': '1-9 Sales'},
{'level': 3, 'name': '400000 Product Sales'},
]
)
def test_budget_report_edit_items_with_date(self):
"""
Ensure budget items are created or updated correctly
when editing across varying date ranges
"""
def set_budget_item(value, account_id, date_from, date_to):
budget._create_or_update_budget_items(
value_to_set=value,
account_id=account_id,
rounding=self.env.company.currency_id.decimal_places,
date_from=date_from,
date_to=date_to,
)
budget = self.env['account.report.budget'].create({'name': "Budget"})
set_budget_item(1200, self.account_1.id, '2025-01-01', '2025-12-01')
expected_items = [
{'amount': 100.0, 'date': fields.Date.to_date('2025-01-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2025-02-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2025-03-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2025-04-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2025-05-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2025-06-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2025-07-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2025-08-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2025-09-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2025-10-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2025-11-01')},
{'amount': 100.0, 'date': fields.Date.to_date('2025-12-01')},
]
self.assertRecordValues(budget.item_ids, expected_items)
# Update the second and third months to total of 900 (450 per month)
set_budget_item(900, self.account_1.id, '2025-01-10', '2025-3-10')
# Now the second and third months each have an amount of 450
set_budget_item(1000, self.account_1.id, '2025-01-01', '2025-12-01')
expected_items = [
{'amount': 100.0 - 75.00, 'date': fields.Date.to_date('2025-01-01')},
{'amount': 450.0 - 75.00, 'date': fields.Date.to_date('2025-02-01')},
{'amount': 450.0 - 75.00, 'date': fields.Date.to_date('2025-03-01')},
{'amount': 100.0 - 75.00, 'date': fields.Date.to_date('2025-04-01')},
{'amount': 100.0 - 75.00, 'date': fields.Date.to_date('2025-05-01')},
{'amount': 100.0 - 75.00, 'date': fields.Date.to_date('2025-06-01')},
{'amount': 100.0 - 75.00, 'date': fields.Date.to_date('2025-07-01')},
{'amount': 100.0 - 75.00, 'date': fields.Date.to_date('2025-08-01')},
{'amount': 100.0 - 75.00, 'date': fields.Date.to_date('2025-09-01')},
{'amount': 100.0 - 75.00, 'date': fields.Date.to_date('2025-10-01')},
{'amount': 100.0 - 75.00, 'date': fields.Date.to_date('2025-11-01')},
{'amount': 100.0 - 75.00, 'date': fields.Date.to_date('2025-12-01')},
]
self.assertRecordValues(budget.item_ids, expected_items)
def test_budget_filter_with_percentage_figure_type(self):
"""
Test that budget filters don't crash when a report has lines with percentage
figure type that cannot be matched for budget comparison.
"""
report_with_percentage = self.env['account.report'].create({
'name': "Report with Percentage Line",
'filter_date_range': True,
'filter_budgets': True,
'root_report_id': self.env.ref('account_reports.profit_and_loss').id,
'column_ids': [
Command.create({
'name': "Balance",
'expression_label': 'balance',
}),
],
'line_ids': [
Command.create({
'name': 'percentage_line',
'expression_ids': [
Command.create({
'label': 'balance',
'formula': '100',
'engine': 'external',
'figure_type': 'percentage',
}),
],
}),
Command.create({
'name': 'monetary_line',
'groupby': 'account_id',
'foldable': False,
'expression_ids': [
Command.create({
'label': 'balance',
'formula': "[('account_id.account_type', '=', 'income')]",
'subformula': 'sum',
'engine': 'domain',
}),
],
}),
],
})
self._create_moves(
{self.account_1.id: 100},
'2020-01-01',
'2020-01-01',
)
options = self._generate_options(
report_with_percentage,
'2020-01-01',
'2020-12-31',
default_options={'budgets': [{'id': self.budget_1.id, 'selected': True}]},
)
lines = report_with_percentage._get_lines(options)
self.assertTrue(lines)