Initial commit
This commit is contained in:
21
Fusion Accounting/tests/__init__.py
Normal file
21
Fusion Accounting/tests/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from . import common
|
||||
from . import test_account_asset
|
||||
from . import test_account_auto_reconcile_wizard
|
||||
from . import test_account_fiscal_year
|
||||
from . import test_account_reconcile_wizard
|
||||
from . import test_analytic_reports
|
||||
from . import test_bank_rec_widget_common
|
||||
from . import test_bank_rec_widget
|
||||
from . import test_bank_rec_widget_tour
|
||||
from . import test_board_compute
|
||||
from . import test_change_lock_date_wizard
|
||||
from . import test_deferred_management
|
||||
from . import test_financial_report
|
||||
from . import test_import_bank_statement
|
||||
from . import test_prediction
|
||||
from . import test_reconciliation_matching_rules
|
||||
from . import test_reconciliation_widget
|
||||
from . import test_reevaluation_asset
|
||||
from . import test_signature
|
||||
from . import test_tour
|
||||
from . import test_ui
|
||||
429
Fusion Accounting/tests/common.py
Normal file
429
Fusion Accounting/tests/common.py
Normal file
@@ -0,0 +1,429 @@
|
||||
from odoo import fields
|
||||
import copy
|
||||
import io
|
||||
import unittest
|
||||
from collections import Counter
|
||||
from datetime import datetime, date
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
try:
|
||||
from openpyxl import load_workbook
|
||||
except ImportError:
|
||||
load_workbook = None
|
||||
from odoo import Command, fields
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
|
||||
from odoo.tools.misc import formatLang, file_open
|
||||
|
||||
class TestAccountAssetCommon(AccountTestInvoicingCommon):
|
||||
|
||||
@classmethod
|
||||
def create_asset(cls, value, periodicity, periods, degressive_factor=None, import_depreciation=0, **kwargs):
|
||||
if degressive_factor is not None:
|
||||
kwargs["method_progress_factor"] = degressive_factor
|
||||
return cls.env['account.asset'].create({
|
||||
'name': 'nice asset',
|
||||
'account_asset_id': cls.company_data['default_account_assets'].id,
|
||||
'account_depreciation_id': cls.company_data['default_account_assets'].copy().id,
|
||||
'account_depreciation_expense_id': cls.company_data['default_account_expense'].id,
|
||||
'journal_id': cls.company_data['default_journal_misc'].id,
|
||||
'acquisition_date': "2020-02-01",
|
||||
'prorata_computation_type': 'none',
|
||||
'original_value': value,
|
||||
'salvage_value': 0,
|
||||
'method_number': periods,
|
||||
'method_period': '12' if periodicity == "yearly" else '1',
|
||||
'method': "linear",
|
||||
'already_depreciated_amount_import': import_depreciation,
|
||||
**kwargs,
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def _get_depreciation_move_values(cls, date, depreciation_value, remaining_value, depreciated_value, state):
|
||||
return {
|
||||
'date': fields.Date.from_string(date),
|
||||
'depreciation_value': depreciation_value,
|
||||
'asset_remaining_value': remaining_value,
|
||||
'asset_depreciated_value': depreciated_value,
|
||||
'state': state,
|
||||
}
|
||||
|
||||
class TestAccountReportsCommon(AccountTestInvoicingCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.other_currency = cls.setup_other_currency('CAD')
|
||||
cls.company_data_2 = cls.setup_other_company()
|
||||
cls.company_data_2['company'].currency_id = cls.other_currency
|
||||
cls.company_data_2['currency'] = cls.other_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(date_from)
|
||||
else:
|
||||
date_from_str = date_from
|
||||
|
||||
if isinstance(date_to, datetime):
|
||||
date_to_str = fields.Date.to_string(date_to)
|
||||
else:
|
||||
date_to_str = date_to
|
||||
|
||||
if not default_options:
|
||||
default_options = {}
|
||||
|
||||
return report.get_options({
|
||||
'selected_variant_id': report.id,
|
||||
'date': {
|
||||
'date_from': date_from_str,
|
||||
'date_to': date_to_str,
|
||||
'mode': 'range',
|
||||
'filter': 'custom',
|
||||
},
|
||||
'show_account': True,
|
||||
'show_currency': True,
|
||||
**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': {
|
||||
**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
|
||||
|
||||
def assertColumnPercentComparisonValues(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 = 'column_percent_comparison_data'
|
||||
self.assertEqual(len(value[key]) + 1, len(expected_value))
|
||||
# Check name, value and class.
|
||||
self.assertEqual((value['name'], value[key]['name'], value[key]['mode']), expected_value)
|
||||
|
||||
def assertHorizontalGroupTotal(self, lines, expected_values):
|
||||
filtered_lines = self._filter_folded_lines(lines)
|
||||
|
||||
# Check number of lines.
|
||||
self.assertEqual(len(filtered_lines), len(expected_values))
|
||||
for line_dict_list, expected_values in zip(filtered_lines, expected_values):
|
||||
column_values = [column['no_format'] for column in line_dict_list['columns']]
|
||||
# Compare the Total column, Total column is there only under certain condition
|
||||
if line_dict_list.get('horizontal_group_total_data'):
|
||||
self.assertEqual(len(line_dict_list['columns']) + 1, len(expected_values[1:]))
|
||||
# Compare the numbers column except the total
|
||||
self.assertEqual(column_values, list(expected_values[1:-1]))
|
||||
# Compare the total column
|
||||
self.assertEqual(line_dict_list['horizontal_group_total_data']['no_format'], expected_values[-1])
|
||||
else:
|
||||
# No total column
|
||||
self.assertEqual(len(line_dict_list['columns']), len(expected_values[1:]))
|
||||
self.assertEqual(column_values, list(expected_values[1:]))
|
||||
|
||||
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 assertIdenticalLines(self, reports):
|
||||
"""Helper to compare report lines with the same `code` across multiple reports.
|
||||
The helper checks the lines for similarity on:
|
||||
- number of expressions
|
||||
- expression label
|
||||
- expression engine
|
||||
- expression formula
|
||||
- expression subformula
|
||||
- expression date_scope
|
||||
|
||||
:param reports: (recordset of account.report) The reports to check
|
||||
"""
|
||||
def expression_to_comparable_values(expr):
|
||||
return (
|
||||
expr.label,
|
||||
expr.engine,
|
||||
expr.formula,
|
||||
expr.subformula,
|
||||
expr.date_scope
|
||||
)
|
||||
|
||||
if not reports:
|
||||
raise UserError('There are no reports to compare.')
|
||||
visited_line_codes = set()
|
||||
for line in reports.line_ids:
|
||||
if not line.code or line.code in visited_line_codes:
|
||||
continue
|
||||
identical_lines = reports.line_ids.filtered(lambda l: l != line and l.code == line.code)
|
||||
if not identical_lines:
|
||||
continue
|
||||
with self.subTest(line_code=line.code):
|
||||
for tested_line in identical_lines:
|
||||
self.assertCountEqual(
|
||||
line.expression_ids.mapped(expression_to_comparable_values),
|
||||
tested_line.expression_ids.mapped(expression_to_comparable_values),
|
||||
(
|
||||
f'The line with code {line.code} from reports "{line.report_id.name}" and '
|
||||
f'"{tested_line.report_id.name}" has different expression values in both reports.'
|
||||
)
|
||||
)
|
||||
visited_line_codes.add(line.code)
|
||||
|
||||
def assertLinesValues(self, lines, columns, expected_values, options, currency_map=None, ignore_folded=True):
|
||||
''' Helper to compare the lines returned by the _get_lines method
|
||||
with some expected results and ensuring the 'id' key of each line holds a unique value.
|
||||
:param lines: See _get_lines.
|
||||
:param columns: The columns index.
|
||||
:param expected_values: A list of iterables.
|
||||
:param options: The options from the current report.
|
||||
: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', '')
|
||||
current_figure_type = line['columns'][index - 1].get('figure_type', '')
|
||||
|
||||
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:
|
||||
if current_figure_type and current_figure_type != 'monetary':
|
||||
expected_value = str(expected_value)
|
||||
elif options.get('multi_currency'):
|
||||
expected_value = formatLang(self.env, expected_value, currency_obj=used_currency)
|
||||
else:
|
||||
expected_value = formatLang(self.env, expected_value, digits=used_currency.decimal_places)
|
||||
|
||||
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]),
|
||||
]
|
||||
|
||||
id_counts = Counter(line['id'] for line in lines)
|
||||
duplicate_ids = {k: v for k, v in id_counts.items() if v > 1}
|
||||
if duplicate_ids:
|
||||
index_to_id = [
|
||||
f"index={index:<6} name={line.get('name', 'no line name?!')} \tline_id={line.get('id', 'no line id?!')}"
|
||||
for index, line in enumerate(lines)
|
||||
if line.get('id', 'no line id?!') in duplicate_ids
|
||||
]
|
||||
errors += [
|
||||
"\n==== There are lines sharing the same id ====",
|
||||
"\n".join(index_to_id)
|
||||
]
|
||||
|
||||
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_tag_ids(cls, sign, expressions, company=False):
|
||||
""" Helper function to define tag ids for taxes """
|
||||
return [(6, 0, cls.env['account.account.tag'].search([
|
||||
('applicability', '=', 'taxes'),
|
||||
('country_id.code', '=', (company or cls.env.company).account_fiscal_country_id.code),
|
||||
('name', 'in', [f"{sign}{f}" for f in expressions.mapped('formula')]),
|
||||
]).ids)]
|
||||
|
||||
@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))
|
||||
|
||||
@classmethod
|
||||
def _get_audit_params_from_report_line(cls, options, report_line, report_line_dict, **kwargs):
|
||||
return {
|
||||
'report_line_id': report_line.id,
|
||||
'calling_line_dict_id': report_line_dict['id'],
|
||||
'expression_label': 'balance',
|
||||
'column_group_key': next(iter(options['column_groups'])),
|
||||
**kwargs,
|
||||
}
|
||||
|
||||
def _report_compare_with_test_file(self, report, xml_file=None, test_xml=None):
|
||||
report_xml = self.get_xml_tree_from_string(report['file_content'])
|
||||
if xml_file and not test_xml:
|
||||
with file_open(f"{self.test_module}/tests/expected_xmls/{xml_file}", 'rb') as fp:
|
||||
test_xml = fp.read()
|
||||
test_xml_tree = self.get_xml_tree_from_string(test_xml)
|
||||
self.assertXmlTreeEqual(report_xml, test_xml_tree)
|
||||
|
||||
@classmethod
|
||||
def _fill_tax_report_line_external_value(cls, target, amount, date):
|
||||
cls.env['account.report.external.value'].create({
|
||||
'company_id': cls.company_data['company'].id,
|
||||
'target_report_expression_id': cls.env.ref(target).id,
|
||||
'name': 'Manual value',
|
||||
'date': fields.Date.from_string(date),
|
||||
'value': amount,
|
||||
})
|
||||
|
||||
def _test_xlsx_file(self, file_content, expected_values):
|
||||
""" Takes in the binary content of a xlsx file and a dict of expected values.
|
||||
It will then parse the file in order to compare the values with the expected ones.
|
||||
The expected values dict format is:
|
||||
'row_number': ['cell_1_val', 'cell_2_val', ...]
|
||||
|
||||
:param file_content: The binary content of the xlsx file
|
||||
:param expected_values: The dict of expected values
|
||||
"""
|
||||
if load_workbook is None:
|
||||
raise unittest.SkipTest("openpyxl not available")
|
||||
|
||||
report_file = io.BytesIO(file_content)
|
||||
xlsx = load_workbook(filename=report_file, data_only=True)
|
||||
sheet = xlsx.worksheets[0]
|
||||
sheet_values = list(sheet.values)
|
||||
|
||||
for row, values in expected_values.items():
|
||||
row_values = [v if v is not None else '' for v in sheet_values[row]]
|
||||
for row_value, expected_value in zip(row_values, values):
|
||||
self.assertEqual(row_value, expected_value)
|
||||
3037
Fusion Accounting/tests/test_account_asset.py
Normal file
3037
Fusion Accounting/tests/test_account_asset.py
Normal file
File diff suppressed because it is too large
Load Diff
244
Fusion Accounting/tests/test_account_auto_reconcile_wizard.py
Normal file
244
Fusion Accounting/tests/test_account_auto_reconcile_wizard.py
Normal file
@@ -0,0 +1,244 @@
|
||||
from odoo import fields
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestAccountAutoReconcileWizard(AccountTestInvoicingCommon):
|
||||
""" Tests the account automatic reconciliation and its wizard. """
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.comp_curr = cls.company_data['currency']
|
||||
cls.foreign_curr = cls.setup_other_currency('EUR')
|
||||
|
||||
cls.misc_journal = cls.company_data['default_journal_misc']
|
||||
cls.partners = cls.partner_a + cls.partner_b
|
||||
cls.receivable_account = cls.company_data['default_account_receivable']
|
||||
cls.payable_account = cls.company_data['default_account_payable']
|
||||
cls.revenue_account = cls.company_data['default_account_revenue']
|
||||
cls.test_date = fields.Date.from_string('2016-01-01')
|
||||
|
||||
def _create_many_lines(self):
|
||||
self.line_1_group_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.comp_curr, '2016-01-01', partner=self.partner_a)
|
||||
self.line_2_group_1 = self.create_line_for_reconciliation(-1000.0, -1000.0, self.comp_curr, '2016-01-02', partner=self.partner_a)
|
||||
self.line_3_group_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.comp_curr, '2016-01-03', partner=self.partner_a)
|
||||
self.line_4_group_1 = self.create_line_for_reconciliation(-1000.0, -1000.0, self.comp_curr, '2016-01-04', partner=self.partner_a)
|
||||
self.line_5_group_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.comp_curr, '2016-01-05', partner=self.partner_a)
|
||||
self.group_1 = self.line_1_group_1 + self.line_2_group_1 + self.line_3_group_1 + self.line_4_group_1 + self.line_5_group_1
|
||||
|
||||
self.line_1_group_2 = self.create_line_for_reconciliation(500.0, 500.0, self.comp_curr, '2016-01-01', partner=self.partner_b)
|
||||
self.line_2_group_2 = self.create_line_for_reconciliation(-500.0, -500.0, self.comp_curr, '2016-01-01', partner=self.partner_b)
|
||||
self.line_3_group_2 = self.create_line_for_reconciliation(500.0, 500.0, self.comp_curr, '2017-01-02', partner=self.partner_b)
|
||||
self.line_4_group_2 = self.create_line_for_reconciliation(-500.0, -500.0, self.comp_curr, '2017-01-02', partner=self.partner_b)
|
||||
self.group_2 = self.line_1_group_2 + self.line_2_group_2 + self.line_3_group_2 + self.line_4_group_2
|
||||
|
||||
self.line_1_group_3 = self.create_line_for_reconciliation(1500.0, 3000.0, self.foreign_curr, '2016-01-01', partner=self.partner_b)
|
||||
self.line_2_group_3 = self.create_line_for_reconciliation(-1000.0, -3000.0, self.foreign_curr, '2017-01-01', partner=self.partner_b)
|
||||
self.line_3_group_3 = self.create_line_for_reconciliation(3000.0, 3000.0, self.comp_curr, '2016-01-01', partner=self.partner_b)
|
||||
self.line_4_group_3 = self.create_line_for_reconciliation(-3000.0, -3000.0, self.comp_curr, '2016-01-01', partner=self.partner_b)
|
||||
self.group_3 = self.line_1_group_3 + self.line_2_group_3 + self.line_3_group_3 + self.line_4_group_3
|
||||
|
||||
self.line_1_group_4 = self.create_line_for_reconciliation(1000.0, 1000.0, self.comp_curr, '2016-01-01', account_1=self.payable_account, partner=self.partner_a)
|
||||
self.line_2_group_4 = self.create_line_for_reconciliation(-1000.0, -1000.0, self.comp_curr, '2016-01-02', account_1=self.payable_account, partner=self.partner_a)
|
||||
self.group_4 = self.line_1_group_4 + self.line_2_group_4
|
||||
|
||||
def test_auto_reconcile_one_to_one(self):
|
||||
self._create_many_lines()
|
||||
should_be_reconciled = self.line_1_group_1 + self.line_2_group_1 + self.line_3_group_1 + self.line_4_group_1 \
|
||||
+ self.line_1_group_2 + self.line_2_group_2 \
|
||||
+ self.line_1_group_3 + self.line_2_group_3 + self.line_3_group_3 + self.line_4_group_3
|
||||
wizard = self.env['account.auto.reconcile.wizard'].new({
|
||||
'from_date': '2016-01-01',
|
||||
'to_date': '2017-01-01',
|
||||
'account_ids': self.receivable_account.ids,
|
||||
'partner_ids': self.partners.ids,
|
||||
'search_mode': 'one_to_one',
|
||||
})
|
||||
wizard.auto_reconcile()
|
||||
|
||||
self.assertTrue(should_be_reconciled.full_reconcile_id)
|
||||
self.assertEqual(self.line_1_group_1.full_reconcile_id, self.line_2_group_1.full_reconcile_id,
|
||||
"Entries should be reconciled together since they are in the same group and have closer dates.")
|
||||
self.assertEqual(self.line_3_group_1.full_reconcile_id, self.line_4_group_1.full_reconcile_id,
|
||||
"Entries should be reconciled together since they are in the same group and have closer dates.")
|
||||
self.assertEqual(self.line_1_group_2.full_reconcile_id, self.line_1_group_2.full_reconcile_id,
|
||||
"Entries should be reconciled together since they are in the same group and have closer dates.")
|
||||
self.assertEqual(self.line_1_group_3.full_reconcile_id, self.line_2_group_3.full_reconcile_id,
|
||||
"Entries should be reconciled together since they are in the same group and have closer dates.")
|
||||
self.assertEqual(self.line_3_group_3.full_reconcile_id, self.line_4_group_3.full_reconcile_id,
|
||||
"Entries should be reconciled together since they are in the same group and have closer dates.")
|
||||
self.assertNotEqual(self.line_2_group_3.full_reconcile_id, self.line_3_group_3.full_reconcile_id,
|
||||
"Entries should NOT be reconciled together as they are of different currencies.")
|
||||
self.assertFalse(self.line_5_group_1.reconciled,
|
||||
"This entry shouldn't be reconciled since group 1 has an odd number of lines, they can't all be reconciled, and it's the most recent one.")
|
||||
self.assertFalse((self.line_3_group_2 + self.line_4_group_2).full_reconcile_id,
|
||||
"Entries shouldn't be reconciled since it's outside of accepted date range of the wizard.")
|
||||
self.assertFalse((self.line_1_group_4 + self.line_2_group_4).full_reconcile_id,
|
||||
"Entries shouldn't be reconciled since their account is out of the wizard's scope.")
|
||||
|
||||
def test_auto_reconcile_zero_balance(self):
|
||||
self._create_many_lines()
|
||||
should_be_reconciled = self.line_1_group_2 + self.line_2_group_2 + self.group_3
|
||||
wizard = self.env['account.auto.reconcile.wizard'].new({
|
||||
'from_date': '2016-01-01',
|
||||
'to_date': '2017-01-01',
|
||||
'account_ids': self.receivable_account.ids,
|
||||
'partner_ids': self.partners.ids,
|
||||
'search_mode': 'zero_balance',
|
||||
})
|
||||
wizard.auto_reconcile()
|
||||
|
||||
self.assertTrue(should_be_reconciled.full_reconcile_id)
|
||||
self.assertFalse(self.group_1.full_reconcile_id,
|
||||
"Entries shouldn't be reconciled since their total balance is not zero.")
|
||||
self.assertEqual((self.line_1_group_2 + self.line_2_group_2).mapped('matching_number'), [self.line_1_group_2.matching_number] * 2,
|
||||
"Entries should be reconciled together as their total balance is zero.")
|
||||
self.assertEqual((self.line_1_group_3 + self.line_2_group_3).mapped('matching_number'), [self.line_1_group_3.matching_number] * 2,
|
||||
"Entries should be reconciled together as their total balance is zero with the same currency.")
|
||||
self.assertEqual((self.line_3_group_3 + self.line_4_group_3).mapped('matching_number'), [self.line_3_group_3.matching_number] * 2,
|
||||
"Lines 3 and 4 are reconciled but not with two first lines since their currency is different.")
|
||||
self.assertFalse(self.group_4.full_reconcile_id,
|
||||
"Entries shouldn't be reonciled since their account is out of the wizard's scope.")
|
||||
|
||||
def test_nothing_to_auto_reconcile(self):
|
||||
wizard = self.env['account.auto.reconcile.wizard'].new({
|
||||
'from_date': '2016-01-01',
|
||||
'to_date': '2017-01-01',
|
||||
'account_ids': self.receivable_account.ids,
|
||||
'partner_ids': self.partners.ids,
|
||||
'search_mode': 'zero_balance',
|
||||
})
|
||||
with self.assertRaises(UserError):
|
||||
wizard.auto_reconcile()
|
||||
|
||||
def test_auto_reconcile_no_account_nor_partner_one_to_one(self):
|
||||
self.create_line_for_reconciliation(1000.0, 1000.0, self.comp_curr, '2016-01-01', partner=self.partner_a)
|
||||
self.create_line_for_reconciliation(-1000.0, -1000.0, self.comp_curr, '2016-01-02', partner=self.partner_a)
|
||||
wizard = self.env['account.auto.reconcile.wizard'].new({
|
||||
'from_date': '2016-01-01',
|
||||
'to_date': '2017-01-01',
|
||||
})
|
||||
reconciled_amls = wizard._auto_reconcile_one_to_one()
|
||||
self.assertTrue(reconciled_amls.full_reconcile_id)
|
||||
|
||||
def test_auto_reconcile_no_account_nor_partner_zero_balance(self):
|
||||
self.create_line_for_reconciliation(1000.0, 1000.0, self.comp_curr, '2016-01-01', partner=self.partner_a)
|
||||
self.create_line_for_reconciliation(-1000.0, -1000.0, self.comp_curr, '2016-01-02', partner=self.partner_a)
|
||||
wizard = self.env['account.auto.reconcile.wizard'].new({
|
||||
'from_date': '2016-01-01',
|
||||
'to_date': '2017-01-01',
|
||||
})
|
||||
reconciled_amls = wizard._auto_reconcile_zero_balance()
|
||||
self.assertTrue(reconciled_amls.full_reconcile_id)
|
||||
|
||||
def test_auto_reconcile_no_account_one_to_one(self):
|
||||
self.create_line_for_reconciliation(1000.0, 1000.0, self.comp_curr, '2016-01-01', partner=self.partner_a)
|
||||
self.create_line_for_reconciliation(-1000.0, -1000.0, self.comp_curr, '2016-01-02', partner=self.partner_a)
|
||||
wizard = self.env['account.auto.reconcile.wizard'].new({
|
||||
'from_date': '2016-01-01',
|
||||
'to_date': '2017-01-01',
|
||||
'partner_ids': self.partners.ids,
|
||||
})
|
||||
reconciled_amls = wizard._auto_reconcile_one_to_one()
|
||||
self.assertTrue(reconciled_amls.full_reconcile_id)
|
||||
|
||||
def test_auto_reconcile_no_account_zero_balance(self):
|
||||
self.create_line_for_reconciliation(1000.0, 1000.0, self.comp_curr, '2016-01-01', partner=self.partner_a)
|
||||
self.create_line_for_reconciliation(-1000.0, -1000.0, self.comp_curr, '2016-01-02', partner=self.partner_a)
|
||||
wizard = self.env['account.auto.reconcile.wizard'].new({
|
||||
'from_date': '2016-01-01',
|
||||
'to_date': '2017-01-01',
|
||||
'partner_ids': self.partners.ids,
|
||||
})
|
||||
reconciled_amls = wizard._auto_reconcile_zero_balance()
|
||||
self.assertTrue(reconciled_amls.full_reconcile_id)
|
||||
|
||||
def test_auto_reconcile_no_partner_one_to_one(self):
|
||||
self.create_line_for_reconciliation(1000.0, 1000.0, self.comp_curr, '2016-01-01', partner=self.partner_a)
|
||||
self.create_line_for_reconciliation(-1000.0, -1000.0, self.comp_curr, '2016-01-02', partner=self.partner_a)
|
||||
wizard = self.env['account.auto.reconcile.wizard'].new({
|
||||
'from_date': '2016-01-01',
|
||||
'to_date': '2017-01-01',
|
||||
'account_ids': self.receivable_account.ids,
|
||||
})
|
||||
reconciled_amls = wizard._auto_reconcile_one_to_one()
|
||||
self.assertTrue(reconciled_amls.full_reconcile_id)
|
||||
|
||||
def test_auto_reconcile_no_partner_zero_balance(self):
|
||||
self.create_line_for_reconciliation(1000.0, 1000.0, self.comp_curr, '2016-01-01', partner=self.partner_a)
|
||||
self.create_line_for_reconciliation(-1000.0, -1000.0, self.comp_curr, '2016-01-02', partner=self.partner_a)
|
||||
wizard = self.env['account.auto.reconcile.wizard'].new({
|
||||
'from_date': '2016-01-01',
|
||||
'to_date': '2017-01-01',
|
||||
'account_ids': self.receivable_account.ids,
|
||||
})
|
||||
reconciled_amls = wizard._auto_reconcile_zero_balance()
|
||||
self.assertTrue(reconciled_amls.full_reconcile_id)
|
||||
|
||||
def test_auto_reconcile_rounding_one_to_one(self):
|
||||
""" Checks that two lines with different values, currency rounding aside, are reconciled in one-to-one mode. """
|
||||
line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.comp_curr, '2016-01-01', partner=self.partner_a)
|
||||
line_2 = self.create_line_for_reconciliation(-1000.0, -1000.0, self.comp_curr, '2016-01-02', partner=self.partner_a)
|
||||
# Need to manually update the values to bypass ORM
|
||||
self.env.cr.execute(
|
||||
"""
|
||||
UPDATE account_move_line SET amount_residual_currency = 1000.0000001 WHERE id = %(line_1_id)s;
|
||||
UPDATE account_move_line SET amount_residual_currency = -999.999999 WHERE id = %(line_2_id)s;
|
||||
""",
|
||||
{'line_1_id': line_1.id, 'line_2_id': line_2.id}
|
||||
)
|
||||
wizard = self.env['account.auto.reconcile.wizard'].new({
|
||||
'from_date': '2016-01-01',
|
||||
'to_date': '2017-01-01',
|
||||
'account_ids': self.receivable_account.ids,
|
||||
})
|
||||
reconciled_amls = wizard._auto_reconcile_one_to_one()
|
||||
self.assertTrue(reconciled_amls.full_reconcile_id)
|
||||
|
||||
def test_auto_reconcile_rounding_zero_balance(self):
|
||||
""" Checks that two lines with different values, currency rounding aside, are reconciled in zero balance mode. """
|
||||
line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.comp_curr, '2016-01-01', partner=self.partner_a)
|
||||
line_2 = self.create_line_for_reconciliation(-1000.0, -1000.0, self.comp_curr, '2016-01-02', partner=self.partner_a)
|
||||
# Need to manually update the values to bypass ORM
|
||||
self.env.cr.execute(
|
||||
"""
|
||||
UPDATE account_move_line SET amount_residual_currency = 1000.0000001 WHERE id = %(line_1_id)s;
|
||||
UPDATE account_move_line SET amount_residual_currency = -999.999999 WHERE id = %(line_2_id)s;
|
||||
""",
|
||||
{'line_1_id': line_1.id, 'line_2_id': line_2.id}
|
||||
)
|
||||
wizard = self.env['account.auto.reconcile.wizard'].new({
|
||||
'from_date': '2016-01-01',
|
||||
'to_date': '2017-01-01',
|
||||
'account_ids': self.receivable_account.ids,
|
||||
})
|
||||
reconciled_amls = wizard._auto_reconcile_zero_balance()
|
||||
self.assertTrue(reconciled_amls.full_reconcile_id)
|
||||
|
||||
def test_preset_wizard(self):
|
||||
""" Tests that giving lines_ids to wizard presets correctly values. """
|
||||
line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.comp_curr, '2016-01-30', partner=self.partner_a)
|
||||
line_2 = self.create_line_for_reconciliation(-1000.0, -1000.0, self.comp_curr, '2016-01-31', partner=self.partner_a)
|
||||
wizard = self.env['account.auto.reconcile.wizard'].with_context(domain=[('id', 'in', (line_1 + line_2).ids)]).create({})
|
||||
self.assertRecordValues(wizard, [{
|
||||
'account_ids': self.receivable_account.ids,
|
||||
'partner_ids': self.partner_a.ids,
|
||||
'from_date': fields.Date.from_string('2016-01-30'),
|
||||
'to_date': fields.Date.from_string('2016-01-31'),
|
||||
'search_mode': 'zero_balance',
|
||||
}])
|
||||
|
||||
line_3 = self.create_line_for_reconciliation(1000.0, 1000.0, self.comp_curr, '2016-01-31', partner=self.partner_a)
|
||||
line_4 = self.create_line_for_reconciliation(-500.0, -500.0, self.comp_curr, '2016-02-28', partner=None)
|
||||
wizard = self.env['account.auto.reconcile.wizard'].with_context(domain=[('id', 'in', (line_3 + line_4).ids)]).create({})
|
||||
self.assertRecordValues(wizard, [{
|
||||
'account_ids': self.receivable_account.ids,
|
||||
'partner_ids': [],
|
||||
'from_date': fields.Date.from_string('2016-01-31'),
|
||||
'to_date': fields.Date.from_string('2016-02-28'),
|
||||
'search_mode': 'one_to_one',
|
||||
}])
|
||||
128
Fusion Accounting/tests/test_account_fiscal_year.py
Normal file
128
Fusion Accounting/tests/test_account_fiscal_year.py
Normal file
@@ -0,0 +1,128 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
from odoo.tests import tagged
|
||||
from odoo import fields
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestFiscalPosition(AccountTestInvoicingCommon):
|
||||
|
||||
def check_compute_fiscal_year(self, company, date, expected_date_from, expected_date_to):
|
||||
'''Compute the fiscal year at a certain date for the company passed as parameter.
|
||||
Then, check if the result matches the 'expected_date_from'/'expected_date_to' dates.
|
||||
|
||||
:param company: The company.
|
||||
:param date: The date belonging to the fiscal year.
|
||||
:param expected_date_from: The expected date_from after computation.
|
||||
:param expected_date_to: The expected date_to after computation.
|
||||
'''
|
||||
current_date = fields.Date.from_string(date)
|
||||
res = company.compute_fiscalyear_dates(current_date)
|
||||
self.assertEqual(res['date_from'], fields.Date.from_string(expected_date_from))
|
||||
self.assertEqual(res['date_to'], fields.Date.from_string(expected_date_to))
|
||||
|
||||
def test_default_fiscal_year(self):
|
||||
'''Basic case with a fiscal year xxxx-01-01 - xxxx-12-31.'''
|
||||
company = self.env.company
|
||||
company.fiscalyear_last_day = 31
|
||||
company.fiscalyear_last_month = '12'
|
||||
|
||||
self.check_compute_fiscal_year(
|
||||
company,
|
||||
'2017-12-31',
|
||||
'2017-01-01',
|
||||
'2017-12-31',
|
||||
)
|
||||
|
||||
self.check_compute_fiscal_year(
|
||||
company,
|
||||
'2017-01-01',
|
||||
'2017-01-01',
|
||||
'2017-12-31',
|
||||
)
|
||||
|
||||
def test_leap_fiscal_year_1(self):
|
||||
'''Case with a leap year ending the 29 February.'''
|
||||
company = self.env.company
|
||||
company.fiscalyear_last_day = 29
|
||||
company.fiscalyear_last_month = '2'
|
||||
|
||||
self.check_compute_fiscal_year(
|
||||
company,
|
||||
'2016-02-29',
|
||||
'2015-03-01',
|
||||
'2016-02-29',
|
||||
)
|
||||
|
||||
self.check_compute_fiscal_year(
|
||||
company,
|
||||
'2015-03-01',
|
||||
'2015-03-01',
|
||||
'2016-02-29',
|
||||
)
|
||||
|
||||
def test_leap_fiscal_year_2(self):
|
||||
'''Case with a leap year ending the 28 February.'''
|
||||
company = self.env.company
|
||||
company.fiscalyear_last_day = 28
|
||||
company.fiscalyear_last_month = '2'
|
||||
|
||||
self.check_compute_fiscal_year(
|
||||
company,
|
||||
'2016-02-29',
|
||||
'2015-03-01',
|
||||
'2016-02-29',
|
||||
)
|
||||
|
||||
self.check_compute_fiscal_year(
|
||||
company,
|
||||
'2016-03-01',
|
||||
'2016-03-01',
|
||||
'2017-02-28',
|
||||
)
|
||||
|
||||
def test_custom_fiscal_year(self):
|
||||
'''Case with custom fiscal years.'''
|
||||
company = self.env.company
|
||||
company.fiscalyear_last_day = 31
|
||||
company.fiscalyear_last_month = '12'
|
||||
|
||||
# Create custom fiscal year covering the 6 first months of 2017.
|
||||
self.env['account.fiscal.year'].create({
|
||||
'name': '6 month 2017',
|
||||
'date_from': '2017-01-01',
|
||||
'date_to': '2017-05-31',
|
||||
'company_id': company.id,
|
||||
})
|
||||
|
||||
# Check before the custom fiscal year).
|
||||
self.check_compute_fiscal_year(
|
||||
company,
|
||||
'2017-02-01',
|
||||
'2017-01-01',
|
||||
'2017-05-31',
|
||||
)
|
||||
|
||||
# Check after the custom fiscal year.
|
||||
self.check_compute_fiscal_year(
|
||||
company,
|
||||
'2017-11-01',
|
||||
'2017-06-01',
|
||||
'2017-12-31',
|
||||
)
|
||||
|
||||
# Create custom fiscal year covering the 3 last months of 2017.
|
||||
self.env['account.fiscal.year'].create({
|
||||
'name': 'last 3 month 2017',
|
||||
'date_from': '2017-10-01',
|
||||
'date_to': '2017-12-31',
|
||||
'company_id': company.id,
|
||||
})
|
||||
|
||||
# Check inside the custom fiscal years.
|
||||
self.check_compute_fiscal_year(
|
||||
company,
|
||||
'2017-07-01',
|
||||
'2017-06-01',
|
||||
'2017-09-30',
|
||||
)
|
||||
765
Fusion Accounting/tests/test_account_reconcile_wizard.py
Normal file
765
Fusion Accounting/tests/test_account_reconcile_wizard.py
Normal file
@@ -0,0 +1,765 @@
|
||||
import re
|
||||
|
||||
from odoo import Command, fields
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestAccountReconcileWizard(AccountTestInvoicingCommon):
|
||||
""" Tests the account reconciliation and its wizard. """
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.receivable_account = cls.company_data['default_account_receivable']
|
||||
cls.payable_account = cls.company_data['default_account_payable']
|
||||
cls.revenue_account = cls.company_data['default_account_revenue']
|
||||
cls.payable_account_2 = cls.env['account.account'].create({
|
||||
'name': 'Payable Account 2',
|
||||
'account_type': 'liability_current',
|
||||
'code': 'PAY2.TEST',
|
||||
'reconcile': True
|
||||
})
|
||||
cls.write_off_account = cls.env['account.account'].create({
|
||||
'name': 'Write-Off Account',
|
||||
'account_type': 'liability_current',
|
||||
'code': 'WO.TEST',
|
||||
'reconcile': False
|
||||
})
|
||||
|
||||
cls.misc_journal = cls.company_data['default_journal_misc']
|
||||
cls.test_date = fields.Date.from_string('2016-01-01')
|
||||
cls.company_currency = cls.company_data['currency']
|
||||
cls.foreign_currency = cls.setup_other_currency('EUR')
|
||||
cls.foreign_currency_2 = cls.setup_other_currency('XAF', rates=[('2016-01-01', 6.0), ('2017-01-01', 4.0)])
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# HELPERS
|
||||
# -------------------------------------------------------------------------
|
||||
def assertWizardReconcileValues(self, selected_lines, input_values, wo_expected_values, expected_transfer_values=None):
|
||||
wizard = self.env['account.reconcile.wizard'].with_context(
|
||||
active_model='account.move.line',
|
||||
active_ids=selected_lines.ids,
|
||||
).new(input_values)
|
||||
if expected_transfer_values:
|
||||
transfer_move = wizard.create_transfer()
|
||||
# transfer move values
|
||||
self.assertRecordValues(transfer_move.line_ids.sorted('balance'), expected_transfer_values)
|
||||
# transfer warning message
|
||||
self.assertTrue(wizard.transfer_warning_message)
|
||||
regex_match = re.findall(r'([+-]*\d*,*\d+\.*\d+)', wizard.transfer_warning_message)
|
||||
# match transferred amount
|
||||
self.assertEqual(
|
||||
float(regex_match[0].replace(',', '')),
|
||||
transfer_move.amount_total_in_currency_signed or transfer_move.amount_total_signed
|
||||
)
|
||||
transfer_from_account = transfer_move.line_ids.filtered(lambda aml: 'Transfer from' in aml.name).account_id
|
||||
transfer_to_account = transfer_move.line_ids.account_id - transfer_from_account
|
||||
transfer_from_amls = transfer_move.line_ids.filtered(lambda aml: aml.account_id == transfer_from_account)
|
||||
transfer_amount = sum(aml.balance for aml in transfer_from_amls)
|
||||
# match account codes
|
||||
if transfer_amount > 0:
|
||||
self.assertEqual(regex_match[1:], [transfer_from_account.code, transfer_to_account.code])
|
||||
else:
|
||||
self.assertEqual(regex_match[1:], [transfer_to_account.code, transfer_from_account.code])
|
||||
write_off_move = wizard.create_write_off()
|
||||
self.assertRecordValues(write_off_move.line_ids.sorted('balance'), wo_expected_values)
|
||||
wizard.reconcile()
|
||||
if wizard.allow_partials or (
|
||||
wizard.edit_mode
|
||||
and wizard.reco_currency_id.compare_amounts(wizard.edit_mode_amount_currency, wizard.amount_currency)
|
||||
):
|
||||
# partial reconcile
|
||||
self.assertTrue(len(selected_lines.matched_debit_ids) > 0 or len(selected_lines.matched_credit_ids) > 0)
|
||||
else:
|
||||
# full reconcile
|
||||
self.assertTrue(selected_lines.full_reconcile_id)
|
||||
self.assertRecordValues(
|
||||
selected_lines,
|
||||
[{'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}] * len(selected_lines),
|
||||
)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# TESTS
|
||||
# -------------------------------------------------------------------------
|
||||
def test_wizard_should_not_open(self):
|
||||
""" Test that when we reconcile two lines that belong to the same account and have a 0 balance should
|
||||
reconcile silently and not open the write-off wizard.
|
||||
"""
|
||||
line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01')
|
||||
line_2 = self.create_line_for_reconciliation(-1000.0, -1000.0, self.company_currency, '2016-01-01')
|
||||
(line_1 + line_2).action_reconcile()
|
||||
self.assertRecordValues(
|
||||
line_1 + line_2,
|
||||
[{'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}] * 2
|
||||
)
|
||||
|
||||
def test_wizard_should_open(self):
|
||||
""" Test that when a write-off is required (because of transfer or non-zero balance) the wizard opens. """
|
||||
line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01')
|
||||
line_2 = self.create_line_for_reconciliation(-500.0, -500.0, self.company_currency, '2016-01-01')
|
||||
line_3 = self.create_line_for_reconciliation(-500.0, -1500.0, self.foreign_currency, '2016-01-01')
|
||||
line_4 = self.create_line_for_reconciliation(-900.0, -900.0, self.company_currency, '2016-01-01', account_1=self.payable_account)
|
||||
for batch, sub_test_name in (
|
||||
(line_1 + line_2, 'Batch with non-zero balance in company currency'),
|
||||
(line_1 + line_3, 'Batch with non-zero balance in foreign currency'),
|
||||
(line_1 + line_4, 'Batch with different accounts'),
|
||||
):
|
||||
with self.subTest(sub_test_name=sub_test_name):
|
||||
returned_action = batch.action_reconcile()
|
||||
self.assertEqual(returned_action.get('res_model'), 'account.reconcile.wizard')
|
||||
|
||||
def test_reconcile_silently_same_account(self):
|
||||
""" When balance is 0 we can silently reconcile items. """
|
||||
line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01')
|
||||
line_2 = self.create_line_for_reconciliation(-1000.0, -1000.0, self.company_currency, '2016-01-01')
|
||||
lines = (line_1 + line_2)
|
||||
lines.action_reconcile()
|
||||
self.assertTrue(lines.full_reconcile_id)
|
||||
self.assertRecordValues(
|
||||
lines,
|
||||
[{'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}] * len(lines),
|
||||
)
|
||||
|
||||
def test_reconcile_silently_transfer(self):
|
||||
""" When balance is 0, and we need a transfer, we do the transfer+reconcile silently. """
|
||||
line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01')
|
||||
line_2 = self.create_line_for_reconciliation(-1000.0, -1000.0, self.company_currency, '2016-01-01', account_1=self.payable_account)
|
||||
lines = (line_1 + line_2)
|
||||
lines.action_reconcile()
|
||||
self.assertTrue(lines.full_reconcile_id)
|
||||
self.assertRecordValues(
|
||||
lines,
|
||||
[{'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}] * len(lines),
|
||||
)
|
||||
|
||||
def test_write_off_same_currency(self):
|
||||
""" Reconciliation of two lines with no transfer/foreign currencies/taxes/reco models."""
|
||||
line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01')
|
||||
line_2 = self.create_line_for_reconciliation(-500.0, -500.0, self.company_currency, '2016-01-01')
|
||||
wizard_input_values = {
|
||||
'journal_id': self.misc_journal.id,
|
||||
'account_id': self.write_off_account.id,
|
||||
'label': 'Write-Off Test Label',
|
||||
'allow_partials': False,
|
||||
'date': self.test_date,
|
||||
}
|
||||
write_off_expected_values = [
|
||||
{'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label', 'balance': -500.0},
|
||||
{'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label', 'balance': 500.0},
|
||||
]
|
||||
self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, write_off_expected_values)
|
||||
|
||||
def test_write_off_one_foreign_currency(self):
|
||||
""" Reconciliation of two lines with one of the two using foreign currency should reconcile in foreign currency."""
|
||||
line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01')
|
||||
line_2 = self.create_line_for_reconciliation(-500.0, -1500.0, self.foreign_currency, '2016-01-01')
|
||||
wizard_input_values = {
|
||||
'journal_id': self.misc_journal.id,
|
||||
'account_id': self.write_off_account.id,
|
||||
'label': 'Write-Off Test Label',
|
||||
'allow_partials': False,
|
||||
'date': self.test_date,
|
||||
}
|
||||
expected_values = [
|
||||
{'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': -500.0, 'amount_currency': -1500.0, 'currency_id': self.foreign_currency.id},
|
||||
{'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': 500.0, 'amount_currency': 1500.0, 'currency_id': self.foreign_currency.id},
|
||||
]
|
||||
self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, expected_values)
|
||||
|
||||
def test_write_off_mixed_foreign_currencies(self):
|
||||
""" Write off with multiple currencies should reconcile in company currency."""
|
||||
line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01')
|
||||
line_2 = self.create_line_for_reconciliation(-500.0, -1500.0, self.foreign_currency, '2016-01-01')
|
||||
line_3 = self.create_line_for_reconciliation(-400.0, -2400.0, self.foreign_currency_2, '2016-01-01')
|
||||
wizard_input_values = {
|
||||
'journal_id': self.misc_journal.id,
|
||||
'account_id': self.write_off_account.id,
|
||||
'label': 'Write-Off Test Label',
|
||||
'allow_partials': False,
|
||||
'date': self.test_date,
|
||||
}
|
||||
expected_values = [
|
||||
{'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': -100.0, 'amount_currency': -100.0, 'currency_id': self.company_currency.id},
|
||||
{'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': 100.0, 'amount_currency': 100.0, 'currency_id': self.company_currency.id},
|
||||
]
|
||||
self.assertWizardReconcileValues(line_1 + line_2 + line_3, wizard_input_values, expected_values)
|
||||
|
||||
def test_write_off_one_foreign_currency_change_rate(self):
|
||||
""" Tests that write-off use the correct rate from/at wizard's date. """
|
||||
foreign_currency = self.setup_other_currency('CAD', rounding=0.001, rates=[('2016-01-01', 0.5), ('2017-01-01', 1 / 3)])
|
||||
new_date = fields.Date.from_string('2017-02-01')
|
||||
line_1 = self.create_line_for_reconciliation(-2000.0, -2000.0, self.company_currency, '2017-01-01') # conversion in 2017 => -666.67🍫
|
||||
line_2 = self.create_line_for_reconciliation(2000.0, 1000.0, foreign_currency, '2016-01-01')
|
||||
wizard_input_values = {
|
||||
'journal_id': self.misc_journal.id,
|
||||
'account_id': self.write_off_account.id,
|
||||
'label': 'Write-Off Test Label',
|
||||
'allow_partials': False,
|
||||
'date': new_date,
|
||||
}
|
||||
expected_values = [
|
||||
{'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': -1000.0, 'amount_currency': -333.333, 'currency_id': foreign_currency.id},
|
||||
{'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': 1000.0, 'amount_currency': 333.333, 'currency_id': foreign_currency.id},
|
||||
]
|
||||
self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, expected_values)
|
||||
|
||||
def test_write_off_mixed_foreign_currencies_change_rate(self):
|
||||
""" Tests that write-off use the correct rate from/at wizard's date. """
|
||||
new_date = fields.Date.from_string('2017-02-01')
|
||||
line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01')
|
||||
line_2 = self.create_line_for_reconciliation(-500.0, -1500.0, self.foreign_currency, '2016-01-01')
|
||||
line_3 = self.create_line_for_reconciliation(-400.0, -2400.0, self.foreign_currency_2, '2016-01-01')
|
||||
wizard_input_values = {
|
||||
'journal_id': self.misc_journal.id,
|
||||
'account_id': self.write_off_account.id,
|
||||
'label': 'Write-Off Test Label',
|
||||
'allow_partials': False,
|
||||
'date': new_date,
|
||||
}
|
||||
expected_values = [
|
||||
{'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': -100.0, 'amount_currency': -100.0, 'currency_id': self.company_currency.id},
|
||||
{'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': 100.0, 'amount_currency': 100.0, 'currency_id': self.company_currency.id},
|
||||
]
|
||||
self.assertWizardReconcileValues(line_1 + line_2 + line_3, wizard_input_values, expected_values)
|
||||
|
||||
def test_write_off_both_same_foreign_currency_ensure_no_exchange_diff(self):
|
||||
""" Test that if both AMLs have the same foreign currency and rate, the amount in company currency
|
||||
is computed on the write-off in such a way that no exchange diff is created.
|
||||
"""
|
||||
foreign_currency = self.setup_other_currency('CAD', rounding=0.01, rates=[('2016-01-01', 1 / 0.225)])
|
||||
new_date = fields.Date.from_string('2017-02-01')
|
||||
line_1 = self.create_line_for_reconciliation(21.38, 95.0, foreign_currency, '2016-01-01')
|
||||
line_2 = self.create_line_for_reconciliation(1.13, 5.0, foreign_currency, '2016-01-01')
|
||||
line_3 = self.create_line_for_reconciliation(1.13, 5.0, foreign_currency, '2016-01-01')
|
||||
wizard_input_values = {
|
||||
'journal_id': self.misc_journal.id,
|
||||
'account_id': self.write_off_account.id,
|
||||
'label': 'Write-Off Test Label',
|
||||
'allow_partials': False,
|
||||
'date': new_date,
|
||||
}
|
||||
expected_values = [
|
||||
{'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': -23.64, 'amount_currency': -105.0, 'currency_id': foreign_currency.id},
|
||||
{'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': 23.64, 'amount_currency': 105.0, 'currency_id': foreign_currency.id},
|
||||
]
|
||||
self.assertWizardReconcileValues(line_1 + line_2 + line_3, wizard_input_values, expected_values)
|
||||
|
||||
def test_write_off_with_transfer_account_same_currency(self):
|
||||
line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01')
|
||||
line_2 = self.create_line_for_reconciliation(100.0, 100.0, self.company_currency, '2016-01-01', account_1=self.payable_account)
|
||||
wizard_input_values = {
|
||||
'journal_id': self.misc_journal.id,
|
||||
'account_id': self.write_off_account.id,
|
||||
'label': 'Write-Off Test Label',
|
||||
'allow_partials': False,
|
||||
'date': self.test_date,
|
||||
}
|
||||
expected_transfer_values = [
|
||||
{'account_id': self.payable_account.id, 'name': f'Transfer to {self.receivable_account.display_name}',
|
||||
'balance': -100.0, 'amount_currency': -100.0, 'currency_id': self.company_currency.id},
|
||||
{'account_id': self.receivable_account.id, 'name': f'Transfer from {self.payable_account.display_name}',
|
||||
'balance': 100.0, 'amount_currency': 100.0, 'currency_id': self.company_currency.id},
|
||||
]
|
||||
expected_values = [
|
||||
{'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': -1100.0, 'amount_currency': -1100.0, 'currency_id': self.company_currency.id},
|
||||
{'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': 1100.0, 'amount_currency': 1100.0, 'currency_id': self.company_currency.id},
|
||||
]
|
||||
self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, expected_values, expected_transfer_values=expected_transfer_values)
|
||||
|
||||
def test_write_off_with_transfer_account_one_foreign_currency(self):
|
||||
line_1 = self.create_line_for_reconciliation(1100.0, 1100.0, self.company_currency, '2016-01-01')
|
||||
line_2 = self.create_line_for_reconciliation(100.0, 300.0, self.foreign_currency, '2016-01-01', account_1=self.payable_account)
|
||||
wizard_input_values = {
|
||||
'journal_id': self.misc_journal.id,
|
||||
'account_id': self.write_off_account.id,
|
||||
'label': 'Write-Off Test Label',
|
||||
'allow_partials': False,
|
||||
'date': self.test_date,
|
||||
}
|
||||
expected_transfer_values = [
|
||||
{'account_id': self.payable_account.id, 'name': f'Transfer to {self.receivable_account.display_name}',
|
||||
'balance': -100.0, 'amount_currency': -300.0, 'currency_id': self.foreign_currency.id},
|
||||
{'account_id': self.receivable_account.id, 'name': f'Transfer from {self.payable_account.display_name}',
|
||||
'balance': 100.0, 'amount_currency': 300.0, 'currency_id': self.foreign_currency.id},
|
||||
]
|
||||
expected_values = [
|
||||
{'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': -1200.0, 'amount_currency': -3600.0, 'currency_id': self.foreign_currency.id},
|
||||
{'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': 1200.0, 'amount_currency': 3600.0, 'currency_id': self.foreign_currency.id},
|
||||
]
|
||||
self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, expected_values, expected_transfer_values=expected_transfer_values)
|
||||
|
||||
def test_write_off_with_complex_transfer(self):
|
||||
partner_1 = self.env['res.partner'].create({'name': 'Test Partner 1'})
|
||||
partner_2 = self.env['res.partner'].create({'name': 'Test Partner 2'})
|
||||
line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01', partner=partner_2)
|
||||
line_2 = self.create_line_for_reconciliation(-100.0, -300.0, self.foreign_currency, '2016-01-01', account_1=self.payable_account, partner=partner_1)
|
||||
line_3 = self.create_line_for_reconciliation(-200.0, -200.0, self.company_currency, '2016-01-01', account_1=self.payable_account, partner=partner_2)
|
||||
line_4 = self.create_line_for_reconciliation(-200.0, -600.0, self.foreign_currency, '2016-01-01', account_1=self.payable_account, partner=partner_2)
|
||||
line_5 = self.create_line_for_reconciliation(-200.0, -600.0, self.foreign_currency, '2016-01-01', account_1=self.payable_account, partner=partner_2)
|
||||
wizard_input_values = {
|
||||
'journal_id': self.misc_journal.id,
|
||||
'account_id': self.write_off_account.id,
|
||||
'label': 'Write-Off Test Label',
|
||||
'allow_partials': False,
|
||||
'date': self.test_date,
|
||||
}
|
||||
expected_transfer_values = [
|
||||
{'account_id': self.receivable_account.id, 'name': f'Transfer from {self.payable_account.display_name}',
|
||||
'balance': -400.0, 'amount_currency': -1200.0, 'currency_id': self.foreign_currency.id, 'partner_id': partner_2.id},
|
||||
{'account_id': self.receivable_account.id, 'name': f'Transfer from {self.payable_account.display_name}',
|
||||
'balance': -200.0, 'amount_currency': -200.0, 'currency_id': self.company_currency.id, 'partner_id': partner_2.id},
|
||||
{'account_id': self.receivable_account.id, 'name': f'Transfer from {self.payable_account.display_name}',
|
||||
'balance': -100.0, 'amount_currency': -300.0, 'currency_id': self.foreign_currency.id, 'partner_id': partner_1.id},
|
||||
{'account_id': self.payable_account.id, 'name': f'Transfer to {self.receivable_account.display_name}',
|
||||
'balance': 100.0, 'amount_currency': 300.0, 'currency_id': self.foreign_currency.id, 'partner_id': partner_1.id},
|
||||
{'account_id': self.payable_account.id, 'name': f'Transfer to {self.receivable_account.display_name}',
|
||||
'balance': 200.0, 'amount_currency': 200.0, 'currency_id': self.company_currency.id, 'partner_id': partner_2.id},
|
||||
{'account_id': self.payable_account.id, 'name': f'Transfer to {self.receivable_account.display_name}',
|
||||
'balance': 400.0, 'amount_currency': 1200.0, 'currency_id': self.foreign_currency.id, 'partner_id': partner_2.id},
|
||||
]
|
||||
expected_values = [
|
||||
{'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': -300.0, 'amount_currency': -900.0, 'currency_id': self.foreign_currency.id},
|
||||
{'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': 300.0, 'amount_currency': 900.0, 'currency_id': self.foreign_currency.id},
|
||||
]
|
||||
self.assertWizardReconcileValues(line_1 + line_2 + line_3 + line_4 + line_5, wizard_input_values, expected_values, expected_transfer_values=expected_transfer_values)
|
||||
|
||||
def test_write_off_with_tax(self):
|
||||
""" Tests write-off with a tax set on the wizard. """
|
||||
line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01')
|
||||
line_2 = self.create_line_for_reconciliation(-500.0, -500.0, self.company_currency, '2016-01-01')
|
||||
tax_recover_account_id = self.env['account.account'].create({
|
||||
'name': 'Tax Account Test',
|
||||
'account_type': 'liability_current',
|
||||
'code': 'TAX.TEST',
|
||||
'reconcile': False
|
||||
})
|
||||
base_tag = self.env['account.account.tag'].create({
|
||||
'applicability': 'taxes',
|
||||
'name': 'base_tax_tag',
|
||||
'country_id': self.company_data['company'].country_id.id,
|
||||
})
|
||||
tax_tag = self.env['account.account.tag'].create({
|
||||
'applicability': 'taxes',
|
||||
'name': 'tax_tax_tag',
|
||||
'country_id': self.company_data['company'].country_id.id,
|
||||
})
|
||||
tax_id = self.env['account.tax'].create({
|
||||
'name': 'tax_test',
|
||||
'amount_type': 'percent',
|
||||
'amount': 25.0,
|
||||
'type_tax_use': 'sale',
|
||||
'company_id': self.company_data['company'].id,
|
||||
'invoice_repartition_line_ids': [
|
||||
Command.create({'factor_percent': 100, 'repartition_type': 'base', 'tag_ids': [Command.set(base_tag.ids)]}),
|
||||
Command.create({'factor_percent': 100, 'account_id': tax_recover_account_id.id, 'tag_ids': [Command.set(tax_tag.ids)]}),
|
||||
],
|
||||
'refund_repartition_line_ids': [
|
||||
Command.create({'factor_percent': 100, 'repartition_type': 'base', 'tag_ids': [Command.set(base_tag.ids)]}),
|
||||
Command.create({'factor_percent': 100, 'account_id': tax_recover_account_id.id, 'tag_ids': [Command.set(tax_tag.ids)]}),
|
||||
],
|
||||
})
|
||||
wizard_input_values = {
|
||||
'journal_id': self.misc_journal.id,
|
||||
'account_id': self.write_off_account.id,
|
||||
'label': 'Write-Off Test Label',
|
||||
'tax_id': tax_id.id,
|
||||
'allow_partials': False,
|
||||
'date': self.test_date,
|
||||
}
|
||||
write_off_expected_values = [
|
||||
{'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label', 'balance': -500.0},
|
||||
{'account_id': tax_recover_account_id.id, 'name': f'{tax_id.name}', 'balance': 100.0},
|
||||
{'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label', 'balance': 400.0},
|
||||
]
|
||||
self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, write_off_expected_values)
|
||||
|
||||
def test_reconcile_partials_allowed(self):
|
||||
line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01')
|
||||
line_2 = self.create_line_for_reconciliation(-500.0, -500.0, self.company_currency, '2016-01-01')
|
||||
lines = line_1 + line_2
|
||||
wizard_input_values = {
|
||||
'allow_partials': True,
|
||||
}
|
||||
wizard = self.env['account.reconcile.wizard'].with_context(
|
||||
active_model='account.move.line',
|
||||
active_ids=lines.ids,
|
||||
).new(wizard_input_values)
|
||||
wizard.reconcile()
|
||||
self.assertTrue(len(lines.matched_debit_ids) > 0 or len(lines.matched_credit_ids) > 0)
|
||||
|
||||
def test_raise_lock_date_violation(self):
|
||||
""" If a write-off violates the lock date we display a banner and change the date afterwards. """
|
||||
company_id = self.company_data['company']
|
||||
company_id.fiscalyear_lock_date = fields.Date.from_string('2016-12-01')
|
||||
line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-06-01')
|
||||
line_2 = self.create_line_for_reconciliation(-500.0, -500.0, self.company_currency, '2016-06-01')
|
||||
wizard = self.env['account.reconcile.wizard'].with_context(
|
||||
active_model='account.move.line',
|
||||
active_ids=(line_1 + line_2).ids,
|
||||
).new({'date': self.test_date})
|
||||
self.assertTrue(bool(wizard.lock_date_violated_warning_message))
|
||||
|
||||
def test_raise_reconcile_too_many_accounts(self):
|
||||
""" If you try to reconcile lines from more than 2 accounts, it should raise an error. """
|
||||
line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01')
|
||||
line_2 = self.create_line_for_reconciliation(-500.0, -500.0, self.company_currency, '2016-01-01', account_1=self.payable_account)
|
||||
line_3 = self.create_line_for_reconciliation(-500.0, -500.0, self.company_currency, '2016-01-01', account_1=self.payable_account_2)
|
||||
with self.assertRaises(UserError):
|
||||
(line_1 + line_2 + line_3).action_reconcile()
|
||||
|
||||
def test_reconcile_no_receivable_no_payable_account(self):
|
||||
""" If you try to reconcile lines in an account that is neither from payable nor receivable
|
||||
it should reconcile in company currency.
|
||||
"""
|
||||
account = self.company_data['default_account_expense']
|
||||
account.reconcile = True
|
||||
line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01', account_1=account)
|
||||
line_2 = self.create_line_for_reconciliation(-500.0, -1500.0, self.foreign_currency, '2016-01-01', account_1=account)
|
||||
wizard_input_values = {
|
||||
'journal_id': self.misc_journal.id,
|
||||
'account_id': self.write_off_account.id,
|
||||
'label': 'Write-Off Test Label',
|
||||
'allow_partials': False,
|
||||
'date': self.test_date,
|
||||
}
|
||||
expected_values = [
|
||||
{'account_id': account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': -500.0, 'amount_currency': -500.0, 'currency_id': self.company_currency.id},
|
||||
{'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': 500.0, 'amount_currency': 500.0, 'currency_id': self.company_currency.id},
|
||||
]
|
||||
self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, expected_values)
|
||||
|
||||
def test_reconcile_exchange_diff_foreign_currency(self):
|
||||
""" When reconciling exchange_diff with amount_residual_currency = 0 we need to reconcile in company_currency.
|
||||
"""
|
||||
exchange_gain_account = self.company_data['company'].income_currency_exchange_account_id
|
||||
exchange_gain_account.reconcile = True
|
||||
line_1 = self.create_line_for_reconciliation(150.0, 0.0, self.foreign_currency, '2016-01-01')
|
||||
line_2 = self.create_line_for_reconciliation(-100.0, 0.0, self.foreign_currency, '2016-01-01', account_1=exchange_gain_account)
|
||||
wizard_input_values = {
|
||||
'journal_id': self.misc_journal.id,
|
||||
'account_id': self.write_off_account.id,
|
||||
'label': 'Write-Off Test Label',
|
||||
'allow_partials': False,
|
||||
'date': self.test_date,
|
||||
}
|
||||
# Note the transfer will always be in the currency of the line transferred
|
||||
expected_transfer_values = [
|
||||
{'account_id': self.receivable_account.id, 'name': f'Transfer from {exchange_gain_account.display_name}',
|
||||
'balance': -100.0, 'amount_currency': 0.0, 'currency_id': self.foreign_currency.id},
|
||||
{'account_id': exchange_gain_account.id, 'name': f'Transfer to {self.receivable_account.display_name}',
|
||||
'balance': 100.0, 'amount_currency': 0.0, 'currency_id': self.foreign_currency.id},
|
||||
]
|
||||
expected_values = [
|
||||
{'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': -50.0, 'amount_currency': -50.0, 'currency_id': self.company_currency.id},
|
||||
{'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': 50.0, 'amount_currency': 50.0, 'currency_id': self.company_currency.id},
|
||||
]
|
||||
self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, expected_values, expected_transfer_values=expected_transfer_values)
|
||||
|
||||
def test_write_off_on_same_account(self):
|
||||
""" When creating a write-off in the same account than the one used by the lines to reconcile,
|
||||
the lines and the write-off should be fully reconciled.
|
||||
"""
|
||||
line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01')
|
||||
line_2 = self.create_line_for_reconciliation(2000.0, 2000.0, self.company_currency, '2016-01-01')
|
||||
wizard_input_values = {
|
||||
'journal_id': self.misc_journal.id,
|
||||
'account_id': self.receivable_account.id,
|
||||
'label': 'Write-Off Test Label',
|
||||
'allow_partials': False,
|
||||
'date': self.test_date,
|
||||
}
|
||||
write_off_expected_values = [
|
||||
{'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label', 'balance': -3000.0},
|
||||
{'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label', 'balance': 3000.0},
|
||||
]
|
||||
self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, write_off_expected_values)
|
||||
|
||||
def test_reconcile_exchange_diff_foreign_currency_full(self):
|
||||
""" When reconciling exchange_diff with amount_residual_currency = 0 we need to reconcile in company_currency.
|
||||
"""
|
||||
exchange_gain_account = self.company_data['company'].income_currency_exchange_account_id
|
||||
exchange_gain_account.reconcile = True
|
||||
line_1 = self.create_line_for_reconciliation(100.0, 0.0, self.foreign_currency, '2016-01-01')
|
||||
line_2 = self.create_line_for_reconciliation(-100.0, 0.0, self.foreign_currency, '2016-01-01', account_1=exchange_gain_account)
|
||||
lines = line_1 + line_2
|
||||
lines.action_reconcile()
|
||||
self.assertTrue(lines.full_reconcile_id)
|
||||
self.assertRecordValues(
|
||||
lines,
|
||||
[{'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}] * len(lines),
|
||||
)
|
||||
|
||||
def test_write_off_kpmg_case(self):
|
||||
""" Test that write-off does a full reconcile with 2 foreign currencies using a custom exchange rate. """
|
||||
new_date = fields.Date.from_string('2017-02-01')
|
||||
line_1 = self.create_line_for_reconciliation(1000.0, 1500.0, self.foreign_currency, '2016-01-01')
|
||||
line_2 = self.create_line_for_reconciliation(-900.0, -5400.0, self.foreign_currency_2, '2016-01-01')
|
||||
wizard_input_values = {
|
||||
'journal_id': self.misc_journal.id,
|
||||
'account_id': self.write_off_account.id,
|
||||
'label': 'Write-Off Test Label',
|
||||
'allow_partials': False,
|
||||
'date': new_date,
|
||||
}
|
||||
self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, [
|
||||
{
|
||||
'account_id': self.receivable_account.id,
|
||||
'balance': -100.0,
|
||||
'amount_currency': -150.0,
|
||||
'currency_id': self.foreign_currency.id,
|
||||
},
|
||||
{
|
||||
'account_id': self.write_off_account.id,
|
||||
'balance': 100.0,
|
||||
'amount_currency': 150.0,
|
||||
'currency_id': self.foreign_currency.id,
|
||||
},
|
||||
])
|
||||
|
||||
def test_write_off_multi_curr_multi_residuals_force_partials(self):
|
||||
""" Test that we raise an error when trying to reconcile lines with multiple residuals.
|
||||
Here debit1 will be reconciled with credit1 first as they have the same currency.
|
||||
Then residual of debit1 will try to reconcile with debit2 which is impossible
|
||||
=> 2 residuals both in foreign currency, we don't know in which currency we should make the write-off
|
||||
=> We should only allow partial reconciliation. """
|
||||
debit_1 = self.create_line_for_reconciliation(2000.0, 12000.0, self.foreign_currency_2, '2016-01-01')
|
||||
credit_1 = self.create_line_for_reconciliation(-1000.0, -6000.0, self.foreign_currency_2, '2016-01-01')
|
||||
debit_2 = self.create_line_for_reconciliation(2000.0, 3000.0, self.foreign_currency, '2016-01-01')
|
||||
wizard = self.env['account.reconcile.wizard'].with_context(
|
||||
active_model='account.move.line',
|
||||
active_ids=(debit_1 + debit_2 + credit_1).ids,
|
||||
).new()
|
||||
self.assertRecordValues(wizard, [{'force_partials': True, 'allow_partials': True}])
|
||||
|
||||
def test_write_off_multi_curr_multi_residuals_exch_diff_force_partials(self):
|
||||
debit_1 = self.create_line_for_reconciliation(2000.0, 0.0, self.foreign_currency_2, '2016-01-01')
|
||||
credit_1 = self.create_line_for_reconciliation(-1000.0, 0.0, self.foreign_currency_2, '2016-01-01')
|
||||
debit_2 = self.create_line_for_reconciliation(2000.0, 0.0, self.foreign_currency, '2016-01-01')
|
||||
wizard = self.env['account.reconcile.wizard'].with_context(
|
||||
active_model='account.move.line',
|
||||
active_ids=(debit_1 + debit_2 + credit_1).ids,
|
||||
).new()
|
||||
self.assertRecordValues(wizard, [{'force_partials': True, 'allow_partials': True}])
|
||||
|
||||
def test_reconcile_with_partner_change(self):
|
||||
partner_1 = self.env['res.partner'].create({'name': 'Test Partner 1'})
|
||||
partner_2 = self.env['res.partner'].create({'name': 'Test Partner 2'})
|
||||
line_1 = self.create_line_for_reconciliation(-1000.0, -1000.0, self.company_currency, '2016-01-01', partner=partner_1)
|
||||
line_2 = self.create_line_for_reconciliation(2000.0, 2000.0, self.company_currency, '2016-01-01')
|
||||
wizard_input_values = {
|
||||
'journal_id': self.misc_journal.id,
|
||||
'account_id': self.receivable_account.id,
|
||||
'to_partner_id': partner_2.id,
|
||||
'label': 'Write-Off Test Label',
|
||||
'allow_partials': False,
|
||||
'date': self.test_date,
|
||||
'tax_id': self.tax_sale_a.id,
|
||||
}
|
||||
write_off_expected_values = [
|
||||
{'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label', 'balance': -1000.0, 'partner_id': partner_1.id},
|
||||
{'account_id': self.company_data['default_account_tax_sale'].id, 'name': '15%', 'balance': 130.43, 'partner_id': partner_2.id},
|
||||
{'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label', 'balance': 869.57, 'partner_id': partner_2.id},
|
||||
]
|
||||
self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, write_off_expected_values)
|
||||
|
||||
def test_reconcile_with_partner_change_and_transfer(self):
|
||||
partner_1 = self.env['res.partner'].create({'name': 'Test Partner 1'})
|
||||
partner_2 = self.env['res.partner'].create({'name': 'Test Partner 2'})
|
||||
line_1 = self.create_line_for_reconciliation(-1000.0, -1000.0, self.company_currency, '2016-01-01', account_1=self.payable_account)
|
||||
line_2 = self.create_line_for_reconciliation(2000.0, 2000.0, self.company_currency, '2016-01-01', partner=partner_1)
|
||||
wizard_input_values = {
|
||||
'journal_id': self.misc_journal.id,
|
||||
'account_id': self.receivable_account.id,
|
||||
'to_partner_id': partner_2.id,
|
||||
'label': 'Write-Off Test Label',
|
||||
'allow_partials': False,
|
||||
'date': self.test_date,
|
||||
}
|
||||
expected_transfer_values = [
|
||||
{'account_id': self.receivable_account.id, 'name': f'Transfer from {self.payable_account.display_name}',
|
||||
'balance': -1000.0, 'amount_currency': -1000.0, 'currency_id': self.company_currency.id},
|
||||
{'account_id': self.payable_account.id, 'name': f'Transfer to {self.receivable_account.display_name}',
|
||||
'balance': 1000.0, 'amount_currency': 1000.0, 'currency_id': self.company_currency.id},
|
||||
]
|
||||
write_off_expected_values = [
|
||||
{'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label', 'balance': -1000.0, 'partner_id': partner_1.id},
|
||||
{'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label', 'balance': 1000.0, 'partner_id': partner_2.id},
|
||||
]
|
||||
self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, write_off_expected_values, expected_transfer_values)
|
||||
|
||||
def test_reconcile_edit_mode_partial_foreign_curr(self):
|
||||
line_1 = self.create_line_for_reconciliation(100.0, 300.0, self.foreign_currency, '2016-01-01')
|
||||
wizard_input_values = {
|
||||
'account_id': self.write_off_account.id,
|
||||
'label': 'Write-Off Test Label',
|
||||
'date': self.test_date,
|
||||
'edit_mode_amount_currency': 30.0,
|
||||
}
|
||||
expected_values = [
|
||||
{'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': -10.0, 'amount_currency': -30.0, 'currency_id': self.foreign_currency.id},
|
||||
{'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': 10.0, 'amount_currency': 30.0, 'currency_id': self.foreign_currency.id},
|
||||
]
|
||||
self.assertWizardReconcileValues(line_1, wizard_input_values, expected_values)
|
||||
|
||||
def test_reconcile_edit_mode_partial_company_curr(self):
|
||||
line_1 = self.create_line_for_reconciliation(300.0, 300.0, self.company_currency, '2016-01-01')
|
||||
wizard_input_values = {
|
||||
'account_id': self.write_off_account.id,
|
||||
'label': 'Write-Off Test Label',
|
||||
'date': self.test_date,
|
||||
'edit_mode_amount_currency': 100.0,
|
||||
}
|
||||
expected_values = [
|
||||
{'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': -100.0, 'amount_currency': -100.0, 'currency_id': self.company_currency.id},
|
||||
{'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': 100.0, 'amount_currency': 100.0, 'currency_id': self.company_currency.id},
|
||||
]
|
||||
self.assertWizardReconcileValues(line_1, wizard_input_values, expected_values)
|
||||
|
||||
def test_reconcile_edit_mode_partial_wrong_amount_raises(self):
|
||||
line_1 = self.create_line_for_reconciliation(300.0, 300.0, self.company_currency, '2016-01-01')
|
||||
wizard_input_values = {
|
||||
'account_id': self.write_off_account.id,
|
||||
}
|
||||
wizard = self.env['account.reconcile.wizard'].with_context(
|
||||
active_model='account.move.line',
|
||||
active_ids=line_1.ids,
|
||||
).create(wizard_input_values)
|
||||
with self.assertRaisesRegex(UserError, 'The amount of the write-off'):
|
||||
wizard.edit_mode_amount_currency = -100.0
|
||||
|
||||
def test_reconcile_edit_mode_full_reconcile(self):
|
||||
line_1 = self.create_line_for_reconciliation(300.0, 300.0, self.company_currency, '2016-01-01')
|
||||
wizard_input_values = {
|
||||
'account_id': self.write_off_account.id,
|
||||
'label': 'Write-Off Test Label',
|
||||
'edit_mode_amount_currency': 300.0,
|
||||
}
|
||||
expected_values = [
|
||||
{'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': -300.0, 'amount_currency': -300.0, 'currency_id': self.company_currency.id},
|
||||
{'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': 300.0, 'amount_currency': 300.0, 'currency_id': self.company_currency.id},
|
||||
]
|
||||
self.assertWizardReconcileValues(line_1, wizard_input_values, expected_values)
|
||||
|
||||
def test_reconcile_same_currency_same_side_not_recpay(self):
|
||||
"""
|
||||
Test the reconciliation with two lines on the same side (debit/credit), same currency and not on a receivable/payable account
|
||||
"""
|
||||
current_assets_account = self.company_data['default_account_assets'].copy({'name': 'Current Assets', 'account_type': 'asset_current', 'reconcile': True})
|
||||
line_1 = self.create_line_for_reconciliation(200, 200, self.company_currency, '2016-01-01', current_assets_account)
|
||||
line_2 = self.create_line_for_reconciliation(200, 200, self.company_currency, '2016-01-01', current_assets_account)
|
||||
|
||||
# Test the opening of the wizard without input values
|
||||
wizard = self.env['account.reconcile.wizard'].with_context(
|
||||
active_model='account.move.line',
|
||||
active_ids=(line_1 + line_2).ids,
|
||||
).new()
|
||||
|
||||
self.assertRecordValues(wizard, [{'is_write_off_required': True, 'amount': 400, 'amount_currency': 400}])
|
||||
|
||||
wizard_input_values = {
|
||||
'journal_id': self.misc_journal.id,
|
||||
'account_id': self.write_off_account.id,
|
||||
'label': 'Write-Off Test Label',
|
||||
'allow_partials': False,
|
||||
'date': self.test_date,
|
||||
}
|
||||
expected_values = [
|
||||
{'account_id': current_assets_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': -400.0, 'amount_currency': -400.0, 'currency_id': self.company_currency.id},
|
||||
{'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': 400.0, 'amount_currency': 400.0, 'currency_id': self.company_currency.id},
|
||||
]
|
||||
self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, expected_values)
|
||||
|
||||
def test_reconcile_foreign_currency_same_side_not_recpay(self):
|
||||
"""
|
||||
Test the reconciliation with two lines on the same side (debit/credit), one foreign currency and not on a receivable/payable account
|
||||
"""
|
||||
current_assets_account = self.company_data['default_account_assets'].copy({'name': 'Current Assets', 'account_type': 'asset_current', 'reconcile': True})
|
||||
line_1 = self.create_line_for_reconciliation(200, 300, self.foreign_currency, '2016-01-01', current_assets_account)
|
||||
line_2 = self.create_line_for_reconciliation(200, 200, self.company_currency, '2016-01-01', current_assets_account)
|
||||
|
||||
# Test the opening of the wizard without input values
|
||||
wizard = self.env['account.reconcile.wizard'].with_context(
|
||||
active_model='account.move.line',
|
||||
active_ids=(line_1 + line_2).ids,
|
||||
).new()
|
||||
|
||||
self.assertRecordValues(wizard, [{'is_write_off_required': True, 'amount': 400, 'amount_currency': 400}])
|
||||
|
||||
wizard_input_values = {
|
||||
'journal_id': self.misc_journal.id,
|
||||
'account_id': self.write_off_account.id,
|
||||
'label': 'Write-Off Test Label',
|
||||
'allow_partials': False,
|
||||
'date': self.test_date,
|
||||
}
|
||||
expected_values = [
|
||||
{'account_id': current_assets_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': -400.0, 'amount_currency': -400.0, 'currency_id': self.company_currency.id},
|
||||
{'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': 400.0, 'amount_currency': 400.0, 'currency_id': self.company_currency.id},
|
||||
]
|
||||
self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, expected_values)
|
||||
|
||||
def test_reconcile_same_side_exch_diff(self):
|
||||
"""
|
||||
Test the reconciliation with two lines on the same side (debit/credit), one exchange diff in foreign currency,
|
||||
one regular aml in company currency
|
||||
"""
|
||||
exchange_gain_account = self.company_data['company'].income_currency_exchange_account_id
|
||||
exchange_gain_account.reconcile = True
|
||||
line_1 = self.create_line_for_reconciliation(150.0, 150.0, self.company_currency, '2016-01-01')
|
||||
line_2 = self.create_line_for_reconciliation(100.0, 0.0, self.foreign_currency, '2016-01-01', account_1=exchange_gain_account)
|
||||
wizard_input_values = {
|
||||
'journal_id': self.misc_journal.id,
|
||||
'account_id': self.write_off_account.id,
|
||||
'label': 'Write-Off Test Label',
|
||||
'allow_partials': False,
|
||||
'date': self.test_date,
|
||||
}
|
||||
# Note the transfer will always be in the currency of the line transferred
|
||||
expected_transfer_values = [
|
||||
{'account_id': exchange_gain_account.id, 'name': f'Transfer to {self.receivable_account.display_name}',
|
||||
'balance': -100.0, 'amount_currency': 0.0, 'currency_id': self.foreign_currency.id},
|
||||
{'account_id': self.receivable_account.id, 'name': f'Transfer from {exchange_gain_account.display_name}',
|
||||
'balance': 100.0, 'amount_currency': 0.0, 'currency_id': self.foreign_currency.id},
|
||||
]
|
||||
expected_values = [
|
||||
{'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': -250.0, 'amount_currency': -250.0, 'currency_id': self.company_currency.id},
|
||||
{'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label',
|
||||
'balance': 250.0, 'amount_currency': 250.0, 'currency_id': self.company_currency.id},
|
||||
]
|
||||
self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, expected_values, expected_transfer_values=expected_transfer_values)
|
||||
708
Fusion Accounting/tests/test_analytic_reports.py
Normal file
708
Fusion Accounting/tests/test_analytic_reports.py
Normal file
@@ -0,0 +1,708 @@
|
||||
from odoo import Command
|
||||
from odoo.tests import tagged
|
||||
|
||||
from .common import TestAccountReportsCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestAnalyticReport(TestAccountReportsCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.env.user.groups_id += cls.env.ref(
|
||||
'analytic.group_analytic_accounting')
|
||||
cls.report = cls.env.ref('fusion_accounting.profit_and_loss')
|
||||
cls.report.write({'filter_analytic': True})
|
||||
|
||||
cls.analytic_plan_parent = cls.env['account.analytic.plan'].create({
|
||||
'name': 'Plan Parent',
|
||||
})
|
||||
cls.analytic_plan_child = cls.env['account.analytic.plan'].create({
|
||||
'name': 'Plan Child',
|
||||
'parent_id': cls.analytic_plan_parent.id,
|
||||
})
|
||||
|
||||
cls.analytic_account_parent = cls.env['account.analytic.account'].create({
|
||||
'name': 'Account 1',
|
||||
'plan_id': cls.analytic_plan_parent.id
|
||||
})
|
||||
cls.analytic_account_parent_2 = cls.env['account.analytic.account'].create({
|
||||
'name': 'Account 2',
|
||||
'plan_id': cls.analytic_plan_parent.id
|
||||
})
|
||||
cls.analytic_account_child = cls.env['account.analytic.account'].create({
|
||||
'name': 'Account 3',
|
||||
'plan_id': cls.analytic_plan_child.id
|
||||
})
|
||||
cls.analytic_account_parent_3 = cls.env['account.analytic.account'].create({
|
||||
'name': 'Account 4',
|
||||
'plan_id': cls.analytic_plan_parent.id
|
||||
})
|
||||
|
||||
def test_report_group_by_analytic_plan(self):
|
||||
|
||||
out_invoice = self.env['account.move'].create([{
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': self.partner_a.id,
|
||||
'date': '2019-05-01',
|
||||
'invoice_date': '2019-05-01',
|
||||
'invoice_line_ids': [
|
||||
Command.create({
|
||||
'product_id': self.product_a.id,
|
||||
'price_unit': 200.0,
|
||||
'analytic_distribution': {
|
||||
self.analytic_account_parent.id: 100,
|
||||
},
|
||||
}),
|
||||
Command.create({
|
||||
'product_id': self.product_b.id,
|
||||
'price_unit': 200.0,
|
||||
'analytic_distribution': {
|
||||
self.analytic_account_child.id: 100,
|
||||
},
|
||||
}),
|
||||
]
|
||||
}])
|
||||
out_invoice.action_post()
|
||||
|
||||
options = self._generate_options(
|
||||
self.report,
|
||||
'2019-01-01',
|
||||
'2019-12-31',
|
||||
default_options={
|
||||
'analytic_plans_groupby': [self.analytic_plan_parent.id, self.analytic_plan_child.id],
|
||||
}
|
||||
)
|
||||
|
||||
lines = self.report._get_lines(options)
|
||||
|
||||
self.assertLinesValues(
|
||||
# pylint: disable=bad-whitespace
|
||||
lines,
|
||||
[ 0, 1, 2],
|
||||
[
|
||||
['Revenue', 400.00, 200.00],
|
||||
['Less Costs of Revenue', 0.00, 0.00],
|
||||
['Gross Profit', 400.00, 200.00],
|
||||
['Less Operating Expenses', 0.00, 0.00],
|
||||
['Operating Income (or Loss)', 400.00, 200.00],
|
||||
['Plus Other Income', 0.00, 0.00],
|
||||
['Less Other Expenses', 0.00, 0.00],
|
||||
['Net Profit', 400.00, 200.00],
|
||||
],
|
||||
options,
|
||||
currency_map={
|
||||
1: {'currency': self.env.company.currency_id},
|
||||
2: {'currency': self.env.company.currency_id},
|
||||
},
|
||||
)
|
||||
|
||||
def test_report_analytic_filter(self):
|
||||
|
||||
out_invoice = self.env['account.move'].create([{
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': self.partner_a.id,
|
||||
'date': '2023-02-01',
|
||||
'invoice_date': '2023-02-01',
|
||||
'invoice_line_ids': [
|
||||
Command.create({
|
||||
'product_id': self.product_a.id,
|
||||
'price_unit': 1000.0,
|
||||
'analytic_distribution': {
|
||||
self.analytic_account_parent.id: 100,
|
||||
},
|
||||
})
|
||||
]
|
||||
}])
|
||||
out_invoice.action_post()
|
||||
|
||||
options = self._generate_options(
|
||||
self.report,
|
||||
'2023-01-01',
|
||||
'2023-12-31',
|
||||
default_options={
|
||||
'analytic_accounts': [self.analytic_account_parent.id],
|
||||
}
|
||||
)
|
||||
|
||||
self.assertLinesValues(
|
||||
# pylint: disable=C0326
|
||||
# pylint: disable=bad-whitespace
|
||||
self.report._get_lines(options),
|
||||
[ 0, 1],
|
||||
[
|
||||
['Revenue', 1000.00],
|
||||
['Less Costs of Revenue', 0.00],
|
||||
['Gross Profit', 1000.00],
|
||||
['Less Operating Expenses', 0.00],
|
||||
['Operating Income (or Loss)', 1000.00],
|
||||
['Plus Other Income', 0.00],
|
||||
['Less Other Expenses', 0.00],
|
||||
['Net Profit', 1000.00],
|
||||
],
|
||||
options,
|
||||
currency_map={
|
||||
1: {'currency': self.env.company.currency_id},
|
||||
2: {'currency': self.env.company.currency_id},
|
||||
},
|
||||
)
|
||||
|
||||
# Set the unused analytic account in filter, as no move is
|
||||
# using this account, the column should be empty
|
||||
options['analytic_accounts'] = [self.analytic_account_child.id]
|
||||
|
||||
self.assertLinesValues(
|
||||
# pylint: disable=C0326
|
||||
# pylint: disable=bad-whitespace
|
||||
self.report._get_lines(options),
|
||||
[ 0, 1],
|
||||
[
|
||||
['Revenue', 0.00],
|
||||
['Less Costs of Revenue', 0.00],
|
||||
['Gross Profit', 0.00],
|
||||
['Less Operating Expenses', 0.00],
|
||||
['Operating Income (or Loss)', 0.00],
|
||||
['Plus Other Income', 0.00],
|
||||
['Less Other Expenses', 0.00],
|
||||
['Net Profit', 0.00],
|
||||
],
|
||||
options,
|
||||
currency_map={
|
||||
1: {'currency': self.env.company.currency_id},
|
||||
2: {'currency': self.env.company.currency_id},
|
||||
},
|
||||
)
|
||||
|
||||
def test_report_audit_analytic_filter(self):
|
||||
out_invoice = self.env['account.move'].create([{
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': self.partner_a.id,
|
||||
'date': '2023-02-01',
|
||||
'invoice_date': '2023-02-01',
|
||||
'invoice_line_ids': [
|
||||
Command.create({
|
||||
'product_id': self.product_a.id,
|
||||
'price_unit': 1000.0,
|
||||
'analytic_distribution': {
|
||||
self.analytic_account_parent.id: 100,
|
||||
},
|
||||
}),
|
||||
Command.create({
|
||||
'product_id': self.product_a.id,
|
||||
'price_unit': 500.0,
|
||||
'analytic_distribution': {
|
||||
self.analytic_account_child.id: 100,
|
||||
},
|
||||
}),
|
||||
],
|
||||
}])
|
||||
out_invoice.action_post()
|
||||
|
||||
options = self._generate_options(
|
||||
self.report,
|
||||
'2023-01-01',
|
||||
'2023-12-31',
|
||||
default_options={
|
||||
'analytic_accounts': [self.analytic_account_parent.id],
|
||||
}
|
||||
)
|
||||
|
||||
lines = self.report._get_lines(options)
|
||||
|
||||
report_line = self.report.line_ids[0]
|
||||
report_line_dict = next(x for x in lines if x['name'] == report_line.name)
|
||||
|
||||
action_dict = self.report.action_audit_cell(
|
||||
options,
|
||||
self._get_audit_params_from_report_line(options, report_line, report_line_dict),
|
||||
)
|
||||
|
||||
audited_lines = self.env['account.move.line'].search(action_dict['domain'])
|
||||
self.assertEqual(audited_lines, out_invoice.invoice_line_ids[0], "Only the line with the parent account should be shown")
|
||||
|
||||
def test_report_analytic_groupby_and_filter(self):
|
||||
"""
|
||||
Test that the analytic filter is applied on the groupby columns
|
||||
"""
|
||||
|
||||
out_invoice = self.env['account.move'].create([{
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': self.partner_a.id,
|
||||
'date': '2023-02-01',
|
||||
'invoice_date': '2023-02-01',
|
||||
'invoice_line_ids': [
|
||||
Command.create({
|
||||
'product_id': self.product_a.id,
|
||||
'price_unit': 1000.0,
|
||||
'analytic_distribution': {
|
||||
self.analytic_account_parent.id: 40,
|
||||
self.analytic_account_child.id: 60,
|
||||
},
|
||||
})
|
||||
]
|
||||
}])
|
||||
out_invoice.action_post()
|
||||
|
||||
# Test with only groupby
|
||||
options = self._generate_options(
|
||||
self.report,
|
||||
'2023-01-01',
|
||||
'2023-12-31',
|
||||
default_options={
|
||||
'analytic_accounts_groupby': [self.analytic_account_parent.id, self.analytic_account_child.id],
|
||||
}
|
||||
)
|
||||
|
||||
self.assertLinesValues(
|
||||
# pylint: disable=C0326
|
||||
# pylint: disable=bad-whitespace
|
||||
self.report._get_lines(options),
|
||||
[ 0, 1, 2, 3],
|
||||
[
|
||||
['Revenue', 400.00, 600.00, 1000.00],
|
||||
['Less Costs of Revenue', 0.00, 0.00, 0.00],
|
||||
['Gross Profit', 400.00, 600.00, 1000.00],
|
||||
['Less Operating Expenses', 0.00, 0.00, 0.00],
|
||||
['Operating Income (or Loss)', 400.00, 600.00, 1000.00],
|
||||
['Plus Other Income', 0.00, 0.00, 0.00],
|
||||
['Less Other Expenses', 0.00, 0.00, 0.00],
|
||||
['Net Profit', 400.00, 600.00, 1000.00],
|
||||
],
|
||||
options,
|
||||
currency_map={
|
||||
1: {'currency': self.env.company.currency_id},
|
||||
2: {'currency': self.env.company.currency_id},
|
||||
},
|
||||
)
|
||||
|
||||
# Adding analytic filter for the two analytic accounts used on the invoice line
|
||||
# The two groupby columns should still be filled
|
||||
options['analytic_accounts'] = [self.analytic_account_parent.id, self.analytic_account_child.id]
|
||||
|
||||
self.assertLinesValues(
|
||||
# pylint: disable=C0326
|
||||
# pylint: disable=bad-whitespace
|
||||
self.report._get_lines(options),
|
||||
[ 0, 1, 2, 3],
|
||||
[
|
||||
['Revenue', 400.00, 600.00, 1000.00],
|
||||
['Less Costs of Revenue', 0.00, 0.00, 0.00],
|
||||
['Gross Profit', 400.00, 600.00, 1000.00],
|
||||
['Less Operating Expenses', 0.00, 0.00, 0.00],
|
||||
['Operating Income (or Loss)', 400.00, 600.00, 1000.00],
|
||||
['Plus Other Income', 0.00, 0.00, 0.00],
|
||||
['Less Other Expenses', 0.00, 0.00, 0.00],
|
||||
['Net Profit', 400.00, 600.00, 1000.00],
|
||||
],
|
||||
options,
|
||||
currency_map={
|
||||
1: {'currency': self.env.company.currency_id},
|
||||
2: {'currency': self.env.company.currency_id},
|
||||
},
|
||||
)
|
||||
# Keep only first analytic account on filter, the groupby column
|
||||
# for this account should still be filled, unlike the other
|
||||
options['analytic_accounts'] = [self.analytic_account_parent.id]
|
||||
|
||||
self.assertLinesValues(
|
||||
# pylint: disable=C0326
|
||||
# pylint: disable=bad-whitespace
|
||||
self.report._get_lines(options),
|
||||
[ 0, 1, 2, 3],
|
||||
[
|
||||
['Revenue', 400.00, 0.00, 1000.00],
|
||||
['Less Costs of Revenue', 0.00, 0.00, 0.00],
|
||||
['Gross Profit', 400.00, 0.00, 1000.00],
|
||||
['Less Operating Expenses', 0.00, 0.00, 0.00],
|
||||
['Operating Income (or Loss)', 400.00, 0.00, 1000.00],
|
||||
['Plus Other Income', 0.00, 0.00, 0.00],
|
||||
['Less Other Expenses', 0.00, 0.00, 0.00],
|
||||
['Net Profit', 400.00, 0.00, 1000.00],
|
||||
],
|
||||
options,
|
||||
currency_map={
|
||||
1: {'currency': self.env.company.currency_id},
|
||||
2: {'currency': self.env.company.currency_id},
|
||||
},
|
||||
)
|
||||
|
||||
# Keep only first analytic account on filter, the groupby column
|
||||
# for this account should still be filled, unlike the other
|
||||
options['analytic_accounts'] = [self.analytic_account_child.id]
|
||||
|
||||
self.assertLinesValues(
|
||||
# pylint: disable=C0326
|
||||
# pylint: disable=bad-whitespace
|
||||
self.report._get_lines(options),
|
||||
[ 0, 1, 2, 3],
|
||||
[
|
||||
['Revenue', 0.00, 600.00, 1000.00],
|
||||
['Less Costs of Revenue', 0.00, 0.00, 0.00],
|
||||
['Gross Profit', 0.00, 600.00, 1000.00],
|
||||
['Less Operating Expenses', 0.00, 0.00, 0.00],
|
||||
['Operating Income (or Loss)', 0.00, 600.00, 1000.00],
|
||||
['Plus Other Income', 0.00, 0.00, 0.00],
|
||||
['Less Other Expenses', 0.00, 0.00, 0.00],
|
||||
['Net Profit', 0.00, 600.00, 1000.00],
|
||||
],
|
||||
options,
|
||||
currency_map={
|
||||
1: {'currency': self.env.company.currency_id},
|
||||
2: {'currency': self.env.company.currency_id},
|
||||
},
|
||||
)
|
||||
|
||||
# Set an unused analytic account in filter, all the columns
|
||||
# should be empty, as no move is using this account
|
||||
options['analytic_accounts'] = [self.analytic_account_parent_2.id]
|
||||
|
||||
self.assertLinesValues(
|
||||
# pylint: disable=C0326
|
||||
# pylint: disable=bad-whitespace
|
||||
self.report._get_lines(options),
|
||||
[ 0, 1, 2, 3],
|
||||
[
|
||||
['Revenue', 0.00, 0.00, 0.00],
|
||||
['Less Costs of Revenue', 0.00, 0.00, 0.00],
|
||||
['Gross Profit', 0.00, 0.00, 0.00],
|
||||
['Less Operating Expenses', 0.00, 0.00, 0.00],
|
||||
['Operating Income (or Loss)', 0.00, 0.00, 0.00],
|
||||
['Plus Other Income', 0.00, 0.00, 0.00],
|
||||
['Less Other Expenses', 0.00, 0.00, 0.00],
|
||||
['Net Profit', 0.00, 0.00, 0.00],
|
||||
],
|
||||
options,
|
||||
currency_map={
|
||||
1: {'currency': self.env.company.currency_id},
|
||||
2: {'currency': self.env.company.currency_id},
|
||||
},
|
||||
)
|
||||
|
||||
def test_audit_cell_analytic_groupby_and_filter(self):
|
||||
"""
|
||||
Test that the analytic filters are applied on the auditing of the cells
|
||||
"""
|
||||
def _get_action_dict(options, column_index):
|
||||
lines = self.report._get_lines(options)
|
||||
report_line = self.report.line_ids[0]
|
||||
report_line_dict = next(x for x in lines if x['name'] == report_line.name)
|
||||
audit_param = self._get_audit_params_from_report_line(options, report_line, report_line_dict, column_group_key=list(options['column_groups'])[column_index])
|
||||
return self.report.action_audit_cell(options, audit_param)
|
||||
|
||||
other_plan = self.env['account.analytic.plan'].create({'name': "Other Plan"})
|
||||
other_account = self.env['account.analytic.account'].create({'name': "Other Account", 'plan_id': other_plan.id, 'active': True})
|
||||
|
||||
out_invoices = self.env['account.move'].create([
|
||||
{
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': self.partner_a.id,
|
||||
'date': '2023-02-01',
|
||||
'invoice_date': '2023-02-01',
|
||||
'invoice_line_ids': [
|
||||
Command.create({
|
||||
'product_id': self.product_a.id,
|
||||
'price_unit': 1000.0,
|
||||
'analytic_distribution': {
|
||||
self.analytic_account_parent.id: 40,
|
||||
self.analytic_account_child.id: 60,
|
||||
}
|
||||
}),
|
||||
]
|
||||
},
|
||||
{
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': self.partner_a.id,
|
||||
'date': '2023-02-01',
|
||||
'invoice_date': '2023-02-01',
|
||||
'invoice_line_ids': [
|
||||
Command.create({
|
||||
'product_id': self.product_a.id,
|
||||
'price_unit': 2000.0,
|
||||
'analytic_distribution': {
|
||||
f'{self.analytic_account_parent.id},{other_account.id}': 100,
|
||||
},
|
||||
}),
|
||||
]
|
||||
}
|
||||
])
|
||||
out_invoices.action_post()
|
||||
out_invoices = out_invoices.with_context(analytic_plan_id=self.analytic_plan_parent.id)
|
||||
analytic_lines_parent = out_invoices.invoice_line_ids.analytic_line_ids.filtered(lambda line: line.auto_account_id == self.analytic_account_parent)
|
||||
analytic_lines_other = out_invoices.with_context(analytic_plan_id=other_plan.id).invoice_line_ids.analytic_line_ids.filtered(lambda line: line.auto_account_id == other_account)
|
||||
|
||||
# Test with only groupby
|
||||
options = self._generate_options(
|
||||
self.report,
|
||||
'2023-01-01',
|
||||
'2023-12-31',
|
||||
default_options={
|
||||
'analytic_accounts_groupby': [self.analytic_account_parent.id, other_account.id],
|
||||
}
|
||||
)
|
||||
action_dict = _get_action_dict(options, 0) # First Column => Parent
|
||||
self.assertEqual(
|
||||
self.env['account.analytic.line'].search(action_dict['domain']),
|
||||
analytic_lines_parent,
|
||||
"Only the Analytic Line related to the Parent should be shown",
|
||||
)
|
||||
action_dict = _get_action_dict(options, 1) # Second Column => Other
|
||||
self.assertEqual(
|
||||
self.env['account.analytic.line'].search(action_dict['domain']),
|
||||
analytic_lines_other,
|
||||
"Only the Analytic Line related to the Parent should be shown",
|
||||
)
|
||||
|
||||
action_dict = _get_action_dict(options, 2) # Third Column => AMLs
|
||||
self.assertEqual(
|
||||
out_invoices.line_ids.filtered_domain(action_dict['domain']),
|
||||
out_invoices.invoice_line_ids,
|
||||
"Both amls should be shown",
|
||||
)
|
||||
|
||||
# Adding analytic filter for the two analytic accounts used on the invoice line
|
||||
options['analytic_accounts'] = [self.analytic_account_parent.id, other_account.id]
|
||||
action_dict = _get_action_dict(options, 0) # First Column => Parent
|
||||
self.assertEqual(
|
||||
self.env['account.analytic.line'].search(action_dict['domain']),
|
||||
analytic_lines_parent,
|
||||
"Still only the Analytic Line related to the Parent should be shown",
|
||||
)
|
||||
action_dict = _get_action_dict(options, 1) # Second Column => Other
|
||||
self.assertEqual(
|
||||
self.env['account.analytic.line'].search(action_dict['domain']),
|
||||
analytic_lines_other,
|
||||
"Still only the Analytic Line related to the Parent should be shown",
|
||||
)
|
||||
|
||||
action_dict = _get_action_dict(options, 2) # Third Column => AMLs
|
||||
self.assertEqual(
|
||||
out_invoices.line_ids.search(action_dict['domain']),
|
||||
out_invoices.invoice_line_ids,
|
||||
"Both amls should be shown",
|
||||
)
|
||||
|
||||
def test_general_ledger_analytic_filter(self):
|
||||
analytic_plan = self.env["account.analytic.plan"].create({
|
||||
"name": "Default Plan",
|
||||
})
|
||||
analytic_account = self.env["account.analytic.account"].create({
|
||||
"name": "Test Account",
|
||||
"plan_id": analytic_plan.id,
|
||||
})
|
||||
|
||||
invoice = self.init_invoice(
|
||||
"out_invoice",
|
||||
amounts=[100, 200],
|
||||
invoice_date="2023-01-01",
|
||||
)
|
||||
invoice.action_post()
|
||||
invoice.invoice_line_ids[0].analytic_distribution = {analytic_account.id: 100}
|
||||
|
||||
general_ledger_report = self.env.ref("fusion_accounting.general_ledger_report")
|
||||
options = self._generate_options(
|
||||
general_ledger_report,
|
||||
"2023-01-01",
|
||||
"2023-01-01",
|
||||
default_options={
|
||||
'analytic_accounts': [analytic_account.id],
|
||||
'unfold_all': True,
|
||||
}
|
||||
)
|
||||
|
||||
self.assertLinesValues(
|
||||
general_ledger_report._get_lines(options),
|
||||
# Name Debit Credit Balance
|
||||
[ 0, 5, 6, 7],
|
||||
[
|
||||
['400000 Product Sales', 0.00, 100.00, -100.00],
|
||||
['INV/2023/00001', 0.00, 100.00, -100.00],
|
||||
['Total 400000 Product Sales', 0.00, 100.00, -100.00],
|
||||
['Total', 0.00, 100.00, -100.00],
|
||||
],
|
||||
options,
|
||||
)
|
||||
|
||||
def test_analytic_groupby_with_horizontal_groupby(self):
|
||||
|
||||
out_invoice_1 = self.env['account.move'].create([{
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': self.partner_a.id,
|
||||
'date': '2024-07-01',
|
||||
'invoice_date': '2024-07-01',
|
||||
'invoice_line_ids': [
|
||||
Command.create({
|
||||
'product_id': self.product_b.id,
|
||||
'price_unit': 500.0,
|
||||
'analytic_distribution': {
|
||||
self.analytic_account_parent_2.id: 80,
|
||||
self.analytic_account_parent_3.id: -10,
|
||||
},
|
||||
}),
|
||||
]
|
||||
}])
|
||||
out_invoice_1.action_post()
|
||||
|
||||
out_invoice_2 = self.env['account.move'].create([{
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': self.partner_a.id,
|
||||
'date': '2024-07-01',
|
||||
'invoice_date': '2024-07-01',
|
||||
'invoice_line_ids': [
|
||||
Command.create({
|
||||
'product_id': self.product_a.id,
|
||||
'price_unit': 100.0,
|
||||
'analytic_distribution': {
|
||||
self.analytic_account_parent.id: 100,
|
||||
},
|
||||
}),
|
||||
]
|
||||
}])
|
||||
out_invoice_2.action_post()
|
||||
|
||||
horizontal_group = self.env['account.report.horizontal.group'].create({
|
||||
'name': 'Horizontal Group Journal Entries',
|
||||
'report_ids': [self.report.id],
|
||||
'rule_ids': [
|
||||
Command.create({
|
||||
'field_name': 'move_id', # this field is specific to account.move.line and not in account.analytic.line
|
||||
'domain': f"[('id', 'in', {(out_invoice_1 + out_invoice_2).ids})]",
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
options = self._generate_options(
|
||||
self.report,
|
||||
'2024-01-01',
|
||||
'2024-12-31',
|
||||
default_options={
|
||||
'analytic_accounts_groupby': [self.analytic_account_parent.id, self.analytic_account_parent_2.id, self.analytic_account_parent_3.id],
|
||||
'selected_horizontal_group_id': horizontal_group.id,
|
||||
}
|
||||
)
|
||||
|
||||
self.assertLinesValues(
|
||||
self.report._get_lines(options),
|
||||
# Horizontal groupby [ Move 2 ] [ Move 1 ]
|
||||
# Analytic groupby A1 A2 A3 Balance A1 A2 A3 Balance
|
||||
[ 0, 1, 2, 3, 4, 5, 6, 7, 8],
|
||||
[
|
||||
['Revenue', 100.00, 0.00, 0.00, 100.00, 0.00, 400.00, -50.00, 500.00],
|
||||
['Less Costs of Revenue', 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00],
|
||||
['Gross Profit', 100.00, 0.00, 0.00, 100.00, 0.00, 400.00, -50.00, 500.00],
|
||||
['Less Operating Expenses', 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00],
|
||||
['Operating Income (or Loss)', 100.00, 0.00, 0.00, 100.00, 0.00, 400.00, -50.00, 500.00],
|
||||
['Plus Other Income', 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00],
|
||||
['Less Other Expenses', 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00],
|
||||
['Net Profit', 100.00, 0.00, 0.00, 100.00, 0.00, 400.00, -50.00, 500.00],
|
||||
],
|
||||
options,
|
||||
)
|
||||
|
||||
def test_analytic_groupby_with_analytic_simulations(self):
|
||||
"""
|
||||
Create an analytic simulation (analytic line without a move line)
|
||||
and check that it is taken into account in the report
|
||||
"""
|
||||
|
||||
self.env['account.analytic.line'].create({
|
||||
'name': 'Simulation',
|
||||
'date': '2019-05-01',
|
||||
'amount': 100.0,
|
||||
'unit_amount': 1.0,
|
||||
'company_id': self.env.company.id,
|
||||
self.analytic_plan_parent._column_name(): self.analytic_account_parent.id,
|
||||
'general_account_id': self.company_data['default_account_revenue'].id,
|
||||
})
|
||||
|
||||
options = self._generate_options(
|
||||
self.report,
|
||||
'2019-01-01',
|
||||
'2019-12-31',
|
||||
default_options={
|
||||
'analytic_plans_groupby': [self.analytic_plan_parent.id, self.analytic_plan_child.id],
|
||||
'include_analytic_without_aml': True,
|
||||
}
|
||||
)
|
||||
|
||||
self.assertLinesValues(
|
||||
self.report._get_lines(options),
|
||||
[ 0, 1, 2],
|
||||
[
|
||||
('Revenue', 100.00, 0.00),
|
||||
('Less Costs of Revenue', 0.00, 0.00),
|
||||
('Gross Profit', 100.00, 0.00),
|
||||
('Less Operating Expenses', 0.00, 0.00),
|
||||
('Operating Income (or Loss)', 100.00, 0.00),
|
||||
('Plus Other Income', 0.00, 0.00),
|
||||
('Less Other Expenses', 0.00, 0.00),
|
||||
('Net Profit', 100.00, 0.00),
|
||||
],
|
||||
options,
|
||||
)
|
||||
|
||||
def test_analytic_groupby_plans_without_analytic_accounts(self):
|
||||
"""
|
||||
Ensure that grouping on several analytic plans without any analytic accounts works as expected
|
||||
"""
|
||||
analytic_plans_without_accounts = self.env['account.analytic.plan'].create([
|
||||
{'name': 'Plan 1'},
|
||||
{'name': 'Plan 2'},
|
||||
])
|
||||
|
||||
options = self._generate_options(
|
||||
self.report, '2019-01-01', '2019-12-31',
|
||||
default_options={'analytic_plans_groupby': analytic_plans_without_accounts.ids}
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
len(options['column_groups']), 3,
|
||||
"the number of column groups should be 3, despite the 2 analytic plans having the exact same analytic accounts list"
|
||||
)
|
||||
|
||||
self.assertLinesValues(
|
||||
self.report._get_lines(options),
|
||||
# Plan 1 Plan 2 Total
|
||||
[ 0, 1, 2, 3],
|
||||
[
|
||||
('Revenue', 0.00, 0.00, 0.00),
|
||||
('Less Costs of Revenue', 0.00, 0.00, 0.00),
|
||||
('Gross Profit', 0.00, 0.00, 0.00),
|
||||
('Less Operating Expenses', 0.00, 0.00, 0.00),
|
||||
('Operating Income (or Loss)', 0.00, 0.00, 0.00),
|
||||
('Plus Other Income', 0.00, 0.00, 0.00),
|
||||
('Less Other Expenses', 0.00, 0.00, 0.00),
|
||||
('Net Profit', 0.00, 0.00, 0.00),
|
||||
],
|
||||
options,
|
||||
)
|
||||
|
||||
def test_profit_and_loss_multicompany_access_rights(self):
|
||||
branch = self.env['res.company'].create([{
|
||||
'name': "My Test Branch",
|
||||
'parent_id': self.env.company.id,
|
||||
}])
|
||||
other_currency = self.setup_other_currency('EUR', rounding=0.001)
|
||||
test_journal = self.env['account.journal'].create({
|
||||
'name': 'Test Journal',
|
||||
'code': 'TEST',
|
||||
'type': 'sale',
|
||||
'company_id': self.env.company.id,
|
||||
'currency_id': other_currency.id,
|
||||
})
|
||||
test_user = self.env['res.users'].create({
|
||||
'login': 'test',
|
||||
'name': 'The King',
|
||||
'email': 'noop@example.com',
|
||||
'groups_id': [Command.link(self.env.ref('account.group_account_manager').id)],
|
||||
'company_ids': [Command.link(self.env.company.id), Command.link(branch.id)],
|
||||
})
|
||||
self.env.invalidate_all()
|
||||
|
||||
options = self._generate_options(
|
||||
self.report.with_user(test_user).with_company(branch), '2019-01-01', '2019-12-31',
|
||||
)
|
||||
lines = self.report._get_lines(options)
|
||||
self.assertTrue(lines)
|
||||
self.assertEqual(test_journal.display_name, "Test Journal (EUR)")
|
||||
3263
Fusion Accounting/tests/test_bank_rec_widget.py
Normal file
3263
Fusion Accounting/tests/test_bank_rec_widget.py
Normal file
File diff suppressed because it is too large
Load Diff
68
Fusion Accounting/tests/test_bank_rec_widget_common.py
Normal file
68
Fusion Accounting/tests/test_bank_rec_widget_common.py
Normal file
@@ -0,0 +1,68 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import Command
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
|
||||
|
||||
class TestBankRecWidgetCommon(AccountTestInvoicingCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.other_currency = cls.setup_other_currency('EUR')
|
||||
cls.other_currency_2 = cls.setup_other_currency('CAD', rounding=0.001, rates=[('2016-01-01', 6.0), ('2017-01-01', 4.0)])
|
||||
cls.other_currency_3 = cls.setup_other_currency('XAF', rounding=0.001, rates=[('2016-01-01', 12.0), ('2017-01-01', 8.0)])
|
||||
|
||||
@classmethod
|
||||
def _create_invoice_line(cls, move_type, **kwargs):
|
||||
''' Create an invoice on the fly.'''
|
||||
kwargs.setdefault('partner_id', cls.partner_a.id)
|
||||
kwargs.setdefault('invoice_date', '2017-01-01')
|
||||
kwargs.setdefault('invoice_line_ids', [])
|
||||
for one2many_values in kwargs['invoice_line_ids']:
|
||||
one2many_values.setdefault('name', 'xxxx')
|
||||
one2many_values.setdefault('quantity', 1)
|
||||
one2many_values.setdefault('tax_ids', [])
|
||||
|
||||
invoice = cls.env['account.move'].create({
|
||||
'move_type': move_type,
|
||||
**kwargs,
|
||||
'invoice_line_ids': [Command.create(x) for x in kwargs['invoice_line_ids']],
|
||||
})
|
||||
invoice.action_post()
|
||||
return invoice.line_ids\
|
||||
.filtered(lambda l: l.account_id.account_type in ('asset_receivable', 'liability_payable'))
|
||||
|
||||
@classmethod
|
||||
def _create_st_line(cls, amount, date='2019-01-01', payment_ref='turlututu', **kwargs):
|
||||
st_line = cls.env['account.bank.statement.line'].create({
|
||||
'amount': amount,
|
||||
'date': date,
|
||||
'payment_ref': payment_ref,
|
||||
'journal_id': kwargs.get('journal_id', cls.company_data['default_journal_bank'].id),
|
||||
**kwargs,
|
||||
})
|
||||
# The automatic reconcile cron checks the create_date when considering st_lines to run on.
|
||||
# create_date is a protected field so this is the only way to set it correctly
|
||||
cls.env.cr.execute("UPDATE account_bank_statement_line SET create_date = %s WHERE id=%s",
|
||||
(st_line.date, st_line.id))
|
||||
return st_line
|
||||
|
||||
@classmethod
|
||||
def _create_reconcile_model(cls, **kwargs):
|
||||
return cls.env['account.reconcile.model'].create({
|
||||
'name': "test",
|
||||
'rule_type': 'invoice_matching',
|
||||
'allow_payment_tolerance': True,
|
||||
'payment_tolerance_type': 'percentage',
|
||||
'payment_tolerance_param': 0.0,
|
||||
**kwargs,
|
||||
'line_ids': [
|
||||
Command.create({
|
||||
'account_id': cls.company_data['default_account_revenue'].id,
|
||||
'amount_type': 'percentage',
|
||||
'label': f"test {i}",
|
||||
**line_vals,
|
||||
})
|
||||
for i, line_vals in enumerate(kwargs.get('line_ids', []))
|
||||
],
|
||||
})
|
||||
200
Fusion Accounting/tests/test_bank_rec_widget_tour.py
Normal file
200
Fusion Accounting/tests/test_bank_rec_widget_tour.py
Normal file
@@ -0,0 +1,200 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo.addons.fusion_accounting.tests.test_bank_rec_widget_common import TestBankRecWidgetCommon
|
||||
from odoo.tests import tagged, HttpCase
|
||||
from odoo import Command
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestBankRecWidget(TestBankRecWidgetCommon, HttpCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.st_line1 = cls._create_st_line(1000.0, payment_ref="line1", sequence=1)
|
||||
cls.st_line2 = cls._create_st_line(1000.0, payment_ref="line2", sequence=2)
|
||||
cls._create_st_line(1000.0, payment_ref="line3", sequence=3)
|
||||
cls._create_st_line(1000.0, payment_ref="line_credit", sequence=4, journal_id=cls.company_data['default_journal_credit'].id)
|
||||
|
||||
# INV/2019/00001:
|
||||
cls._create_invoice_line(
|
||||
'out_invoice',
|
||||
partner_id=cls.partner_a.id,
|
||||
invoice_date='2019-01-01',
|
||||
invoice_line_ids=[{'price_unit': 1000.0}],
|
||||
)
|
||||
|
||||
# INV/2019/00002:
|
||||
cls._create_invoice_line(
|
||||
'out_invoice',
|
||||
partner_id=cls.partner_a.id,
|
||||
invoice_date='2019-01-01',
|
||||
invoice_line_ids=[{'price_unit': 1000.0}],
|
||||
)
|
||||
|
||||
cls.env['account.reconcile.model']\
|
||||
.search([('company_id', '=', cls.company_data['company'].id)])\
|
||||
.write({'past_months_limit': None})
|
||||
|
||||
cls.reco_model_invoice = cls.env['account.reconcile.model'].create({
|
||||
'name': "test reconcile create invoice",
|
||||
'rule_type': 'writeoff_button',
|
||||
'counterpart_type': 'sale',
|
||||
'line_ids': [
|
||||
Command.create({'amount_string': '50'}),
|
||||
Command.create({'amount_string': '50'}),
|
||||
],
|
||||
})
|
||||
|
||||
def test_tour_bank_rec_widget(self):
|
||||
self.start_tour('/odoo', 'fusion_accounting_bank_rec_widget', login=self.env.user.login)
|
||||
|
||||
self.assertRecordValues(self.st_line1.line_ids, [
|
||||
# pylint: disable=C0326
|
||||
{'account_id': self.st_line1.journal_id.default_account_id.id, 'balance': 1000.0, 'reconciled': False},
|
||||
{'account_id': self.company_data['default_account_receivable'].id, 'balance': -1000.0, 'reconciled': True},
|
||||
])
|
||||
|
||||
tax_account = self.company_data['default_tax_sale'].invoice_repartition_line_ids.account_id
|
||||
self.assertRecordValues(self.st_line2.line_ids, [
|
||||
# pylint: disable=C0326
|
||||
{'account_id': self.st_line2.journal_id.default_account_id.id, 'balance': 1000.0, 'tax_ids': []},
|
||||
{'account_id': self.company_data['default_account_payable'].id, 'balance': -869.57, 'tax_ids': self.company_data['default_tax_sale'].ids},
|
||||
{'account_id': tax_account.id, 'balance': -130.43, 'tax_ids': []},
|
||||
])
|
||||
|
||||
def test_tour_bank_rec_widget_ui(self):
|
||||
bank2 = self.env['account.journal'].create({
|
||||
'name': 'Bank2',
|
||||
'type': 'bank',
|
||||
'code': 'BNK2',
|
||||
})
|
||||
self._create_st_line(222.22, payment_ref="line4", sequence=4, journal_id=bank2.id)
|
||||
# INV/2019/00003:
|
||||
self._create_invoice_line(
|
||||
'out_invoice',
|
||||
partner_id=self.partner_a.id,
|
||||
invoice_date='2019-01-01',
|
||||
invoice_line_ids=[{'price_unit': 2000.0}],
|
||||
)
|
||||
self.st_line2.payment_ref = self.st_line2.payment_ref + ' - ' + 'INV/2019/00001'
|
||||
self.start_tour('/odoo?debug=assets', 'fusion_accounting_bank_rec_widget_ui', timeout=120, login=self.env.user.login)
|
||||
|
||||
def test_tour_bank_rec_widget_rainbowman_reset(self):
|
||||
self.start_tour('/odoo?debug=assets', 'fusion_accounting_bank_rec_widget_rainbowman_reset', login=self.env.user.login)
|
||||
|
||||
def test_tour_bank_rec_widget_statements(self):
|
||||
self.start_tour('/odoo?debug=assets', 'fusion_accounting_bank_rec_widget_statements', login=self.env.user.login)
|
||||
|
||||
def test_tour_invoice_creation_from_reco_model(self):
|
||||
""" Test if move is created and added as a new_aml line in bank reconciliation widget """
|
||||
st_line = self._create_st_line(amount=1000, partner_id=self.partner_a.id)
|
||||
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
|
||||
# The tour creates a move through reco model button, posts it, returns to widget and validates the move
|
||||
self.start_tour(
|
||||
'/odoo',
|
||||
'fusion_accounting_bank_rec_widget_reconciliation_button',
|
||||
login=self.env.user.login,
|
||||
)
|
||||
# Mount the validated statement line to confirm that information matches.
|
||||
wizard._js_action_mount_st_line(st_line.id)
|
||||
self.assertRecordValues(wizard.line_ids, [
|
||||
{'flag': 'liquidity', 'account_id': st_line.journal_id.default_account_id.id, 'balance': 1000},
|
||||
{'flag': 'aml', 'account_id': self.company_data['default_account_receivable'].id, 'balance': -1000},
|
||||
])
|
||||
# Check that the aml comes from a move, and not from the auto-balance line
|
||||
self.assertTrue(wizard.line_ids[1].source_aml_move_id)
|
||||
|
||||
def test_tour_invoice_creation_reco_model_currency(self):
|
||||
""" Test move creation through reconcile button when a foreign currency is used for the statement line """
|
||||
st_line = self._create_st_line(
|
||||
1800.0,
|
||||
date='2019-02-01',
|
||||
foreign_currency_id=self.other_currency.id, # rate 2:1
|
||||
amount_currency=3600.0,
|
||||
partner_id=self.partner_a.id,
|
||||
)
|
||||
|
||||
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
|
||||
|
||||
self.start_tour(
|
||||
'/odoo',
|
||||
'fusion_accounting_bank_rec_widget_reconciliation_button',
|
||||
login=self.env.user.login,
|
||||
)
|
||||
# Mount the validated statement line to confirm that information matches.
|
||||
wizard._js_action_mount_st_line(st_line.id)
|
||||
|
||||
# Move is created in the foreign currency, but in bank widget the balance appears in main currency.
|
||||
# If aml was created from the reco model button, display name matches payment_ref.
|
||||
self.assertRecordValues(wizard.line_ids, [
|
||||
{'flag': 'liquidity', 'balance': 1800, 'amount_currency': 1800},
|
||||
{'flag': 'aml', 'balance': -1800, 'amount_currency': -3600},
|
||||
])
|
||||
# Confirm that the aml comes from a move, and not from the auto-balance line
|
||||
self.assertTrue(wizard.line_ids[1].source_aml_move_id)
|
||||
|
||||
def test_tour_invoice_creation_combined_reco_model(self):
|
||||
""" Test creation of a move from a reconciliation model with different amount types """
|
||||
self.reco_model_invoice.name = "old test" # rename previous reco model to be able to reuse the existing tour
|
||||
self.env['account.reconcile.model'].create({
|
||||
'name': "test reconcile combined",
|
||||
'rule_type': 'writeoff_button',
|
||||
'counterpart_type': 'purchase',
|
||||
'line_ids': [
|
||||
Command.create({
|
||||
'amount_type': 'percentage_st_line',
|
||||
'amount_string': '50',
|
||||
}),
|
||||
Command.create({
|
||||
'amount_type': 'percentage',
|
||||
'amount_string': '50',
|
||||
'tax_ids': self.tax_purchase_b.ids,
|
||||
}),
|
||||
Command.create({
|
||||
'amount_type': 'fixed',
|
||||
'amount_string': '100',
|
||||
'account_id': self.env.company.expense_currency_exchange_account_id.id,
|
||||
'tax_ids': [Command.clear()] # remove default tax added
|
||||
}),
|
||||
# Regex line will not be added to move, as the label of st line does not include digits
|
||||
Command.create({
|
||||
'amount_type': 'regex',
|
||||
'amount_string': r'BRT: ([\d,.]+)',
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
st_line = self._create_st_line(amount=-1000, partner_id=self.partner_a.id, payment_ref="combined test")
|
||||
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
|
||||
# The tour creates a move through reco model button, posts it, returns to widget and validates the move
|
||||
self.start_tour(
|
||||
'/odoo',
|
||||
'fusion_accounting_bank_rec_widget_reconciliation_button',
|
||||
login=self.env.user.login,
|
||||
)
|
||||
# Mount the validated statement line to confirm that widget line matches created move and balance line is added.
|
||||
wizard._js_action_mount_st_line(st_line.id)
|
||||
self.assertRecordValues(wizard.line_ids, [
|
||||
{'flag': 'liquidity', 'account_id': st_line.journal_id.default_account_id.id, 'balance': -1000},
|
||||
{'flag': 'aml', 'account_id': self.company_data['default_account_payable'].id, 'balance': 850},
|
||||
{'flag': 'aml', 'account_id': self.company_data['default_account_payable'].id, 'balance': 150},
|
||||
])
|
||||
# Check that the aml comes from an existing move
|
||||
move = wizard.line_ids[1].source_aml_move_id
|
||||
self.assertTrue(move)
|
||||
|
||||
# The total price of these lines should match the percentage or fixed amount of reco model lines
|
||||
self.assertRecordValues(move.line_ids, [
|
||||
# 50% of statement line (of 1000.0)
|
||||
{'price_total': 500, 'debit': 434.78, 'credit': 0, 'name': 'combined test', 'account_id': self.company_data['default_account_expense'].id},
|
||||
# 50% of balance (of residual value = 500.0)
|
||||
{'price_total': 250, 'debit': 217.39, 'credit': 0, 'name': 'combined test', 'account_id': self.company_data['default_account_expense'].id},
|
||||
# fixed amount of 100.0, no tax in reco model line
|
||||
{'price_total': 100, 'debit': 100, 'credit': 0, 'name': 'combined test', 'account_id': self.env.company.expense_currency_exchange_account_id.id},
|
||||
# Tax for line 1 (65.22 + 434.78 = 500)
|
||||
{'price_total': 0, 'debit': 65.22, 'credit': 0, 'name': '15%', 'account_id': self.company_data['default_account_tax_purchase'].id},
|
||||
# Tax for line 1 (32.61 + 217.39 = 250)
|
||||
{'price_total': 0, 'debit': 32.61, 'credit': 0, 'name': '15% (Copy)', 'account_id': self.company_data['default_account_tax_purchase'].id},
|
||||
{'price_total': 0, 'debit': 0, 'credit': 850, 'name': 'combined test', 'account_id': self.company_data['default_account_payable'].id},
|
||||
])
|
||||
1321
Fusion Accounting/tests/test_board_compute.py
Normal file
1321
Fusion Accounting/tests/test_board_compute.py
Normal file
File diff suppressed because it is too large
Load Diff
201
Fusion Accounting/tests/test_change_lock_date_wizard.py
Normal file
201
Fusion Accounting/tests/test_change_lock_date_wizard.py
Normal file
@@ -0,0 +1,201 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import fields
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
from odoo.addons.fusion_accounting.wizard.account_change_lock_date import SOFT_LOCK_DATE_FIELDS
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import frozendict
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestChangeLockDateWizard(AccountTestInvoicingCommon):
|
||||
|
||||
def test_exception_generation(self):
|
||||
"""
|
||||
Test the exception generation from the wizard.
|
||||
Note that exceptions for 'everyone' and 'forever' are not tested here.
|
||||
They do not create an exception (no 'account.lock_exception' object), but just change the lock date.
|
||||
(See `test_everyone_forever_exception`.)
|
||||
"""
|
||||
self.env['account.lock_exception'].search([]).sudo().unlink()
|
||||
|
||||
for lock_date_field in SOFT_LOCK_DATE_FIELDS:
|
||||
with self.subTest(lock_date_field=lock_date_field), self.cr.savepoint() as sp:
|
||||
# We can set the lock date if there is none.
|
||||
self.env['account.change.lock.date'].create({lock_date_field: '2010-01-01'}).change_lock_date()
|
||||
self.assertEqual(self.env.company[lock_date_field], fields.Date.from_string('2010-01-01'))
|
||||
|
||||
# We can increase the lock date if there is one.
|
||||
self.env['account.change.lock.date'].create({lock_date_field: '2011-01-01'}).change_lock_date()
|
||||
self.assertEqual(self.env.company[lock_date_field], fields.Date.from_string('2011-01-01'))
|
||||
|
||||
# We cannot remove the lock date; but we can create an exception
|
||||
wizard = self.env['account.change.lock.date'].create({
|
||||
lock_date_field: False,
|
||||
'exception_applies_to': 'everyone',
|
||||
'exception_duration': '1h',
|
||||
'exception_reason': ':TestChangeLockDateWizard.test_exception_generation; remove',
|
||||
})
|
||||
wizard.change_lock_date()
|
||||
self.assertEqual(self.env['account.lock_exception'].search_count([]), 1)
|
||||
exception = self.env['account.lock_exception'].search([])
|
||||
self.assertEqual(len(exception), 1)
|
||||
self.assertRecordValues(exception, [{
|
||||
lock_date_field: False,
|
||||
'company_id': self.env.company.id,
|
||||
'user_id': False,
|
||||
'create_uid': self.env.user.id,
|
||||
'end_datetime': self.env.cr.now() + timedelta(hours=1),
|
||||
'reason': ':TestChangeLockDateWizard.test_exception_generation; remove',
|
||||
}])
|
||||
exception.sudo().unlink()
|
||||
|
||||
# Ensure we have not created any exceptions yet
|
||||
self.assertEqual(self.env['account.lock_exception'].search_count([]), 0)
|
||||
|
||||
# We cannot decrease the lock date; but we can create an exception
|
||||
self.env['account.change.lock.date'].create({lock_date_field: '2009-01-01'}).change_lock_date()
|
||||
self.assertEqual(self.env.company[lock_date_field], fields.Date.from_string('2011-01-01'))
|
||||
exception = self.env['account.lock_exception'].search([])
|
||||
self.assertEqual(len(exception), 1)
|
||||
# Check lock date and default values on exception
|
||||
self.assertRecordValues(exception, [{
|
||||
lock_date_field: fields.Date.from_string('2009-01-01'),
|
||||
'company_id': self.env.company.id,
|
||||
'user_id': self.env.user.id,
|
||||
'create_uid': self.env.user.id,
|
||||
'end_datetime': self.env.cr.now() + timedelta(minutes=5),
|
||||
'reason': False,
|
||||
}])
|
||||
|
||||
sp.close() # Rollback to ensure all subtests start in the same situation
|
||||
|
||||
def test_exception_generation_multiple(self):
|
||||
"""
|
||||
Test the exception generation from the wizard.
|
||||
Here we test the case that we create multiple exceptions at once.
|
||||
This should create an exception object for every changed lock date.
|
||||
"""
|
||||
self.env['account.lock_exception'].search([]).sudo().unlink()
|
||||
|
||||
wizard = self.env['account.change.lock.date'].create({
|
||||
'fiscalyear_lock_date': '2010-01-01',
|
||||
'tax_lock_date': '2010-01-01',
|
||||
'sale_lock_date': '2010-01-01',
|
||||
'purchase_lock_date': '2010-01-01',
|
||||
})
|
||||
wizard.change_lock_date()
|
||||
|
||||
self.assertRecordValues(self.env.company, [{
|
||||
'fiscalyear_lock_date': fields.Date.from_string('2010-01-01'),
|
||||
'tax_lock_date': fields.Date.from_string('2010-01-01'),
|
||||
'sale_lock_date': fields.Date.from_string('2010-01-01'),
|
||||
'purchase_lock_date': fields.Date.from_string('2010-01-01'),
|
||||
}])
|
||||
|
||||
wizard = self.env['account.change.lock.date'].create({
|
||||
'fiscalyear_lock_date': '2009-01-01',
|
||||
'tax_lock_date': '2009-01-01',
|
||||
'sale_lock_date': '2009-01-01',
|
||||
'purchase_lock_date': '2009-01-01',
|
||||
'exception_applies_to': 'everyone',
|
||||
'exception_duration': '1h',
|
||||
'exception_reason': ':TestChangeLockDateWizard.test_exception_generation; remove',
|
||||
})
|
||||
wizard.change_lock_date()
|
||||
|
||||
exceptions = self.env['account.lock_exception'].search([])
|
||||
self.assertEqual(len(exceptions), 4)
|
||||
expected_exceptions = {
|
||||
frozendict({
|
||||
'lock_date_field': 'fiscalyear_lock_date',
|
||||
'lock_date': fields.Date.from_string('2009-01-01'),
|
||||
}),
|
||||
frozendict({
|
||||
'lock_date_field': 'tax_lock_date',
|
||||
'lock_date': fields.Date.from_string('2009-01-01'),
|
||||
}),
|
||||
frozendict({
|
||||
'lock_date_field': 'sale_lock_date',
|
||||
'lock_date': fields.Date.from_string('2009-01-01'),
|
||||
}),
|
||||
frozendict({
|
||||
'lock_date_field': 'purchase_lock_date',
|
||||
'lock_date': fields.Date.from_string('2009-01-01'),
|
||||
}),
|
||||
}
|
||||
created_exceptions = {
|
||||
frozendict({
|
||||
'lock_date_field': exception.lock_date_field,
|
||||
'lock_date': exception.lock_date,
|
||||
})
|
||||
for exception in exceptions
|
||||
}
|
||||
self.assertSetEqual(created_exceptions, expected_exceptions)
|
||||
|
||||
def test_hard_lock_date(self):
|
||||
self.env['account.lock_exception'].search([]).sudo().unlink()
|
||||
|
||||
# We can set the hard lock date if there is none.
|
||||
self.env['account.change.lock.date'].create({'hard_lock_date': '2010-01-01'}).change_lock_date()
|
||||
self.assertEqual(self.env.company.hard_lock_date, fields.Date.from_string('2010-01-01'))
|
||||
|
||||
# We can increase the hard lock date if there is one.
|
||||
self.env['account.change.lock.date'].create({'hard_lock_date': '2011-01-01'}).change_lock_date()
|
||||
self.assertEqual(self.env.company.hard_lock_date, fields.Date.from_string('2011-01-01'))
|
||||
|
||||
# We cannot decrease the hard lock date; not even with an exception.
|
||||
wizard = self.env['account.change.lock.date'].create({
|
||||
'hard_lock_date': '2009-01-01',
|
||||
'exception_applies_to': 'everyone',
|
||||
'exception_duration': '1h',
|
||||
'exception_reason': ':TestChangeLockDateWizard.test_hard_lock_date',
|
||||
})
|
||||
with self.assertRaises(UserError), self.env.cr.savepoint():
|
||||
wizard.change_lock_date()
|
||||
self.assertEqual(self.env.company.hard_lock_date, fields.Date.from_string('2011-01-01'))
|
||||
|
||||
# We cannot remove the hard lock date; not even with an exception.
|
||||
wizard = self.env['account.change.lock.date'].create({
|
||||
'hard_lock_date': False,
|
||||
'exception_applies_to': 'everyone',
|
||||
'exception_duration': '1h',
|
||||
'exception_reason': ':TestChangeLockDateWizard.test_hard_lock_date',
|
||||
})
|
||||
with self.assertRaises(UserError), self.env.cr.savepoint():
|
||||
wizard.change_lock_date()
|
||||
self.assertEqual(self.env.company.hard_lock_date, fields.Date.from_string('2011-01-01'))
|
||||
|
||||
self.assertEqual(self.env['account.lock_exception'].search_count([]), 0)
|
||||
|
||||
def test_everyone_forever_exception(self):
|
||||
self.env['account.lock_exception'].search([]).sudo().unlink()
|
||||
|
||||
for lock_date_field in SOFT_LOCK_DATE_FIELDS:
|
||||
with self.subTest(lock_date_field=lock_date_field), self.cr.savepoint() as sp:
|
||||
self.env['account.change.lock.date'].create({lock_date_field: '2010-01-01'}).change_lock_date()
|
||||
self.assertEqual(self.env.company[lock_date_field], fields.Date.from_string('2010-01-01'))
|
||||
|
||||
# We can decrease the lock date with a 'forever' / 'everyone' exception.
|
||||
self.env['account.change.lock.date'].create({
|
||||
lock_date_field: '2009-01-01',
|
||||
'exception_applies_to': 'everyone',
|
||||
'exception_duration': 'forever',
|
||||
'exception_reason': ':TestChangeLockDateWizard.test_everyone_forever_exception; remove',
|
||||
}).change_lock_date()
|
||||
self.assertEqual(self.env.company[lock_date_field], fields.Date.from_string('2009-01-01'))
|
||||
|
||||
# We can remove the lock date with a 'forever' / 'everyone' exception.
|
||||
self.env['account.change.lock.date'].create({
|
||||
lock_date_field: False,
|
||||
'exception_applies_to': 'everyone',
|
||||
'exception_duration': 'forever',
|
||||
'exception_reason': ':TestChangeLockDateWizard.test_everyone_forever_exception; remove',
|
||||
}).change_lock_date()
|
||||
self.assertEqual(self.env.company[lock_date_field], False)
|
||||
|
||||
# Ensure we have not created any exceptions
|
||||
self.assertEqual(self.env['account.lock_exception'].search_count([]), 0)
|
||||
|
||||
sp.close() # Rollback to ensure all subtests start in the same situation
|
||||
626
Fusion Accounting/tests/test_deferred_management.py
Normal file
626
Fusion Accounting/tests/test_deferred_management.py
Normal file
@@ -0,0 +1,626 @@
|
||||
# -*- 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},
|
||||
])
|
||||
919
Fusion Accounting/tests/test_financial_report.py
Normal file
919
Fusion Accounting/tests/test_financial_report.py
Normal file
@@ -0,0 +1,919 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=C0326
|
||||
|
||||
from .common import TestAccountReportsCommon
|
||||
|
||||
from odoo import fields, Command
|
||||
from odoo.tests import tagged
|
||||
|
||||
from freezegun import freeze_time
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestFinancialReport(TestAccountReportsCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
# ==== Partners ====
|
||||
cls.partner_c = cls._create_partner(name='partner_c')
|
||||
|
||||
# ==== Accounts ====
|
||||
|
||||
# Cleanup existing "Current year earnings" accounts since we can only have one by company.
|
||||
cls.env['account.account'].search([
|
||||
('company_ids', 'in', (cls.company_data['company'] + cls.company_data_2['company']).ids),
|
||||
('account_type', '=', 'equity_unaffected'),
|
||||
]).unlink()
|
||||
|
||||
account_type_data = [
|
||||
('asset_receivable', {'reconcile': True}),
|
||||
('liability_payable', {'reconcile': True}),
|
||||
('asset_cash', {}),
|
||||
('asset_current', {}),
|
||||
('asset_prepayments', {}),
|
||||
('asset_fixed', {}),
|
||||
('asset_non_current', {}),
|
||||
('equity', {}),
|
||||
('equity_unaffected', {}),
|
||||
('income', {}),
|
||||
]
|
||||
|
||||
accounts = cls.env['account.account'].create([{
|
||||
**data[1],
|
||||
'name': 'account%s' % i,
|
||||
'code': 'code%s' % i,
|
||||
'account_type': data[0],
|
||||
} for i, data in enumerate(account_type_data)])
|
||||
|
||||
accounts_2 = cls.env['account.account'].create([{
|
||||
**data[1],
|
||||
'name': 'account%s' % (i + 100),
|
||||
'code': 'code%s' % (i + 100),
|
||||
'account_type': data[0],
|
||||
'company_ids': [Command.link(cls.company_data_2['company'].id)]
|
||||
} for i, data in enumerate(account_type_data)])
|
||||
|
||||
for account in accounts_2:
|
||||
account.code = account.with_company(cls.company_data_2['company']).code
|
||||
|
||||
# ==== Custom filters ====
|
||||
|
||||
cls.horizontal_group = cls.env['account.report.horizontal.group'].create({
|
||||
'name': 'Horizontal Group',
|
||||
'rule_ids': [
|
||||
Command.create({
|
||||
'field_name': 'partner_id',
|
||||
'domain': f"[('id', 'in', {(cls.partner_a + cls.partner_b).ids})]",
|
||||
}),
|
||||
Command.create({
|
||||
'field_name': 'account_id',
|
||||
'domain': f"[('id', 'in', {accounts[:2].ids})]",
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
# ==== Journal entries ====
|
||||
|
||||
cls.move_2019 = cls.env['account.move'].create({
|
||||
'move_type': 'entry',
|
||||
'date': fields.Date.from_string('2019-01-01'),
|
||||
'line_ids': [
|
||||
(0, 0, {'debit': 25.0, 'credit': 0.0, 'account_id': accounts[0].id, 'partner_id': cls.partner_a.id}),
|
||||
(0, 0, {'debit': 25.0, 'credit': 0.0, 'account_id': accounts[0].id, 'partner_id': cls.partner_b.id}),
|
||||
(0, 0, {'debit': 25.0, 'credit': 0.0, 'account_id': accounts[0].id, 'partner_id': cls.partner_c.id}),
|
||||
(0, 0, {'debit': 25.0, 'credit': 0.0, 'account_id': accounts[0].id, 'partner_id': cls.partner_a.id}),
|
||||
(0, 0, {'debit': 200.0, 'credit': 0.0, 'account_id': accounts[1].id, 'partner_id': cls.partner_b.id}),
|
||||
(0, 0, {'debit': 0.0, 'credit': 300.0, 'account_id': accounts[2].id, 'partner_id': cls.partner_c.id}),
|
||||
(0, 0, {'debit': 400.0, 'credit': 0.0, 'account_id': accounts[3].id, 'partner_id': cls.partner_a.id}),
|
||||
(0, 0, {'debit': 0.0, 'credit': 1100.0, 'account_id': accounts[4].id, 'partner_id': cls.partner_b.id}),
|
||||
(0, 0, {'debit': 700.0, 'credit': 0.0, 'account_id': accounts[6].id, 'partner_id': cls.partner_a.id}),
|
||||
(0, 0, {'debit': 0.0, 'credit': 800.0, 'account_id': accounts[7].id, 'partner_id': cls.partner_b.id}),
|
||||
(0, 0, {'debit': 800.0, 'credit': 0.0, 'account_id': accounts[8].id, 'partner_id': cls.partner_c.id}),
|
||||
],
|
||||
})
|
||||
cls.move_2019.action_post()
|
||||
|
||||
cls.move_2018 = cls.env['account.move'].create({
|
||||
'move_type': 'entry',
|
||||
'date': fields.Date.from_string('2018-01-01'),
|
||||
'line_ids': [
|
||||
(0, 0, {'debit': 1000.0, 'credit': 0.0, 'account_id': accounts[0].id, 'partner_id': cls.partner_a.id}),
|
||||
(0, 0, {'debit': 0.0, 'credit': 1000.0, 'account_id': accounts[2].id, 'partner_id': cls.partner_b.id}),
|
||||
(0, 0, {'debit': 250.0, 'credit': 0.0, 'account_id': accounts[0].id, 'partner_id': cls.partner_a.id}),
|
||||
(0, 0, {'debit': 0.0, 'credit': 250.0, 'account_id': accounts[9].id, 'partner_id': cls.partner_a.id}),
|
||||
],
|
||||
})
|
||||
cls.move_2018.action_post()
|
||||
|
||||
cls.move_2017 = cls.env['account.move'].with_company(cls.company_data_2['company']).create({
|
||||
'move_type': 'entry',
|
||||
'date': fields.Date.from_string('2017-01-01'),
|
||||
'line_ids': [
|
||||
(0, 0, {'debit': 2000.0, 'credit': 0.0, 'account_id': accounts_2[0].id, 'partner_id': cls.partner_a.id}),
|
||||
(0, 0, {'debit': 0.0, 'credit': 4000.0, 'account_id': accounts_2[2].id, 'partner_id': cls.partner_b.id}),
|
||||
(0, 0, {'debit': 0.0, 'credit': 5000.0, 'account_id': accounts_2[4].id, 'partner_id': cls.partner_c.id}),
|
||||
(0, 0, {'debit': 7000.0, 'credit': 0.0, 'account_id': accounts_2[6].id, 'partner_id': cls.partner_a.id}),
|
||||
],
|
||||
})
|
||||
cls.move_2017.action_post()
|
||||
|
||||
cls.report = cls.env.ref('fusion_accounting.balance_sheet')
|
||||
|
||||
cls.report_no_parent_id = cls.env["account.report"].create({
|
||||
'name': "Test report",
|
||||
|
||||
'column_ids': [
|
||||
Command.create({
|
||||
'name': 'Balance',
|
||||
'expression_label': 'balance',
|
||||
'sequence': 1
|
||||
})
|
||||
],
|
||||
|
||||
'line_ids': [
|
||||
Command.create({
|
||||
'name': "Invisible Partner A line",
|
||||
'code': "INVA",
|
||||
'sequence': 1,
|
||||
'hierarchy_level': 0,
|
||||
'groupby': "account_id",
|
||||
'foldable': True,
|
||||
'expression_ids': [Command.clear(), Command.create({
|
||||
'label': 'balance',
|
||||
'engine': 'domain',
|
||||
'formula': [("partner_id", "=", cls.partner_a.id)],
|
||||
'subformula': 'sum',
|
||||
})],
|
||||
}),
|
||||
Command.create({
|
||||
'name': "Invisible Partner B line",
|
||||
'code': "INVB",
|
||||
'sequence': 2,
|
||||
'hierarchy_level': 0,
|
||||
'groupby': "account_id",
|
||||
'foldable': True,
|
||||
'expression_ids': [Command.clear(), Command.create({
|
||||
'label': 'balance',
|
||||
'engine': 'domain',
|
||||
'formula': [("partner_id", "=", cls.partner_b.id)],
|
||||
'subformula': 'sum',
|
||||
})],
|
||||
}),
|
||||
Command.create({
|
||||
'name': "Total of Invisible lines",
|
||||
'code': "INVT",
|
||||
'sequence': 3,
|
||||
'hierarchy_level': 0,
|
||||
'expression_ids': [Command.clear(), Command.create({
|
||||
'label': 'balance',
|
||||
'engine': 'aggregation',
|
||||
'formula': 'INVA.balance + INVB.balance',
|
||||
})],
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
def _build_generic_id_from_financial_line(self, financial_rep_ln_xmlid):
|
||||
report_line = self.env.ref(financial_rep_ln_xmlid)
|
||||
return '-account.financial.html.report.line-%s' % report_line.id
|
||||
|
||||
def _get_line_id_from_generic_id(self, generic_id):
|
||||
return int(generic_id.split('-')[-1])
|
||||
|
||||
def test_financial_report_strict_range_on_report_lines_with_no_parent_id(self):
|
||||
""" Tests that lines with no parent can be correctly filtered by date range """
|
||||
self.report_no_parent_id.filter_multi_company = 'disabled'
|
||||
options = self._generate_options(self.report_no_parent_id, fields.Date.from_string('2019-01-01'), fields.Date.from_string('2019-12-31'))
|
||||
|
||||
lines = self.report_no_parent_id._get_lines(options)
|
||||
self.assertLinesValues(
|
||||
lines,
|
||||
# Name Balance
|
||||
[ 0, 1],
|
||||
[
|
||||
('Invisible Partner A line', 1150.0),
|
||||
('Invisible Partner B line', -1675.0),
|
||||
('Total of Invisible lines', -525.0),
|
||||
|
||||
],
|
||||
options,
|
||||
)
|
||||
|
||||
def test_financial_report_strict_empty_range_on_report_lines_with_no_parent_id(self):
|
||||
""" Tests that lines with no parent can be correctly filtered by date range with no invoices"""
|
||||
self.report_no_parent_id.filter_multi_company = 'disabled'
|
||||
options = self._generate_options(self.report_no_parent_id, fields.Date.from_string('2019-03-01'), fields.Date.from_string('2019-03-31'))
|
||||
|
||||
lines = self.report_no_parent_id._get_lines(options)
|
||||
self.assertLinesValues(
|
||||
lines,
|
||||
# Name Balance
|
||||
[ 0, 1],
|
||||
[
|
||||
('Invisible Partner A line', 0.0),
|
||||
('Invisible Partner B line', 0.0),
|
||||
('Total of Invisible lines', 0.0),
|
||||
],
|
||||
options,
|
||||
)
|
||||
|
||||
@freeze_time("2016-06-06")
|
||||
def test_balance_sheet_today_current_year_earnings(self):
|
||||
invoice = self.env['account.move'].create({
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': self.partner_a.id,
|
||||
'date': '2016-02-02',
|
||||
'invoice_line_ids': [Command.create({
|
||||
'product_id': self.product_a.id,
|
||||
'price_unit': 110,
|
||||
'tax_ids': [],
|
||||
})]
|
||||
})
|
||||
invoice.action_post()
|
||||
|
||||
self.report.filter_multi_company = 'disabled'
|
||||
options = self._generate_options(self.report, fields.Date.from_string('2016-06-01'), fields.Date.from_string('2016-06-06'))
|
||||
options['date']['filter'] = 'today'
|
||||
|
||||
lines = self.report._get_lines(options)
|
||||
self.assertLinesValues(
|
||||
lines,
|
||||
# Name Balance
|
||||
[ 0, 1],
|
||||
[
|
||||
('ASSETS', 110.0),
|
||||
('Current Assets', 110.0),
|
||||
('Bank and Cash Accounts', 0.0),
|
||||
('Receivables', 110.0),
|
||||
('Current Assets', 0.0),
|
||||
('Prepayments', 0.0),
|
||||
('Total Current Assets', 110.0),
|
||||
('Plus Fixed Assets', 0.0),
|
||||
('Plus Non-current Assets', 0.0),
|
||||
('Total ASSETS', 110.0),
|
||||
|
||||
('LIABILITIES', 0.0),
|
||||
('Current Liabilities', 0.0),
|
||||
('Current Liabilities', 0.0),
|
||||
('Payables', 0.0),
|
||||
('Total Current Liabilities', 0.0),
|
||||
('Plus Non-current Liabilities', 0.0),
|
||||
('Total LIABILITIES', 0.0),
|
||||
|
||||
('EQUITY', 110.0),
|
||||
('Unallocated Earnings', 110.0),
|
||||
('Current Year Unallocated Earnings', 110.0),
|
||||
('Previous Years Unallocated Earnings', 0.0),
|
||||
('Total Unallocated Earnings', 110.0),
|
||||
('Retained Earnings', 0.0),
|
||||
('Current Year Retained Earnings', 0.0),
|
||||
('Previous Years Retained Earnings', 0.0),
|
||||
('Total Retained Earnings', 0.0),
|
||||
('Total EQUITY', 110.0),
|
||||
|
||||
('LIABILITIES + EQUITY', 110.0),
|
||||
],
|
||||
options,
|
||||
)
|
||||
|
||||
@freeze_time("2016-05-05")
|
||||
def test_balance_sheet_last_month_vs_custom_current_year_earnings(self):
|
||||
"""
|
||||
Checks the balance sheet calls the right period of the P&L when using last_month date filter, or an equivalent custom filter
|
||||
(this used to fail due to options regeneration made by the P&L's get_options())"
|
||||
"""
|
||||
to_invoice = [('15', '11'), ('15', '12'), ('16', '01'), ('16', '02'), ('16', '03'), ('16', '04')]
|
||||
for year, month in to_invoice:
|
||||
invoice = self.env['account.move'].create({
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': self.partner_a.id,
|
||||
'invoice_date': f'20{year}-{month}-01',
|
||||
'invoice_line_ids': [Command.create({
|
||||
'product_id': self.product_a.id,
|
||||
'price_unit': 1000,
|
||||
'tax_ids': [],
|
||||
})]
|
||||
})
|
||||
invoice.action_post()
|
||||
expected_result =[
|
||||
('ASSETS', 6000.0),
|
||||
('Current Assets', 6000.0),
|
||||
('Bank and Cash Accounts', 0.0),
|
||||
('Receivables', 6000.0),
|
||||
('Current Assets', 0.0),
|
||||
('Prepayments', 0.0),
|
||||
('Total Current Assets', 6000.0),
|
||||
('Plus Fixed Assets', 0.0),
|
||||
('Plus Non-current Assets', 0.0),
|
||||
('Total ASSETS', 6000.0),
|
||||
|
||||
('LIABILITIES', 0.0),
|
||||
('Current Liabilities', 0.0),
|
||||
('Current Liabilities', 0.0),
|
||||
('Payables', 0.0),
|
||||
('Total Current Liabilities', 0.0),
|
||||
('Plus Non-current Liabilities', 0.0),
|
||||
('Total LIABILITIES', 0.0),
|
||||
|
||||
('EQUITY', 6000.0),
|
||||
('Unallocated Earnings', 6000.0),
|
||||
('Current Year Unallocated Earnings', 4000.0),
|
||||
('Previous Years Unallocated Earnings', 2000.0),
|
||||
('Total Unallocated Earnings', 6000.0),
|
||||
('Retained Earnings', 0.0),
|
||||
('Current Year Retained Earnings', 0.0),
|
||||
('Previous Years Retained Earnings', 0.0),
|
||||
('Total Retained Earnings', 0.0),
|
||||
('Total EQUITY', 6000.0),
|
||||
('LIABILITIES + EQUITY', 6000.0),
|
||||
|
||||
]
|
||||
self.report.filter_multi_company = 'disabled'
|
||||
options = self._generate_options(self.report, fields.Date.from_string('2016-05-05'), fields.Date.from_string('2016-05-05'))
|
||||
|
||||
# End of Last Month
|
||||
options['date']['filter'] = 'last_month'
|
||||
lines = self.report._get_lines(options)
|
||||
self.assertLinesValues(
|
||||
lines,
|
||||
# Name Balance
|
||||
[ 0, 1],
|
||||
expected_result,
|
||||
options,
|
||||
)
|
||||
# Custom
|
||||
options['date']['filter'] = 'custom'
|
||||
lines = self.report._get_lines(options)
|
||||
self.assertLinesValues(
|
||||
lines,
|
||||
# Name Balance
|
||||
[ 0, 1],
|
||||
expected_result,
|
||||
options,
|
||||
)
|
||||
|
||||
def test_financial_report_single_company(self):
|
||||
line_id = self._get_basic_line_dict_id_from_report_line_ref('fusion_accounting.account_financial_report_bank_view0')
|
||||
self.report.filter_multi_company = 'disabled'
|
||||
options = self._generate_options(self.report, fields.Date.from_string('2019-01-01'), fields.Date.from_string('2019-12-31'))
|
||||
options['unfolded_lines'] = [line_id]
|
||||
|
||||
lines = self.report._get_lines(options)
|
||||
self.assertLinesValues(
|
||||
lines,
|
||||
# Name Balance
|
||||
[ 0, 1],
|
||||
[
|
||||
('ASSETS', 50.0),
|
||||
('Current Assets', -650.0),
|
||||
('Bank and Cash Accounts', -1300.0),
|
||||
('code2 account2', -1300.0),
|
||||
('Total Bank and Cash Accounts', -1300.0),
|
||||
('Receivables', 1350.0),
|
||||
('Current Assets', 400.0),
|
||||
('Prepayments', -1100.0),
|
||||
('Total Current Assets', -650.0),
|
||||
('Plus Fixed Assets', 0.0),
|
||||
('Plus Non-current Assets', 700.0),
|
||||
('Total ASSETS', 50.0),
|
||||
|
||||
('LIABILITIES', -200.0),
|
||||
('Current Liabilities', -200.0),
|
||||
('Current Liabilities', 0.0),
|
||||
('Payables', -200.0),
|
||||
('Total Current Liabilities', -200.0),
|
||||
('Plus Non-current Liabilities', 0.0),
|
||||
('Total LIABILITIES', -200.0),
|
||||
|
||||
('EQUITY', 250.0),
|
||||
('Unallocated Earnings', -550.0),
|
||||
('Current Year Unallocated Earnings', -800.0),
|
||||
('Previous Years Unallocated Earnings', 250.0),
|
||||
('Total Unallocated Earnings', -550.0),
|
||||
('Retained Earnings', 800.0),
|
||||
('Current Year Retained Earnings', 800.0),
|
||||
('Previous Years Retained Earnings', 0.0),
|
||||
('Total Retained Earnings', 800.0),
|
||||
('Total EQUITY', 250.0),
|
||||
|
||||
('LIABILITIES + EQUITY', 50.0),
|
||||
],
|
||||
options,
|
||||
)
|
||||
|
||||
unfolded_lines = self.report._get_unfolded_lines(lines, line_id)
|
||||
self.assertLinesValues(
|
||||
unfolded_lines,
|
||||
# Name Balance
|
||||
[ 0, 1],
|
||||
[
|
||||
('Bank and Cash Accounts', -1300.0),
|
||||
('code2 account2', -1300.0),
|
||||
('Total Bank and Cash Accounts', -1300.0),
|
||||
],
|
||||
options,
|
||||
)
|
||||
|
||||
def test_financial_report_multi_company_currency(self):
|
||||
line_id = self._get_basic_line_dict_id_from_report_line_ref('fusion_accounting.account_financial_report_bank_view0')
|
||||
options = self._generate_options(self.report, fields.Date.from_string('2019-01-01'), fields.Date.from_string('2019-12-31'))
|
||||
options['unfolded_lines'] = [line_id]
|
||||
|
||||
lines = self.report._get_lines(options)
|
||||
self.assertLinesValues(
|
||||
lines,
|
||||
# Name Balance
|
||||
[ 0, 1],
|
||||
[
|
||||
('ASSETS', 50.0),
|
||||
('Current Assets', -4150.0),
|
||||
('Bank and Cash Accounts', -3300.0),
|
||||
('code102 account102', -2000.0),
|
||||
('code2 account2', -1300.0),
|
||||
('Total Bank and Cash Accounts', -3300.0),
|
||||
('Receivables', 2350.0),
|
||||
('Current Assets', 400.0),
|
||||
('Prepayments', -3600.0),
|
||||
('Total Current Assets', -4150.0),
|
||||
('Plus Fixed Assets', 0.0),
|
||||
('Plus Non-current Assets', 4200.0),
|
||||
('Total ASSETS', 50.0),
|
||||
|
||||
('LIABILITIES', -200.0),
|
||||
('Current Liabilities', -200.0),
|
||||
('Current Liabilities', 0.0),
|
||||
('Payables', -200.0),
|
||||
('Total Current Liabilities', -200.0),
|
||||
('Plus Non-current Liabilities', 0.0),
|
||||
('Total LIABILITIES', -200.0),
|
||||
|
||||
('EQUITY', 250.0),
|
||||
('Unallocated Earnings', -550.0),
|
||||
('Current Year Unallocated Earnings', -800.0),
|
||||
('Previous Years Unallocated Earnings', 250.0),
|
||||
('Total Unallocated Earnings', -550.0),
|
||||
('Retained Earnings', 800.0),
|
||||
('Current Year Retained Earnings', 800.0),
|
||||
('Previous Years Retained Earnings', 0.0),
|
||||
('Total Retained Earnings', 800.0),
|
||||
('Total EQUITY', 250.0),
|
||||
|
||||
('LIABILITIES + EQUITY', 50.0),
|
||||
],
|
||||
options,
|
||||
)
|
||||
|
||||
unfolded_lines = self.report._get_unfolded_lines(lines, line_id)
|
||||
self.assertLinesValues(
|
||||
unfolded_lines,
|
||||
# Name Balance
|
||||
[ 0, 1],
|
||||
[
|
||||
('Bank and Cash Accounts', -3300.0),
|
||||
('code102 account102', -2000.0),
|
||||
('code2 account2', -1300.0),
|
||||
('Total Bank and Cash Accounts', -3300.0),
|
||||
],
|
||||
options,
|
||||
)
|
||||
|
||||
def test_financial_report_comparison(self):
|
||||
line_id = self._get_basic_line_dict_id_from_report_line_ref('fusion_accounting.account_financial_report_bank_view0')
|
||||
options = self._generate_options(self.report, fields.Date.from_string('2019-01-01'), fields.Date.from_string('2019-12-31'))
|
||||
options = self._update_comparison_filter(options, self.report, 'custom', 1, date_to=fields.Date.from_string('2018-12-31'))
|
||||
options['unfolded_lines'] = [line_id]
|
||||
|
||||
lines = self.report._get_lines(options)
|
||||
|
||||
self.assertColumnPercentComparisonValues(
|
||||
lines,
|
||||
[
|
||||
('ASSETS', '-80.0%', 'red'),
|
||||
('Current Assets', '27.7%', 'red'),
|
||||
('Bank and Cash Accounts', '10.0%', 'red'),
|
||||
('code102 account102', '0.0%', 'muted'),
|
||||
('code2 account2', '30.0%', 'red'),
|
||||
('Total Bank and Cash Accounts', '10.0%', 'red'),
|
||||
('Receivables', '4.4%', 'green'),
|
||||
('Current Assets', 'n/a', 'muted'),
|
||||
('Prepayments', '44.0%', 'red'),
|
||||
('Total Current Assets', '27.7%', 'red'),
|
||||
('Plus Fixed Assets', 'n/a', 'muted'),
|
||||
('Plus Non-current Assets', '20.0%', 'green'),
|
||||
('Total ASSETS', '-80.0%', 'red'),
|
||||
|
||||
('LIABILITIES', 'n/a', 'muted'),
|
||||
('Current Liabilities', 'n/a', 'muted'),
|
||||
('Current Liabilities', 'n/a', 'muted'),
|
||||
('Payables', 'n/a', 'muted'),
|
||||
('Total Current Liabilities', 'n/a', 'muted'),
|
||||
('Plus Non-current Liabilities', 'n/a', 'muted'),
|
||||
('Total LIABILITIES', 'n/a', 'muted'),
|
||||
|
||||
('EQUITY', '0.0%', 'muted'),
|
||||
('Unallocated Earnings', '-320.0%', 'red'),
|
||||
('Current Year Unallocated Earnings', '-420.0%', 'red'),
|
||||
('Previous Years Unallocated Earnings', 'n/a', 'muted'),
|
||||
('Total Unallocated Earnings', '-320.0%', 'red'),
|
||||
('Retained Earnings', 'n/a', 'muted'),
|
||||
('Current Year Retained Earnings', 'n/a', 'muted'),
|
||||
('Previous Years Retained Earnings', 'n/a', 'muted'),
|
||||
('Total Retained Earnings', 'n/a', 'muted'),
|
||||
('Total EQUITY', '0.0%', 'muted'),
|
||||
|
||||
|
||||
('LIABILITIES + EQUITY', '-80.0%', 'green'),
|
||||
]
|
||||
)
|
||||
|
||||
def test_financial_report_horizontal_group(self):
|
||||
line_id = self._get_basic_line_dict_id_from_report_line_ref('fusion_accounting.account_financial_report_receivable0')
|
||||
self.report.horizontal_group_ids |= self.horizontal_group
|
||||
|
||||
options = self._generate_options(
|
||||
self.report,
|
||||
fields.Date.from_string('2019-01-01'),
|
||||
fields.Date.from_string('2019-12-31'),
|
||||
default_options={
|
||||
'unfolded_lines': [line_id],
|
||||
'selected_horizontal_group_id': self.horizontal_group.id,
|
||||
}
|
||||
)
|
||||
options = self._update_comparison_filter(options, self.report, 'custom', 1, date_to=fields.Date.from_string('2018-12-31'))
|
||||
|
||||
lines = self.report._get_lines(options)
|
||||
self.assertHeadersValues(
|
||||
options['column_headers'],
|
||||
[
|
||||
['As of 12/31/2019', 'As of 12/31/2018'],
|
||||
['partner_a', 'partner_b'],
|
||||
['code0 account0', 'code1 account1'],
|
||||
]
|
||||
)
|
||||
self.assertLinesValues(
|
||||
lines,
|
||||
[ 0, 1, 2, 3, 4, 5, 6, 7, 8],
|
||||
[
|
||||
('ASSETS', 1300.0, 0.0, 25.0, 0.0, 1250.0, 0.0, 0.0, 0.0),
|
||||
('Current Assets', 1300.0, 0.0, 25.0, 0.0, 1250.0, 0.0, 0.0, 0.0),
|
||||
('Bank and Cash Accounts', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||
('Receivables', 1300.0, 0.0, 25.0, 0.0, 1250.0, 0.0, 0.0, 0.0),
|
||||
('code0 account0', 1300.0, 0.0, 25.0, 0.0, 1250.0, 0.0, 0.0, 0.0),
|
||||
('Total Receivables', 1300.0, 0.0, 25.0, 0.0, 1250.0, 0.0, 0.0, 0.0),
|
||||
('Current Assets', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||
('Prepayments', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||
('Total Current Assets', 1300.0, 0.0, 25.0, 0.0, 1250.0, 0.0, 0.0, 0.0),
|
||||
('Plus Fixed Assets', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||
('Plus Non-current Assets', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||
('Total ASSETS', 1300.0, 0.0, 25.0, 0.0, 1250.0, 0.0, 0.0, 0.0),
|
||||
|
||||
('LIABILITIES', 0.0, 0.0, 0.0, -200.0, 0.0, 0.0, 0.0, 0.0),
|
||||
('Current Liabilities', 0.0, 0.0, 0.0, -200.0, 0.0, 0.0, 0.0, 0.0),
|
||||
('Current Liabilities', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||
('Payables', 0.0, 0.0, 0.0, -200.0, 0.0, 0.0, 0.0, 0.0),
|
||||
('Total Current Liabilities', 0.0, 0.0, 0.0, -200.0, 0.0, 0.0, 0.0, 0.0),
|
||||
('Plus Non-current Liabilities', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||
('Total LIABILITIES', 0.0, 0.0, 0.0, -200.0, 0.0, 0.0, 0.0, 0.0),
|
||||
|
||||
('EQUITY', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||
('Unallocated Earnings', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||
('Current Year Unallocated Earnings', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||
('Previous Years Unallocated Earnings', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||
('Total Unallocated Earnings', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||
('Retained Earnings', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||
('Current Year Retained Earnings', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||
('Previous Years Retained Earnings', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||
('Total Retained Earnings', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||
('Total EQUITY', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||
|
||||
('LIABILITIES + EQUITY', 0.0, 0.0, 0.0, -200.0, 0.0, 0.0, 0.0, 0.0),
|
||||
],
|
||||
options,
|
||||
)
|
||||
|
||||
def test_financial_report_horizontal_group_total(self):
|
||||
"""
|
||||
In case we don't have comparison, just one column and one level of groupby a new column is added which is the total
|
||||
of the horizontal group
|
||||
"""
|
||||
horizontal_group = self.env['account.report.horizontal.group'].create({
|
||||
'name': 'Horizontal Group total',
|
||||
'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, '2019-01-01', '2019-12-31', default_options={'selected_horizontal_group_id': horizontal_group.id})
|
||||
self.assertHeadersValues(
|
||||
options['column_headers'],
|
||||
[
|
||||
['As of 12/31/2019'],
|
||||
['partner_a', 'partner_b'],
|
||||
]
|
||||
)
|
||||
|
||||
self.assertTrue(options['show_horizontal_group_total'])
|
||||
# Since we don't calculate the value when totals below section is activated, we disable it
|
||||
self.env.company.totals_below_sections = False
|
||||
self.assertHorizontalGroupTotal(
|
||||
self.report._get_lines(options),
|
||||
[
|
||||
('ASSETS', 6900.0, -4075.0, 2825.0),
|
||||
('Current Assets', 2700.0, -4075.0, -1375.0),
|
||||
('Bank and Cash Accounts', 0.0, -3000.0, -3000.0),
|
||||
('Receivables', 2300.0, 25.0, 2325.0),
|
||||
('Current Assets', 400.0, 0.0, 400.0),
|
||||
('Prepayments', 0.0, -1100.0, -1100.0),
|
||||
('Plus Fixed Assets', 0.0, 0.0, 0.0),
|
||||
('Plus Non-current Assets', 4200.0, 0.0, 4200.0),
|
||||
('LIABILITIES', 0.0, -200.0, -200.0),
|
||||
('Current Liabilities', 0.0, -200.0, -200.0),
|
||||
('Current Liabilities', 0.0, 0.0, 0.0),
|
||||
('Payables', 0.0, -200.0, -200.0),
|
||||
('Plus Non-current Liabilities', 0.0, 0.0, 0.0),
|
||||
('EQUITY', 250.0, 800.0, 1050.0),
|
||||
('Unallocated Earnings', 250.0, 0.0, 250.0),
|
||||
('Current Year Unallocated Earnings', 0.0, 0.0, 0.0),
|
||||
('Previous Years Unallocated Earnings', 250.0, 0.0, 250.0),
|
||||
('Retained Earnings', 0.0, 800.0, 800.0),
|
||||
('Current Year Retained Earnings', 0.0, 800.0, 800.0),
|
||||
('Previous Years Retained Earnings', 0.0, 0.0, 0.0),
|
||||
('LIABILITIES + EQUITY', 250.0, 600.0, 850.0),
|
||||
],
|
||||
)
|
||||
|
||||
options = self._generate_options(self.report, '2019-01-01', '2019-12-31', default_options={'selected_horizontal_group_id': horizontal_group.id})
|
||||
options = self._update_comparison_filter(options, self.report, 'custom', 1, date_to=fields.Date.from_string('2018-12-31'))
|
||||
self.assertHeadersValues(
|
||||
options['column_headers'],
|
||||
[
|
||||
['As of 12/31/2019', 'As of 12/31/2018'],
|
||||
['partner_a', 'partner_b'],
|
||||
]
|
||||
)
|
||||
|
||||
self.assertFalse(options['show_horizontal_group_total'])
|
||||
|
||||
self.assertHorizontalGroupTotal(
|
||||
self.report._get_lines(options),
|
||||
[
|
||||
('ASSETS', 6900.0, -4075.0, 5750.0, -3000.0),
|
||||
('Current Assets', 2700.0, -4075.0, 2250.0, -3000.0),
|
||||
('Bank and Cash Accounts', 0.0, -3000.0, 0.0, -3000.0),
|
||||
('Receivables', 2300.0, 25.0, 2250.0, 0.0),
|
||||
('Current Assets', 400.0, 0.0, 0.0, 0.0),
|
||||
('Prepayments', 0.0, -1100.0, 0.0, 0.0),
|
||||
('Plus Fixed Assets', 0.0, 0.0, 0.0, 0.0),
|
||||
('Plus Non-current Assets', 4200.0, 0.0, 3500.0, 0.0),
|
||||
('LIABILITIES', 0.0, -200.0, 0.0, 0.0),
|
||||
('Current Liabilities', 0.0, -200.0, 0.0, 0.0),
|
||||
('Current Liabilities', 0.0, 0.0, 0.0, 0.0),
|
||||
('Payables', 0.0, -200.0, 0.0, 0.0),
|
||||
('Plus Non-current Liabilities', 0.0, 0.0, 0.0, 0.0),
|
||||
('EQUITY', 250.0, 800.0, 250.0, 0.0),
|
||||
('Unallocated Earnings', 250.0, 0.0, 250.0, 0.0),
|
||||
('Current Year Unallocated Earnings', 0.0, 0.0, 250.0, 0.0),
|
||||
('Previous Years Unallocated Earnings', 250.0, 0.0, 0.0, 0.0),
|
||||
('Retained Earnings', 0.0, 800.0, 0.0, 0.0),
|
||||
('Current Year Retained Earnings', 0.0, 800.0, 0.0, 0.0),
|
||||
('Previous Years Retained Earnings', 0.0, 0.0, 0.0, 0.0),
|
||||
('LIABILITIES + EQUITY', 250.0, 600.0, 250.0, 0.0),
|
||||
],
|
||||
)
|
||||
|
||||
def test_hide_if_zero_with_no_formulas(self):
|
||||
"""
|
||||
Check if a report line stays displayed when hide_if_zero is True and no formulas
|
||||
is set on the line but has some child which have balance != 0
|
||||
We check also if the line is hidden when all its children have balance == 0
|
||||
"""
|
||||
account1, account2 = self.env['account.account'].create([{
|
||||
'name': "test_financial_report_1",
|
||||
'code': "42241",
|
||||
'account_type': "asset_fixed",
|
||||
}, {
|
||||
'name': "test_financial_report_2",
|
||||
'code': "42242",
|
||||
'account_type': "asset_fixed",
|
||||
}])
|
||||
|
||||
moves = self.env['account.move'].create([
|
||||
{
|
||||
'move_type': 'entry',
|
||||
'date': '2019-04-01',
|
||||
'line_ids': [
|
||||
(0, 0, {'debit': 3.0, 'credit': 0.0, 'account_id': account1.id}),
|
||||
(0, 0, {'debit': 0.0, 'credit': 3.0, 'account_id': self.company_data['default_account_revenue'].id}),
|
||||
],
|
||||
},
|
||||
{
|
||||
'move_type': 'entry',
|
||||
'date': '2019-05-01',
|
||||
'line_ids': [
|
||||
(0, 0, {'debit': 0.0, 'credit': 1.0, 'account_id': account2.id}),
|
||||
(0, 0, {'debit': 1.0, 'credit': 0.0, 'account_id': self.company_data['default_account_revenue'].id}),
|
||||
],
|
||||
},
|
||||
{
|
||||
'move_type': 'entry',
|
||||
'date': '2019-04-01',
|
||||
'line_ids': [
|
||||
(0, 0, {'debit': 0.0, 'credit': 3.0, 'account_id': account2.id}),
|
||||
(0, 0, {'debit': 3.0, 'credit': 0.0, 'account_id': self.company_data['default_account_revenue'].id}),
|
||||
],
|
||||
},
|
||||
])
|
||||
moves.action_post()
|
||||
moves.line_ids.flush_recordset()
|
||||
|
||||
report = self.env["account.report"].create({
|
||||
'name': "test_financial_report_sum",
|
||||
'column_ids': [
|
||||
Command.create({
|
||||
'name': "Balance",
|
||||
'expression_label': 'balance',
|
||||
'sequence': 1,
|
||||
}),
|
||||
],
|
||||
'line_ids': [
|
||||
Command.create({
|
||||
'name': "Title",
|
||||
'code': 'TT',
|
||||
'hide_if_zero': True,
|
||||
'sequence': 0,
|
||||
'children_ids': [
|
||||
Command.create({
|
||||
'name': "report_line_1",
|
||||
'code': 'TEST_L1',
|
||||
'sequence': 1,
|
||||
'expression_ids': [
|
||||
Command.create({
|
||||
'label': 'balance',
|
||||
'engine': 'domain',
|
||||
'formula': f"[('account_id', '=', {account1.id})]",
|
||||
'subformula': 'sum',
|
||||
'date_scope': 'from_beginning',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
Command.create({
|
||||
'name': "report_line_2",
|
||||
'code': 'TEST_L2',
|
||||
'sequence': 2,
|
||||
'expression_ids': [
|
||||
Command.create({
|
||||
'label': 'balance',
|
||||
'engine': 'domain',
|
||||
'formula': f"[('account_id', '=', {account2.id})]",
|
||||
'subformula': 'sum',
|
||||
'date_scope': 'from_beginning',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
]
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
# TODO without this, the create() puts newIds in the sublines, and flushing doesn't help. Seems to be an ORM bug.
|
||||
self.env.invalidate_all()
|
||||
|
||||
options = self._generate_options(report, fields.Date.from_string('2019-05-01'), fields.Date.from_string('2019-05-01'))
|
||||
options = self._update_comparison_filter(options, report, 'previous_period', 2)
|
||||
|
||||
self.assertLinesValues(
|
||||
report._get_lines(options),
|
||||
[ 0, 1, 2, 3],
|
||||
[
|
||||
("Title", '', '', ''),
|
||||
("report_line_1", 3.0, 3.0, 0.0),
|
||||
("report_line_2", -4.0, -3.0, 0.0),
|
||||
],
|
||||
options,
|
||||
)
|
||||
|
||||
move = self.env['account.move'].create({
|
||||
'move_type': 'entry',
|
||||
'date': '2019-05-01',
|
||||
'line_ids': [
|
||||
(0, 0, {'debit': 0.0, 'credit': 3.0, 'account_id': account1.id}),
|
||||
(0, 0, {'debit': 4.0, 'credit': 0.0, 'account_id': account2.id}),
|
||||
(0, 0, {'debit': 0.0, 'credit': 1.0, 'account_id': self.company_data['default_account_revenue'].id}),
|
||||
],
|
||||
})
|
||||
|
||||
move.action_post()
|
||||
move.line_ids.flush_recordset()
|
||||
|
||||
# With the comparison still on, the lines shouldn't be hidden
|
||||
self.assertLinesValues(
|
||||
report._get_lines(options),
|
||||
[ 0, 1, 2, 3],
|
||||
[
|
||||
("Title", '', '', ''),
|
||||
("report_line_1", 0.0, 3.0, 0.0),
|
||||
("report_line_2", 0.0, -3.0, 0.0),
|
||||
],
|
||||
options,
|
||||
)
|
||||
|
||||
# Removing the comparison should hide the lines, as they will be 0 in every considered period (the current one)
|
||||
options = self._update_comparison_filter(options, report, 'previous_period', 0)
|
||||
self.assertLinesValues(report._get_lines(options), [0, 1, 2, 3], [], options)
|
||||
|
||||
def test_option_hierarchy(self):
|
||||
""" Check that the report lines are correct when the option "Hierarchy and subtotals is ticked"""
|
||||
self.env['account.group'].create({
|
||||
'name': 'Sales',
|
||||
'code_prefix_start': '40',
|
||||
'code_prefix_end': '49',
|
||||
})
|
||||
|
||||
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('fusion_accounting.profit_and_loss')
|
||||
line_id = self._get_basic_line_dict_id_from_report_line_ref('fusion_accounting.account_financial_report_revenue0')
|
||||
options = self._generate_options(profit_and_loss_report, '2020-02-01', '2020-02-28')
|
||||
options['unfolded_lines'] = [line_id]
|
||||
options['hierarchy'] = True
|
||||
self.env.company.totals_below_sections = False
|
||||
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': '40-49 Sales'},
|
||||
{'level': 3, 'name': '400000 Product Sales'},
|
||||
]
|
||||
)
|
||||
|
||||
def test_option_hierarchy_with_no_group_lines(self):
|
||||
""" Check that the report lines of 'No Group' have correct ids with the option 'Hierarchy and subtotals' """
|
||||
self.env['account.group'].create({
|
||||
'name': 'Sales',
|
||||
'code_prefix_start': '45',
|
||||
'code_prefix_end': '49',
|
||||
})
|
||||
|
||||
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('fusion_accounting.profit_and_loss')
|
||||
line_id = self._get_basic_line_dict_id_from_report_line_ref('fusion_accounting.account_financial_report_revenue0')
|
||||
options = self._generate_options(profit_and_loss_report, '2020-02-01', '2020-02-28')
|
||||
options['unfolded_lines'] = [line_id]
|
||||
options['hierarchy'] = True
|
||||
self.env.company.totals_below_sections = False
|
||||
lines = profit_and_loss_report._get_lines(options)
|
||||
lines_array = [{'name': line['name'], 'level': line['level']} for line in lines]
|
||||
|
||||
self.assertEqual(
|
||||
lines_array,
|
||||
[
|
||||
{'name': 'Revenue', 'level': 1},
|
||||
{'name': '(No Group)', 'level': 2},
|
||||
{'name': '400000 Product Sales', 'level': 3},
|
||||
{'name': 'Less Costs of Revenue', 'level': 1},
|
||||
{'name': 'Gross Profit', 'level': 0},
|
||||
{'name': 'Less Operating Expenses', 'level': 1},
|
||||
{'name': 'Operating Income (or Loss)', 'level': 0},
|
||||
{'name': 'Plus Other Income', 'level': 1},
|
||||
{'name': 'Less Other Expenses', 'level': 1},
|
||||
{'name': 'Net Profit', 'level': 0},
|
||||
]
|
||||
)
|
||||
|
||||
self.assertEqual(lines[1]['id'], lines[0]['id'] + '|' + '~account.group~')
|
||||
|
||||
def test_parse_line_id(self):
|
||||
line_id_1 = self.env['account.report']._parse_line_id('markup1~account.account~5|markup2~res.partner~8|markup3~~')
|
||||
line_id_2 = self.env['account.report']._parse_line_id('~account.report~14|{"groupby_prefix_group": "~"}~account.report~21')
|
||||
|
||||
self.assertEqual(line_id_1, [('markup1', 'account.account', 5), ('markup2', 'res.partner', 8), ('markup3', None, None)])
|
||||
self.assertEqual(line_id_2, [('', 'account.report', 14), ({"groupby_prefix_group": "~"}, 'account.report', 21)])
|
||||
105
Fusion Accounting/tests/test_import_bank_statement.py
Normal file
105
Fusion Accounting/tests/test_import_bank_statement.py
Normal file
@@ -0,0 +1,105 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Fusion Accounting - Tests. Copyright (C) 2026 Nexa Systems Inc.
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
from odoo import fields
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import file_open
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestAccountBankStatementImportCSV(AccountTestInvoicingCommon):
|
||||
|
||||
def _import_file(self, csv_file_path, csv_fields=False):
|
||||
# Create a bank account and journal corresponding to the CSV file (same currency and account number)
|
||||
bank_journal = self.env['account.journal'].create({
|
||||
'name': 'Bank 123456',
|
||||
'code': 'BNK67',
|
||||
'type': 'bank',
|
||||
'bank_acc_number': '123456',
|
||||
'currency_id': self.env.ref("base.USD").id,
|
||||
})
|
||||
|
||||
# Use an import wizard to process the file
|
||||
with file_open(csv_file_path, 'rb') as csv_file:
|
||||
action = bank_journal.create_document_from_attachment(self.env['ir.attachment'].create({
|
||||
'mimetype': 'text/csv',
|
||||
'name': 'test_csv.csv',
|
||||
'raw': csv_file.read(),
|
||||
}).ids)
|
||||
import_wizard = self.env['base_import.import'].browse(
|
||||
action['params']['context']['wizard_id']
|
||||
).with_context(action['params']['context'])
|
||||
|
||||
import_wizard_options = {
|
||||
'date_format': '%m %d %y',
|
||||
'keep_matches': False,
|
||||
'encoding': 'utf-8',
|
||||
'fields': [],
|
||||
'quoting': '"',
|
||||
'bank_stmt_import': True,
|
||||
'headers': True,
|
||||
'separator': ';',
|
||||
'float_thousand_separator': ',',
|
||||
'float_decimal_separator': '.',
|
||||
'advanced': False,
|
||||
}
|
||||
import_wizard_fields = csv_fields or ['date', False, 'payment_ref', 'amount', 'balance']
|
||||
import_wizard.execute_import(import_wizard_fields, [], import_wizard_options, dryrun=False)
|
||||
|
||||
def test_csv_file_import(self):
|
||||
self._import_file('fusion_accounting/test_csv_file/test_csv.csv')
|
||||
|
||||
# Check the imported bank statement
|
||||
imported_statement = self.env['account.bank.statement'].search([('company_id', '=', self.env.company.id)])
|
||||
self.assertRecordValues(imported_statement, [{
|
||||
'reference': 'test_csv.csv',
|
||||
'balance_start': 21699.55,
|
||||
'balance_end_real': 23462.55,
|
||||
}])
|
||||
self.assertRecordValues(imported_statement.line_ids.sorted(lambda line: (line.date, line.payment_ref)), [
|
||||
{'date': fields.Date.from_string('2015-02-02'), 'amount': 3728.87, 'payment_ref': 'ACH CREDIT"AMERICAN EXPRESS-SETTLEMENT'},
|
||||
{'date': fields.Date.from_string('2015-02-02'), 'amount': -500.08, 'payment_ref': 'DEBIT CARD 6906 EFF 02-01"01/31 INDEED 203-564-2400 CT'},
|
||||
{'date': fields.Date.from_string('2015-02-02'), 'amount': -240.00, 'payment_ref': 'DEBIT CARD 6906 EFF 02-01"01/31 MAILCHIMP MAILCHIMP.COMGA'},
|
||||
{'date': fields.Date.from_string('2015-02-02'), 'amount': -2064.82, 'payment_ref': 'DEBIT CARD 6906"02/02 COMFORT INNS SAN FRANCISCOCA'},
|
||||
{'date': fields.Date.from_string('2015-02-02'), 'amount': -41.64, 'payment_ref': 'DEBIT CARD 6906"BAYSIDE MARKET/1 SAN FRANCISCO CA'},
|
||||
{'date': fields.Date.from_string('2015-02-03'), 'amount': 2500.00, 'payment_ref': 'ACH CREDIT"CHECKFLUID INC -013015'},
|
||||
{'date': fields.Date.from_string('2015-02-03'), 'amount': -25.00, 'payment_ref': 'ACH DEBIT"AUTHNET GATEWAY -BILLING'},
|
||||
{'date': fields.Date.from_string('2015-02-03'), 'amount': -7500.00, 'payment_ref': 'ACH DEBIT"WW 222 BROADWAY -ACH'},
|
||||
{'date': fields.Date.from_string('2015-02-03'), 'amount': -45.86, 'payment_ref': 'DEBIT CARD 6906"02/02 DISTRICT SF SAN FRANCISCOCA'},
|
||||
{'date': fields.Date.from_string('2015-02-03'), 'amount': -1284.33, 'payment_ref': 'DEBIT CARD 6906"02/02 VIR ATL 9327 180-08628621 CT'},
|
||||
{'date': fields.Date.from_string('2015-02-03'), 'amount': -1284.33, 'payment_ref': 'DEBIT CARD 6906"02/02 VIR ATL 9327 180-08628621 CT'},
|
||||
{'date': fields.Date.from_string('2015-02-03'), 'amount': -1284.33, 'payment_ref': 'DEBIT CARD 6906"02/02 VIR ATL 9327 180-08628621 CT'},
|
||||
{'date': fields.Date.from_string('2015-02-03'), 'amount': -1123.33, 'payment_ref': 'DEBIT CARD 6906"02/02 VIR ATL 9327 180-08628621 CT'},
|
||||
{'date': fields.Date.from_string('2015-02-03'), 'amount': -1123.33, 'payment_ref': 'DEBIT CARD 6906"02/02 VIR ATL 9327 180-08628621 CT'},
|
||||
{'date': fields.Date.from_string('2015-02-03'), 'amount': -4344.66, 'payment_ref': 'DEBIT CARD 6906"02/03 IBM USED PC 888S 188-874-6742 NY'},
|
||||
{'date': fields.Date.from_string('2015-02-03'), 'amount': 8366.00, 'payment_ref': 'DEPOSIT-WIRED FUNDS"TVET OPERATING PLLC'},
|
||||
{'date': fields.Date.from_string('2015-02-04'), 'amount': -1284.33, 'payment_ref': 'DEBIT CARD 6906"02/03 VIR ATL 9327 180-08628621 CT'},
|
||||
{'date': fields.Date.from_string('2015-02-04'), 'amount': -204.23, 'payment_ref': 'DEBIT CARD 6906"02/04 GROUPON INC 877-788-7858 IL'},
|
||||
{'date': fields.Date.from_string('2015-02-05'), 'amount': 9518.40, 'payment_ref': 'ACH CREDIT"MERCHE-SOLUTIONS-MERCH DEP'},
|
||||
])
|
||||
|
||||
def test_csv_file_import_with_missing_values(self):
|
||||
self._import_file('fusion_accounting/test_csv_file/test_csv_missing_values.csv', ['transaction_type', 'ref', 'payment_ref', 'debit', 'credit'])
|
||||
|
||||
imported_statement = self.env['account.bank.statement'].search([('company_id', '=', self.env.company.id)])
|
||||
|
||||
self.assertEqual(len(imported_statement.line_ids), 2)
|
||||
|
||||
self.assertRecordValues(imported_statement.line_ids.sorted(lambda line: line.amount), [
|
||||
{'transaction_type': 'TRANSFER', 'ref': 'bank_ref_1', 'payment_ref': 'bank_statement_line_1', 'sequence': 0, 'amount': 1000.0},
|
||||
{'transaction_type': 'TRANSFER', 'ref': False, 'payment_ref': 'bank_statement_line_2', 'sequence': 1, 'amount': 3500.0},
|
||||
])
|
||||
|
||||
def test_csv_file_import_non_ordered(self):
|
||||
with self.assertRaises(UserError):
|
||||
self._import_file('fusion_accounting/test_csv_file/test_csv_non_sorted.csv')
|
||||
|
||||
def test_csv_file_empty_date(self):
|
||||
with self.assertRaises(UserError):
|
||||
self._import_file('fusion_accounting/test_csv_file/test_csv_empty_date.csv')
|
||||
|
||||
def test_csv_file_import_without_amount(self):
|
||||
csv_fields = ['date', False, 'payment_ref', 'balance']
|
||||
with self.assertRaisesRegex(ValidationError, "Make sure that an Amount or Debit and Credit is in the file."):
|
||||
self._import_file('fusion_accounting/test_csv_file/test_csv_without_amount.csv', csv_fields)
|
||||
203
Fusion Accounting/tests/test_prediction.py
Normal file
203
Fusion Accounting/tests/test_prediction.py
Normal file
@@ -0,0 +1,203 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
from odoo import fields, Command
|
||||
from odoo.tests import Form, tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestBillsPrediction(AccountTestInvoicingCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.company.predict_bill_product = True
|
||||
|
||||
cls.test_partners = cls.env['res.partner'].create([{'name': 'test partner %s' % i} for i in range(7)])
|
||||
|
||||
accounts_data = [{
|
||||
'code': 'test%s' % i,
|
||||
'name': name,
|
||||
'account_type': 'expense',
|
||||
} for i, name in enumerate((
|
||||
"Test Maintenance and Repair",
|
||||
"Test Purchase of services, studies and preparatory work",
|
||||
"Test Various Contributions",
|
||||
"Test Rental Charges",
|
||||
"Test Purchase of commodity",
|
||||
))]
|
||||
|
||||
cls.test_accounts = cls.env['account.account'].create(accounts_data)
|
||||
|
||||
cls.frozen_today = fields.Date.today()
|
||||
|
||||
def _create_bill(self, vendor, line_name, expected_account, account_to_set=None, post=True):
|
||||
''' Create a new vendor bill to test the prediction.
|
||||
:param vendor: The vendor to set on the invoice.
|
||||
:param line_name: The name of the invoice line that will be used to predict.
|
||||
:param expected_account: The expected predicted account.
|
||||
:param account_to_set: The optional account to set as a correction of the predicted account.
|
||||
:return: The newly created vendor bill.
|
||||
'''
|
||||
invoice_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
|
||||
invoice_form.partner_id = vendor
|
||||
invoice_form.invoice_date = self.frozen_today
|
||||
with invoice_form.invoice_line_ids.new() as invoice_line_form:
|
||||
# Set the default account to avoid "account_id is a required field" in case of bad configuration.
|
||||
invoice_line_form.account_id = self.company_data['default_journal_purchase'].default_account_id
|
||||
|
||||
invoice_line_form.quantity = 1.0
|
||||
invoice_line_form.price_unit = 42.0
|
||||
invoice_line_form.name = line_name
|
||||
invoice = invoice_form.save()
|
||||
invoice_line = invoice.invoice_line_ids
|
||||
|
||||
self.assertEqual(
|
||||
invoice_line.account_id,
|
||||
expected_account,
|
||||
"Account '%s' should have been predicted instead of '%s'" % (
|
||||
expected_account.display_name,
|
||||
invoice_line.account_id.display_name,
|
||||
),
|
||||
)
|
||||
|
||||
if account_to_set:
|
||||
invoice_line.account_id = account_to_set
|
||||
|
||||
if post:
|
||||
invoice.action_post()
|
||||
return invoice
|
||||
|
||||
def test_account_prediction_flow(self):
|
||||
default_account = self.company_data['default_journal_purchase'].default_account_id
|
||||
self._create_bill(self.test_partners[0], "Maintenance and repair", self.test_accounts[0])
|
||||
self._create_bill(self.test_partners[5], "Subsidies obtained", default_account, account_to_set=self.test_accounts[1])
|
||||
self._create_bill(self.test_partners[6], "Prepare subsidies file", default_account, account_to_set=self.test_accounts[1])
|
||||
self._create_bill(self.test_partners[6], "Prepare subsidies file", self.test_accounts[1])
|
||||
self._create_bill(self.test_partners[1], "Contributions January", self.test_accounts[2])
|
||||
self._create_bill(self.test_partners[2], "Coca-cola", default_account, account_to_set=self.test_accounts[4])
|
||||
self._create_bill(self.test_partners[1], "Contribution February", self.test_accounts[2])
|
||||
self._create_bill(self.test_partners[3], "Electricity Bruxelles", default_account, account_to_set=self.test_accounts[3])
|
||||
self._create_bill(self.test_partners[3], "Electricity Grand-Rosière", self.test_accounts[3])
|
||||
self._create_bill(self.test_partners[2], "Purchase of coca-cola", self.test_accounts[4])
|
||||
self._create_bill(self.test_partners[4], "Crate of coca-cola", default_account, account_to_set=self.test_accounts[4])
|
||||
self._create_bill(self.test_partners[4], "Crate of coca-cola", self.test_accounts[4])
|
||||
self._create_bill(self.test_partners[1], "March", self.test_accounts[2])
|
||||
|
||||
def test_account_prediction_from_label_expected_behavior(self):
|
||||
"""Prevent the prediction from being annoying."""
|
||||
default_account = self.company_data['default_journal_purchase'].default_account_id
|
||||
payable_account = self.company_data['default_account_payable'].copy()
|
||||
payable_account.write({'name': f'Account payable - {self.test_accounts[0].name}'})
|
||||
|
||||
# There is no prior result, we take the default account, but we don't post
|
||||
self._create_bill(self.test_partners[0], self.test_partners[0].name, default_account, post=False)
|
||||
|
||||
# There is no prior result, we take the default account
|
||||
self._create_bill(self.test_partners[0], "Drinks", default_account, account_to_set=self.test_accounts[0])
|
||||
|
||||
# There is only one prior account for the partner, we take that one
|
||||
self._create_bill(self.test_partners[0], "Desert", self.test_accounts[0], account_to_set=self.test_accounts[1])
|
||||
|
||||
# We find something close enough, take that one
|
||||
self._create_bill(self.test_partners[0], "Drinks too", self.test_accounts[0])
|
||||
|
||||
# There is no clear preference for any account (both previous accounts have the same rank)
|
||||
# don't make any prediction and let the default behavior fill the account
|
||||
invoice = self._create_bill(self.test_partners[0], "Main course", default_account)
|
||||
invoice.button_draft()
|
||||
|
||||
with Form(invoice) as move_form:
|
||||
with move_form.invoice_line_ids.edit(0) as line_form:
|
||||
# There isn't any account clearly better than the manually set one, we keep the current one
|
||||
line_form.account_id = self.test_accounts[2]
|
||||
line_form.name = "Apple"
|
||||
self.assertEqual(line_form.account_id, self.test_accounts[2])
|
||||
|
||||
# There is an account that looks clearly better, use it
|
||||
line_form.name = "Second desert"
|
||||
self.assertEqual(line_form.account_id, self.test_accounts[1])
|
||||
|
||||
def test_account_prediction_with_product(self):
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'product_a',
|
||||
'lst_price': 1000.0,
|
||||
'standard_price': 800.0,
|
||||
'property_account_income_id': self.company_data['default_account_revenue'].id,
|
||||
'property_account_expense_id': self.company_data['default_account_expense'].id,
|
||||
})
|
||||
|
||||
invoice_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
|
||||
invoice_form.partner_id = self.test_partners[0]
|
||||
invoice_form.invoice_date = self.frozen_today
|
||||
with invoice_form.invoice_line_ids.new() as invoice_line_form:
|
||||
invoice_line_form.product_id = product
|
||||
invoice_line_form.name = "Maintenance and repair"
|
||||
invoice = invoice_form.save()
|
||||
|
||||
self.assertRecordValues(invoice.invoice_line_ids, [{
|
||||
'name': "Maintenance and repair",
|
||||
'product_id': product.id,
|
||||
'account_id': self.company_data['default_account_expense'].id,
|
||||
}])
|
||||
|
||||
def test_product_prediction_price_subtotal_computation(self):
|
||||
invoice_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
|
||||
invoice_form.partner_id = self.test_partners[0]
|
||||
invoice_form.invoice_date = self.frozen_today
|
||||
with invoice_form.invoice_line_ids.new() as invoice_line_form:
|
||||
invoice_line_form.product_id = self.product_a
|
||||
invoice = invoice_form.save()
|
||||
invoice.action_post()
|
||||
|
||||
self.product_a.supplier_taxes_id = [Command.set(self.tax_purchase_b.ids)]
|
||||
|
||||
invoice_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
|
||||
invoice_form.partner_id = self.test_partners[0]
|
||||
invoice_form.invoice_date = self.frozen_today
|
||||
with invoice_form.invoice_line_ids.new() as invoice_line_form:
|
||||
invoice_line_form.name = 'product_a'
|
||||
invoice = invoice_form.save()
|
||||
|
||||
self.assertRecordValues(invoice.invoice_line_ids, [{
|
||||
'quantity': 1.0,
|
||||
'price_unit': 800.0,
|
||||
'price_subtotal': 800.0,
|
||||
'balance': 800.0,
|
||||
'tax_ids': self.tax_purchase_b.ids,
|
||||
}])
|
||||
|
||||
# In case a unit price is already set we do not update the unit price
|
||||
invoice_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
|
||||
invoice_form.partner_id = self.test_partners[0]
|
||||
invoice_form.invoice_date = self.frozen_today
|
||||
with invoice_form.invoice_line_ids.new() as invoice_line_form:
|
||||
invoice_line_form.price_unit = 42.0
|
||||
invoice_line_form.name = 'product_a'
|
||||
invoice = invoice_form.save()
|
||||
|
||||
self.assertRecordValues(invoice.invoice_line_ids, [{
|
||||
'quantity': 1.0,
|
||||
'price_unit': 42.0,
|
||||
'price_subtotal': 42.0,
|
||||
'balance': 42.0,
|
||||
'tax_ids': self.tax_purchase_b.ids,
|
||||
}])
|
||||
|
||||
# In case a tax is already set we do not update the taxes
|
||||
invoice_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
|
||||
invoice_form.partner_id = self.test_partners[0]
|
||||
invoice_form.invoice_date = self.frozen_today
|
||||
with invoice_form.invoice_line_ids.new() as invoice_line_form:
|
||||
invoice_line_form.tax_ids = self.tax_purchase_a
|
||||
invoice_line_form.name = 'product_a'
|
||||
invoice = invoice_form.save()
|
||||
|
||||
self.assertRecordValues(invoice.invoice_line_ids, [{
|
||||
'quantity': 1.0,
|
||||
'price_unit': 800.0,
|
||||
'price_subtotal': 800.0,
|
||||
'balance': 800.0,
|
||||
'tax_ids': self.tax_purchase_a.ids,
|
||||
}])
|
||||
1306
Fusion Accounting/tests/test_reconciliation_matching_rules.py
Normal file
1306
Fusion Accounting/tests/test_reconciliation_matching_rules.py
Normal file
File diff suppressed because it is too large
Load Diff
83
Fusion Accounting/tests/test_reconciliation_widget.py
Normal file
83
Fusion Accounting/tests/test_reconciliation_widget.py
Normal file
@@ -0,0 +1,83 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Fusion Accounting - Tests. Copyright (C) 2026 Nexa Systems Inc.
|
||||
|
||||
from odoo.addons.stock_account.tests.test_anglo_saxon_valuation_reconciliation_common import ValuationReconciliationTestCommon
|
||||
from odoo.tests.common import tagged
|
||||
|
||||
|
||||
@tagged("post_install", "-at_install")
|
||||
class TestReconciliationWidget(ValuationReconciliationTestCommon):
|
||||
|
||||
def test_no_stock_account_in_reconciliation_proposition(self):
|
||||
"""
|
||||
We check if no stock interim account is present in the reconcialiation proposition,
|
||||
with both standard and custom stock accounts
|
||||
"""
|
||||
avco_1 = self.stock_account_product_categ.copy({'property_cost_method': 'average'})
|
||||
|
||||
# We need a product category with custom stock accounts
|
||||
avco_2 = self.stock_account_product_categ.copy({
|
||||
'property_cost_method': 'average',
|
||||
'property_stock_account_input_categ_id': self.company_data['default_account_stock_in'].copy().id,
|
||||
'property_stock_account_output_categ_id': self.company_data['default_account_stock_out'].copy().id,
|
||||
'property_stock_journal': avco_1.property_stock_journal.copy().id,
|
||||
'property_stock_valuation_account_id': self.company_data['default_account_stock_valuation'].copy().id
|
||||
})
|
||||
|
||||
move_1, move_2 = self.env['account.move'].create([
|
||||
{
|
||||
'move_type': 'entry',
|
||||
'name': 'Entry 1',
|
||||
'journal_id': avco_1.property_stock_journal.id,
|
||||
'line_ids': [
|
||||
(0, 0, {
|
||||
'account_id': avco_1.property_stock_account_input_categ_id.id,
|
||||
'debit': 0.0,
|
||||
'credit': 100.0
|
||||
}),
|
||||
(0, 0, {
|
||||
'account_id': avco_1.property_stock_valuation_account_id.id,
|
||||
'debit': 100.0,
|
||||
'credit': 0.0
|
||||
})
|
||||
]
|
||||
},
|
||||
{
|
||||
'move_type': 'entry',
|
||||
'name': 'Entry 2',
|
||||
'journal_id': avco_2.property_stock_journal.id,
|
||||
'line_ids': [
|
||||
(0, 0, {
|
||||
'account_id': avco_2.property_stock_account_input_categ_id.id,
|
||||
'debit': 0.0,
|
||||
'credit': 100.0
|
||||
}),
|
||||
(0, 0, {
|
||||
'account_id': avco_2.property_stock_valuation_account_id.id,
|
||||
'debit': 100.0,
|
||||
'credit': 0.0
|
||||
})
|
||||
]
|
||||
},
|
||||
])
|
||||
|
||||
(move_1 + move_2).action_post()
|
||||
|
||||
statement = self.env['account.bank.statement'].create({
|
||||
'balance_start': 0.0,
|
||||
'balance_end_real': -100.0,
|
||||
'line_ids': [(0, 0, {
|
||||
'payment_ref': 'test',
|
||||
'amount': -100.0,
|
||||
'journal_id': self.company_data['default_journal_bank'].id,
|
||||
})]
|
||||
})
|
||||
|
||||
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=statement.line_ids.id).new({})
|
||||
amls = self.env['account.move.line'].search(wizard._prepare_embedded_views_data()['amls']['domain'])
|
||||
stock_accounts = (
|
||||
avco_1.property_stock_account_input_categ_id + avco_2.property_stock_account_input_categ_id
|
||||
+ avco_1.property_stock_account_output_categ_id + avco_2.property_stock_account_output_categ_id
|
||||
)
|
||||
stock_res = [line for line in amls if line.account_id in stock_accounts]
|
||||
self.assertEqual(len(stock_res), 0)
|
||||
1643
Fusion Accounting/tests/test_reevaluation_asset.py
Normal file
1643
Fusion Accounting/tests/test_reevaluation_asset.py
Normal file
File diff suppressed because it is too large
Load Diff
87
Fusion Accounting/tests/test_signature.py
Normal file
87
Fusion Accounting/tests/test_signature.py
Normal file
@@ -0,0 +1,87 @@
|
||||
import base64
|
||||
|
||||
from odoo import Command
|
||||
from odoo.tests import tagged
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestInvoiceSignature(AccountTestInvoicingCommon):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
if cls.env.ref('base.module_sign').state != 'installed':
|
||||
cls.skipTest(cls, "`sign` module not installed")
|
||||
|
||||
cls.env.company.sign_invoice = True
|
||||
|
||||
cls.signature_fake_1 = base64.b64encode(b"fake_signature_1")
|
||||
cls.signature_fake_2 = base64.b64encode(b"fake_signature_2")
|
||||
|
||||
cls.user.sign_signature = cls.signature_fake_1
|
||||
cls.another_user = cls.env['res.users'].create({
|
||||
'name': 'another accountant',
|
||||
'login': 'another_accountant',
|
||||
'password': 'another_accountant',
|
||||
'groups_id': [
|
||||
Command.set(cls.env.user.groups_id.ids),
|
||||
],
|
||||
'sign_signature': cls.signature_fake_2,
|
||||
})
|
||||
|
||||
cls.invoice = cls.env['account.move'].create({
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': cls.partner_a.id,
|
||||
'journal_id': cls.company_data['default_journal_sale'].id,
|
||||
'invoice_line_ids': [
|
||||
Command.create({
|
||||
'product_id': cls.product_a.id,
|
||||
'quantity': 1,
|
||||
'price_unit': 1,
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
def test_draft_invoice_shouldnt_have_signature(self):
|
||||
self.assertEqual(self.invoice.state, 'draft')
|
||||
self.assertFalse(self.invoice.show_signature_area, "the signature area shouldn't appear on a draft invoice")
|
||||
|
||||
def test_posted_invoice_should_have_signature(self):
|
||||
self.invoice.action_post()
|
||||
self.assertTrue(self.invoice.show_signature_area,
|
||||
"the signature area should appear on posted invoice when the `sign_invoice` settings is True")
|
||||
|
||||
def test_invoice_from_company_without_signature_settings_shouldnt_have_signature(self):
|
||||
self.env.company.sign_invoice = False
|
||||
self.invoice.action_post()
|
||||
self.assertFalse(self.invoice.show_signature_area,
|
||||
"the signature area shouldn't appear when the `sign_invoice` settings is False")
|
||||
|
||||
def test_invoice_signing_user_should_be_the_user_that_posted_it(self):
|
||||
self.assertFalse(self.invoice.signing_user,
|
||||
"invoice that weren't created by automated action shouldn't have a signing user")
|
||||
self.assertEqual(self.invoice.signature, False, "There shouldn't be any signature if there isn't a signing user")
|
||||
self.invoice.action_post()
|
||||
self.assertEqual(self.invoice.signing_user, self.user, "The signing user should be the user that posted the invoice")
|
||||
self.assertEqual(self.invoice.signature, self.signature_fake_1, "The signature should be from `self.user`")
|
||||
|
||||
self.invoice.button_draft()
|
||||
self.invoice.with_user(self.another_user).action_post()
|
||||
self.assertEqual(self.invoice.signing_user, self.another_user,
|
||||
"The signing user should be the user that posted the invoice")
|
||||
self.assertEqual(self.invoice.signature, self.signature_fake_2, "The signature should be from `self.another_user`")
|
||||
|
||||
def test_invoice_signing_user_should_be_reprensative_user_if_there_is_one(self):
|
||||
self.env.company.signing_user = self.another_user # set the representative user of the company
|
||||
self.invoice.action_post()
|
||||
self.assertEqual(self.invoice.signing_user, self.another_user, "The signing user should be the representative person set in the settings")
|
||||
self.assertEqual(self.invoice.signature, self.signature_fake_2, "The signature should be from `self.another_user`, the representative user")
|
||||
|
||||
def test_setting_representative_user_shouldnt_change_signer_of_already_posted_invoice(self):
|
||||
# Note: Changing this behavior might not be a good idea as having all account.move updated at once
|
||||
# would be very costly
|
||||
self.invoice.action_post()
|
||||
self.env.company.signing_user = self.another_user # set the representative user of the company
|
||||
self.assertEqual(self.invoice.signing_user, self.user,
|
||||
"The signing user should be the one that posted the invoice even if a representative has been added later on")
|
||||
self.assertEqual(self.invoice.signature, self.signature_fake_1, "The signature should be from `self.user`")
|
||||
48
Fusion Accounting/tests/test_tour.py
Normal file
48
Fusion Accounting/tests/test_tour.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# Fusion Accounting - Tests. Copyright (C) 2026 Nexa Systems Inc.
|
||||
|
||||
import odoo.tests
|
||||
|
||||
from odoo import Command
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingHttpCommon
|
||||
|
||||
|
||||
@odoo.tests.tagged('post_install_l10n', 'post_install', '-at_install')
|
||||
class TestAccountantTours(AccountTestInvoicingHttpCommon):
|
||||
def test_account_merge_wizard_tour(self):
|
||||
companies = self.env['res.company'].create([
|
||||
{'name': 'tour_company_1'},
|
||||
{'name': 'tour_company_2'},
|
||||
])
|
||||
|
||||
self.env['account.account'].create([
|
||||
{
|
||||
'company_ids': [Command.set(companies[0].ids)],
|
||||
'code': "100001",
|
||||
'name': "Current Assets",
|
||||
'account_type': 'asset_current',
|
||||
},
|
||||
{
|
||||
'company_ids': [Command.set(companies[0].ids)],
|
||||
'code': "100002",
|
||||
'name': "Non-Current Assets",
|
||||
'account_type': 'asset_non_current',
|
||||
},
|
||||
{
|
||||
'company_ids': [Command.set(companies[1].ids)],
|
||||
'code': "200001",
|
||||
'name': "Current Assets",
|
||||
'account_type': 'asset_current',
|
||||
},
|
||||
{
|
||||
'company_ids': [Command.set(companies[1].ids)],
|
||||
'code': "200002",
|
||||
'name': "Non-Current Assets",
|
||||
'account_type': 'asset_non_current',
|
||||
},
|
||||
])
|
||||
|
||||
self.env.ref('base.user_admin').write({
|
||||
'company_id': companies[0].id,
|
||||
'company_ids': [Command.set(companies.ids)],
|
||||
})
|
||||
self.start_tour("/odoo", 'account_merge_wizard_tour', login="admin", cookies={"cids": f"{companies[0].id}-{companies[1].id}"})
|
||||
64
Fusion Accounting/tests/test_ui.py
Normal file
64
Fusion Accounting/tests/test_ui.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Fusion Accounting - Tests. Copyright (C) 2026 Nexa Systems Inc.
|
||||
|
||||
import logging
|
||||
|
||||
from odoo import Command, fields
|
||||
from odoo.addons.account.tests.common import AccountTestMockOnlineSyncCommon
|
||||
import odoo.tests
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@odoo.tests.tagged('-at_install', 'post_install')
|
||||
class TestUi(AccountTestMockOnlineSyncCommon):
|
||||
def test_accountant_tour(self):
|
||||
# Reset country and fiscal country, so that fields added by localizations are
|
||||
# hidden and non-required, and don't make the tour crash.
|
||||
# Also remove default taxes from the company and its accounts, to avoid inconsistencies
|
||||
# with empty fiscal country.
|
||||
self.env.company.write({
|
||||
'country_id': None, # Also resets account_fiscal_country_id
|
||||
'account_sale_tax_id': None,
|
||||
'account_purchase_tax_id': None,
|
||||
})
|
||||
|
||||
# An unconfigured bank journal is required for the connect bank step
|
||||
self.env['account.journal'].create({
|
||||
'type': 'bank',
|
||||
'name': 'Empty Bank',
|
||||
'code': 'EBJ',
|
||||
})
|
||||
|
||||
account_with_taxes = self.env['account.account'].search([('tax_ids', '!=', False), ('company_ids', '=', self.env.company.id)])
|
||||
account_with_taxes.write({
|
||||
'tax_ids': [Command.clear()],
|
||||
})
|
||||
# This tour doesn't work with demo data on runbot
|
||||
all_moves = self.env['account.move'].search([('company_id', '=', self.env.company.id), ('move_type', '!=', 'entry')])
|
||||
all_moves.filtered(lambda m: not m.inalterable_hash and not m.deferred_move_ids and m.state != 'draft').button_draft()
|
||||
all_moves.with_context(force_delete=True).unlink()
|
||||
# We need at least two bank statement lines to reconcile for the tour.
|
||||
bnk = self.env['account.account'].create({
|
||||
'code': 'X1014',
|
||||
'name': 'Bank Current Account - (test)',
|
||||
'account_type': 'asset_cash',
|
||||
})
|
||||
journal = self.env['account.journal'].create({
|
||||
'name': 'Bank - Test',
|
||||
'code': 'TBNK',
|
||||
'type': 'bank',
|
||||
'default_account_id': bnk.id,
|
||||
})
|
||||
self.env['account.bank.statement.line'].create([{
|
||||
'journal_id': journal.id,
|
||||
'amount': 100,
|
||||
'date': fields.Date.today(),
|
||||
'payment_ref': 'stl_0001',
|
||||
}, {
|
||||
'journal_id': journal.id,
|
||||
'amount': 200,
|
||||
'date': fields.Date.today(),
|
||||
'payment_ref': 'stl_0002',
|
||||
}])
|
||||
self.start_tour("/odoo", 'fusion_accounting_tour', login="admin")
|
||||
Reference in New Issue
Block a user