You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
261 lines
12 KiB
Python
261 lines
12 KiB
Python
8 months ago
|
# -*- coding: utf-8 -*-
|
||
|
import copy
|
||
|
from contextlib import contextmanager
|
||
|
from datetime import datetime, date
|
||
|
from unittest.mock import patch
|
||
|
|
||
|
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||
|
|
||
|
from odoo import Command, fields
|
||
|
from odoo.exceptions import UserError
|
||
|
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
|
||
|
from odoo.tools.misc import formatLang
|
||
|
|
||
|
class TestAccountReportsCommon(AccountTestInvoicingCommon):
|
||
|
|
||
|
@classmethod
|
||
|
def setUpClass(cls, chart_template_ref=None):
|
||
|
super().setUpClass(chart_template_ref=chart_template_ref)
|
||
|
|
||
|
cls.company_data_2['company'].currency_id = cls.currency_data['currency']
|
||
|
cls.company_data_2['currency'] = cls.currency_data['currency']
|
||
|
|
||
|
@classmethod
|
||
|
def _generate_options(cls, report, date_from, date_to, default_options=None):
|
||
|
''' Create new options at a certain date.
|
||
|
:param report: The report.
|
||
|
:param date_from: A datetime object, str representation of a date or False.
|
||
|
:param date_to: A datetime object or str representation of a date.
|
||
|
:return: The newly created options.
|
||
|
'''
|
||
|
if isinstance(date_from, datetime):
|
||
|
date_from_str = fields.Date.to_string(datetime)
|
||
|
else:
|
||
|
date_from_str = date_from
|
||
|
|
||
|
if isinstance(date_to, datetime):
|
||
|
date_to_str = fields.Date.to_string(datetime)
|
||
|
else:
|
||
|
date_to_str = date_to
|
||
|
|
||
|
if not default_options:
|
||
|
default_options = {}
|
||
|
|
||
|
return report._get_options({
|
||
|
'report_id': report.id,
|
||
|
'date': {
|
||
|
'date_from': date_from_str,
|
||
|
'date_to': date_to_str,
|
||
|
'mode': 'range',
|
||
|
'filter': 'custom',
|
||
|
},
|
||
|
**default_options,
|
||
|
})
|
||
|
|
||
|
def _update_comparison_filter(self, options, report, comparison_type, number_period, date_from=None, date_to=None):
|
||
|
''' Modify the existing options to set a new filter_comparison.
|
||
|
:param options: The report options.
|
||
|
:param report: The report.
|
||
|
:param comparison_type: One of the following values: ('no_comparison', 'custom', 'previous_period', 'previous_year').
|
||
|
:param number_period: The number of period to compare.
|
||
|
:param date_from: A datetime object for the 'custom' comparison_type.
|
||
|
:param date_to: A datetime object the 'custom' comparison_type.
|
||
|
:return: The newly created options.
|
||
|
'''
|
||
|
previous_options = {**options, 'comparison': {
|
||
|
'date_from': date_from and date_from.strftime(DEFAULT_SERVER_DATE_FORMAT),
|
||
|
'date_to': date_to and date_to.strftime(DEFAULT_SERVER_DATE_FORMAT),
|
||
|
'filter': comparison_type,
|
||
|
'number_period': number_period,
|
||
|
}}
|
||
|
return report._get_options(previous_options)
|
||
|
|
||
|
def _update_multi_selector_filter(self, options, option_key, selected_ids):
|
||
|
''' Modify a selector in the options to select .
|
||
|
:param options: The report options.
|
||
|
:param option_key: The key to the option.
|
||
|
:param selected_ids: The ids to be selected.
|
||
|
:return: The newly created options.
|
||
|
'''
|
||
|
new_options = copy.deepcopy(options)
|
||
|
for c in new_options[option_key]:
|
||
|
c['selected'] = c['id'] in selected_ids
|
||
|
return new_options
|
||
|
|
||
|
@contextmanager
|
||
|
def debug_mode(self, report):
|
||
|
Report_user_has_groups = type(report).user_has_groups
|
||
|
|
||
|
def user_has_groups(self, groups):
|
||
|
if groups == 'base.group_no_one':
|
||
|
return True
|
||
|
return Report_user_has_groups(self, groups)
|
||
|
|
||
|
with patch.object(type(report), 'user_has_groups', user_has_groups):
|
||
|
yield
|
||
|
|
||
|
def assertGrowthComparisonValues(self, lines, expected_values):
|
||
|
filtered_lines = self._filter_folded_lines(lines)
|
||
|
|
||
|
# Check number of lines.
|
||
|
self.assertEqual(len(filtered_lines), len(expected_values))
|
||
|
|
||
|
for value, expected_value in zip(filtered_lines, expected_values):
|
||
|
# Check number of columns.
|
||
|
key = 'growth_comparison_data'
|
||
|
self.assertEqual(len(value[key]) + 1, len(expected_value))
|
||
|
# Check name, value and class.
|
||
|
self.assertEqual(tuple([value['name'], value[key]['name'], value[key]['class']]), expected_value)
|
||
|
|
||
|
def assertHeadersValues(self, headers, expected_headers):
|
||
|
''' Helper to compare the headers returned by the _get_table method
|
||
|
with some expected results.
|
||
|
An header is a row of columns. Then, headers is a list of list of dictionary.
|
||
|
:param headers: The headers to compare.
|
||
|
:param expected_headers: The expected headers.
|
||
|
:return:
|
||
|
'''
|
||
|
# Check number of header lines.
|
||
|
self.assertEqual(len(headers), len(expected_headers))
|
||
|
|
||
|
for header, expected_header in zip(headers, expected_headers):
|
||
|
# Check number of columns.
|
||
|
self.assertEqual(len(header), len(expected_header))
|
||
|
|
||
|
for i, column in enumerate(header):
|
||
|
# Check name.
|
||
|
self.assertEqual(column['name'], self._convert_str_to_date(column['name'], expected_header[i]))
|
||
|
|
||
|
def assertLinesValues(self, lines, columns, expected_values, currency_map=None, ignore_folded=True):
|
||
|
''' Helper to compare the lines returned by the _get_lines method
|
||
|
with some expected results.
|
||
|
:param lines: See _get_lines.
|
||
|
:param columns: The columns index.
|
||
|
:param expected_values: A list of iterables.
|
||
|
:param currency_map: A map mapping each column_index to some extra options to test the lines:
|
||
|
- currency: The currency to be applied on the column.
|
||
|
- currency_code_index: The index of the column containing the currency code.
|
||
|
:param ignore_folded: Will not filter folded lines when True.
|
||
|
'''
|
||
|
if currency_map is None:
|
||
|
currency_map = {}
|
||
|
|
||
|
filtered_lines = self._filter_folded_lines(lines) if ignore_folded else lines
|
||
|
|
||
|
# Compare the table length to see if any line is missing
|
||
|
self.assertEqual(len(filtered_lines), len(expected_values))
|
||
|
|
||
|
# Compare cell by cell the current value with the expected one.
|
||
|
to_compare_list = []
|
||
|
for i, line in enumerate(filtered_lines):
|
||
|
compared_values = [[], []]
|
||
|
for j, index in enumerate(columns):
|
||
|
if index == 0:
|
||
|
current_value = line['name']
|
||
|
else:
|
||
|
# Some lines may not have columns, like title lines. In such case, no values should be provided for these.
|
||
|
# Note that the function expect a tuple, so the line still need a comma after the name value.
|
||
|
if j > len(expected_values[i]) - 1:
|
||
|
break
|
||
|
current_value = line['columns'][index-1].get('name', '')
|
||
|
|
||
|
expected_value = expected_values[i][j]
|
||
|
currency_data = currency_map.get(index, {})
|
||
|
used_currency = None
|
||
|
if 'currency' in currency_data:
|
||
|
used_currency = currency_data['currency']
|
||
|
elif 'currency_code_index' in currency_data:
|
||
|
currency_code = line['columns'][currency_data['currency_code_index'] - 1].get('name', '')
|
||
|
if currency_code:
|
||
|
used_currency = self.env['res.currency'].search([('name', '=', currency_code)], limit=1)
|
||
|
assert used_currency, "Currency having name=%s not found." % currency_code
|
||
|
if not used_currency:
|
||
|
used_currency = self.env.company.currency_id
|
||
|
|
||
|
if type(expected_value) in (int, float) and type(current_value) == str:
|
||
|
expected_value = formatLang(self.env, expected_value, currency_obj=used_currency)
|
||
|
|
||
|
compared_values[0].append(current_value)
|
||
|
compared_values[1].append(expected_value)
|
||
|
|
||
|
to_compare_list.append(compared_values)
|
||
|
|
||
|
errors = []
|
||
|
for i, to_compare in enumerate(to_compare_list):
|
||
|
if to_compare[0] != to_compare[1]:
|
||
|
errors += [
|
||
|
"\n==== Differences at index %s ====" % str(i),
|
||
|
"Current Values: %s" % str(to_compare[0]),
|
||
|
"Expected Values: %s" % str(to_compare[1]),
|
||
|
]
|
||
|
if errors:
|
||
|
self.fail('\n'.join(errors))
|
||
|
|
||
|
def _filter_folded_lines(self, lines):
|
||
|
""" Children lines returned for folded lines (for example, totals below sections) should be ignored when comparing the results
|
||
|
in assertLinesValues (their parents are folded, so they are not shown anyway). This function returns a filtered version of lines
|
||
|
list, without the chilren of folded lines.
|
||
|
"""
|
||
|
filtered_lines = []
|
||
|
folded_lines = set()
|
||
|
for line in lines:
|
||
|
if line.get('parent_id') in folded_lines:
|
||
|
folded_lines.add(line['id'])
|
||
|
else:
|
||
|
if line.get('unfoldable') and not line.get('unfolded'):
|
||
|
folded_lines.add(line['id'])
|
||
|
filtered_lines.append(line)
|
||
|
return filtered_lines
|
||
|
|
||
|
def _convert_str_to_date(self, ref, val):
|
||
|
if isinstance(ref, date) and isinstance(val, str):
|
||
|
return datetime.strptime(val, '%Y-%m-%d').date()
|
||
|
return val
|
||
|
|
||
|
@classmethod
|
||
|
def _create_tax_report_line(cls, name, report, tag_name=None, parent_line=None, sequence=None, code=None, formula=None):
|
||
|
""" Creates a tax report line
|
||
|
"""
|
||
|
create_vals = {
|
||
|
'name': name,
|
||
|
'code': code,
|
||
|
'report_id': report.id,
|
||
|
'sequence': sequence,
|
||
|
'expression_ids': [],
|
||
|
}
|
||
|
if tag_name and formula:
|
||
|
raise UserError("Can't use this helper to create a line with both tags and formula")
|
||
|
if tag_name:
|
||
|
create_vals['expression_ids'].append(Command.create({
|
||
|
"label": "balance",
|
||
|
"engine": "tax_tags",
|
||
|
"formula": tag_name,
|
||
|
}))
|
||
|
if parent_line:
|
||
|
create_vals['parent_id'] = parent_line.id
|
||
|
if formula:
|
||
|
create_vals['expression_ids'].append(Command.create({
|
||
|
"label": "balance",
|
||
|
"engine": "aggregation",
|
||
|
"formula": formula,
|
||
|
}))
|
||
|
|
||
|
return cls.env['account.report.line'].create(create_vals)
|
||
|
|
||
|
@classmethod
|
||
|
def _get_basic_line_dict_id_from_report_line(cls, report_line):
|
||
|
""" Computes a full generic id for the provided report line (hence including the one of its parent as prefix), using no markup.
|
||
|
"""
|
||
|
report = report_line.report_id
|
||
|
if report_line.parent_id:
|
||
|
parent_line_id = cls._get_basic_line_dict_id_from_report_line(report_line.parent_id)
|
||
|
return report._get_generic_line_id(report_line._name, report_line.id, parent_line_id=parent_line_id)
|
||
|
|
||
|
return report._get_generic_line_id(report_line._name, report_line.id)
|
||
|
|
||
|
@classmethod
|
||
|
def _get_basic_line_dict_id_from_report_line_ref(cls, report_line_xmlid):
|
||
|
""" Same as _get_basic_line_dict_id_from_report_line, but from the line's xmlid, for convenience in the tests.
|
||
|
"""
|
||
|
return cls._get_basic_line_dict_id_from_report_line(cls.env.ref(report_line_xmlid))
|