870 lines
40 KiB
Python
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)
|