mirror of
https://github.com/bringout/oca-report.git
synced 2026-04-18 09:22:02 +02:00
Initial commit: OCA Report packages (45 packages)
This commit is contained in:
commit
2f4db400df
2543 changed files with 469120 additions and 0 deletions
|
|
@ -0,0 +1,17 @@
|
|||
# Copyright 2014 ACSONE SA/NV (<http://acsone.eu>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from . import test_accounting_none
|
||||
from . import test_aep
|
||||
from . import test_multi_company_aep
|
||||
from . import test_aggregate
|
||||
from . import test_data_sources
|
||||
from . import test_kpi_data
|
||||
from . import test_mis_report_instance
|
||||
from . import test_mis_safe_eval
|
||||
from . import test_period_dates
|
||||
from . import test_render
|
||||
from . import test_simple_array
|
||||
from . import test_target_move
|
||||
from . import test_utc_midnight
|
||||
from . import test_mis_report_instance_annotation
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
# Copyright 2017 ACSONE SA/NV (<http://acsone.eu>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
import doctest
|
||||
|
||||
from odoo.tests import BaseCase, tagged
|
||||
|
||||
|
||||
def _zip(iter1, iter2):
|
||||
i = 0
|
||||
iter1 = iter(iter1)
|
||||
iter2 = iter(iter2)
|
||||
while True:
|
||||
i1 = next(iter1, None)
|
||||
i2 = next(iter2, None)
|
||||
if i1 is None and i2 is None:
|
||||
return
|
||||
yield i, i1, i2
|
||||
i += 1
|
||||
|
||||
|
||||
def assert_matrix(matrix, expected):
|
||||
for i, row, expected_row in _zip(matrix.iter_rows(), expected):
|
||||
if row is None and expected_row is not None:
|
||||
raise AssertionError("not enough rows")
|
||||
if row is not None and expected_row is None:
|
||||
raise AssertionError("too many rows")
|
||||
for j, cell, expected_val in _zip(row.iter_cells(), expected_row):
|
||||
assert (
|
||||
cell and cell.val
|
||||
) == expected_val, "{} != {} in row {} col {}".format(
|
||||
cell and cell.val, expected_val, i, j
|
||||
)
|
||||
|
||||
|
||||
@tagged("doctest")
|
||||
class OdooDocTestCase(BaseCase):
|
||||
"""
|
||||
We need a custom DocTestCase class in order to:
|
||||
- define test_tags to run as part of standard tests
|
||||
- output a more meaningful test name than default "DocTestCase.runTest"
|
||||
"""
|
||||
|
||||
__qualname__ = "doctests for "
|
||||
|
||||
def __init__(self, test):
|
||||
self.__test = test
|
||||
self.__name = test._dt_test.name
|
||||
super().__init__(self.__name)
|
||||
|
||||
def __getattr__(self, item):
|
||||
if item == self.__name:
|
||||
return self.__test
|
||||
|
||||
|
||||
def load_doctests(module):
|
||||
"""
|
||||
Generates a tests loading method for the doctests of the given module
|
||||
https://docs.python.org/3/library/unittest.html#load-tests-protocol
|
||||
"""
|
||||
|
||||
def load_tests(loader, tests, ignore):
|
||||
for test in doctest.DocTestSuite(module):
|
||||
tests.addTest(OdooDocTestCase(test))
|
||||
return tests
|
||||
|
||||
return load_tests
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
from odoo import models
|
||||
|
||||
|
||||
class MisKpiDataTestItem(models.Model):
|
||||
_name = "mis.kpi.data.test.item"
|
||||
_inherit = "mis.kpi.data"
|
||||
_description = "MIS Kpi Data test item"
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
# Copyright 2014 ACSONE SA/NV (<http://acsone.eu>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
|
||||
from ..models import accounting_none
|
||||
from .common import load_doctests
|
||||
|
||||
load_tests = load_doctests(accounting_none)
|
||||
|
|
@ -0,0 +1,467 @@
|
|||
# Copyright 2014 ACSONE SA/NV (<http://acsone.eu>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
import datetime
|
||||
import time
|
||||
|
||||
import odoo.tests.common as common
|
||||
from odoo import fields
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tools.safe_eval import safe_eval
|
||||
|
||||
from ..models import aep
|
||||
from ..models.accounting_none import AccountingNone
|
||||
from ..models.aep import AccountingExpressionProcessor as AEP
|
||||
from ..models.aep import _is_domain
|
||||
from .common import load_doctests
|
||||
|
||||
load_tests = load_doctests(aep)
|
||||
|
||||
|
||||
class TestAEP(common.TransactionCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.res_company = self.env["res.company"]
|
||||
self.account_model = self.env["account.account"]
|
||||
self.move_model = self.env["account.move"]
|
||||
self.journal_model = self.env["account.journal"]
|
||||
self.curr_year = datetime.date.today().year
|
||||
self.prev_year = self.curr_year - 1
|
||||
# create company
|
||||
self.company = self.res_company.create({"name": "AEP Company"})
|
||||
# create receivable bs account
|
||||
self.account_ar = self.account_model.create(
|
||||
{
|
||||
"company_id": self.company.id,
|
||||
"code": "400AR",
|
||||
"name": "Receivable",
|
||||
"account_type": "asset_receivable",
|
||||
"reconcile": True,
|
||||
}
|
||||
)
|
||||
# create income pl account
|
||||
self.account_in = self.account_model.create(
|
||||
{
|
||||
"company_id": self.company.id,
|
||||
"code": "700IN",
|
||||
"name": "Income",
|
||||
"account_type": "income",
|
||||
}
|
||||
)
|
||||
self.account_in_no_data = self.account_model.create(
|
||||
{
|
||||
"company_id": self.company.id,
|
||||
"code": "700INNODATA",
|
||||
"name": "Income (no data)",
|
||||
"account_type": "income",
|
||||
}
|
||||
)
|
||||
# create journal
|
||||
self.journal = self.journal_model.create(
|
||||
{
|
||||
"company_id": self.company.id,
|
||||
"name": "Sale journal",
|
||||
"code": "VEN",
|
||||
"type": "sale",
|
||||
}
|
||||
)
|
||||
# create move in December last year
|
||||
self._create_move(
|
||||
date=datetime.date(self.prev_year, 12, 1),
|
||||
amount=100,
|
||||
debit_acc=self.account_ar,
|
||||
credit_acc=self.account_in,
|
||||
)
|
||||
# create move in January this year
|
||||
self._create_move(
|
||||
date=datetime.date(self.curr_year, 1, 1),
|
||||
amount=300,
|
||||
debit_acc=self.account_ar,
|
||||
credit_acc=self.account_in,
|
||||
credit_quantity=3,
|
||||
)
|
||||
# create move in March this year
|
||||
self._create_move(
|
||||
date=datetime.date(self.curr_year, 3, 1),
|
||||
amount=500,
|
||||
debit_acc=self.account_ar,
|
||||
credit_acc=self.account_in,
|
||||
)
|
||||
# create the AEP, and prepare the expressions we'll need
|
||||
self.aep = AEP(self.company)
|
||||
self.aep.parse_expr("bali[]")
|
||||
self.aep.parse_expr("bale[]")
|
||||
self.aep.parse_expr("balp[]")
|
||||
self.aep.parse_expr("balu[]")
|
||||
self.aep.parse_expr("bali[700IN]")
|
||||
self.aep.parse_expr("bale[700IN]")
|
||||
self.aep.parse_expr("balp[700IN]")
|
||||
self.aep.parse_expr("balp[700NA]") # account that does not exist
|
||||
self.aep.parse_expr("bali[400AR]")
|
||||
self.aep.parse_expr("bale[400AR]")
|
||||
self.aep.parse_expr("balp[400AR]")
|
||||
self.aep.parse_expr("debp[400A%]")
|
||||
self.aep.parse_expr("crdp[700I%]")
|
||||
self.aep.parse_expr("bali[400%]")
|
||||
self.aep.parse_expr("bale[700%]")
|
||||
self.aep.parse_expr("balp[700I%]")
|
||||
self.aep.parse_expr("fldp.quantity[700%]")
|
||||
self.aep.parse_expr("balp[]" "[('account_id.code', '=', '400AR')]")
|
||||
self.aep.parse_expr(
|
||||
"balp[]" "[('account_id.account_type', '=', " " 'asset_receivable')]"
|
||||
)
|
||||
self.aep.parse_expr("balp[('account_type', '=', " " 'asset_receivable')]")
|
||||
self.aep.parse_expr(
|
||||
"balp['&', "
|
||||
" ('account_type', '=', "
|
||||
" 'asset_receivable'), "
|
||||
" ('code', '=', '400AR')]"
|
||||
)
|
||||
self.aep.parse_expr("bal_700IN") # deprecated
|
||||
self.aep.parse_expr("bals[700IN]") # deprecated
|
||||
|
||||
def _create_move(
|
||||
self, date, amount, debit_acc, credit_acc, post=True, credit_quantity=0
|
||||
):
|
||||
move = self.move_model.create(
|
||||
{
|
||||
"journal_id": self.journal.id,
|
||||
"date": fields.Date.to_string(date),
|
||||
"line_ids": [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"name": "/",
|
||||
"debit": amount,
|
||||
"account_id": debit_acc.id,
|
||||
},
|
||||
),
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"name": "/",
|
||||
"credit": amount,
|
||||
"account_id": credit_acc.id,
|
||||
"quantity": credit_quantity,
|
||||
},
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
if post:
|
||||
move._post()
|
||||
return move
|
||||
|
||||
def _do_queries(self, date_from, date_to):
|
||||
self.aep.do_queries(
|
||||
date_from=fields.Date.to_string(date_from),
|
||||
date_to=fields.Date.to_string(date_to),
|
||||
)
|
||||
|
||||
def _eval(self, expr):
|
||||
eval_dict = {"AccountingNone": AccountingNone}
|
||||
return safe_eval(self.aep.replace_expr(expr), eval_dict)
|
||||
|
||||
def _eval_by_account_id(self, expr):
|
||||
res = {}
|
||||
eval_dict = {"AccountingNone": AccountingNone}
|
||||
for account_id, replaced_exprs in self.aep.replace_exprs_by_account_id([expr]):
|
||||
res[account_id] = safe_eval(replaced_exprs[0], eval_dict)
|
||||
return res
|
||||
|
||||
def test_sanity_check(self):
|
||||
self.assertEqual(self.company.fiscalyear_last_day, 31)
|
||||
self.assertEqual(self.company.fiscalyear_last_month, "12")
|
||||
|
||||
def test_parse_expr_error_handling(self):
|
||||
aep = AEP(self.company)
|
||||
with self.assertRaises(UserError) as cm:
|
||||
aep.parse_expr("fldi.quantity[700%]")
|
||||
self.assertIn(
|
||||
"`fld` can only be used with mode `p` (variation)", str(cm.exception)
|
||||
)
|
||||
with self.assertRaises(UserError) as cm:
|
||||
aep.parse_expr("fldp[700%]")
|
||||
self.assertIn("`fld` must have a field name", str(cm.exception))
|
||||
with self.assertRaises(UserError) as cm:
|
||||
aep.parse_expr("balp.quantity[700%]")
|
||||
self.assertIn("`bal` cannot have a field name", str(cm.exception))
|
||||
|
||||
def test_aep_basic(self):
|
||||
self.aep.done_parsing()
|
||||
# let's query for december
|
||||
self._do_queries(
|
||||
datetime.date(self.prev_year, 12, 1), datetime.date(self.prev_year, 12, 31)
|
||||
)
|
||||
# initial balance must be None
|
||||
self.assertIs(self._eval("bali[400AR]"), AccountingNone)
|
||||
self.assertIs(self._eval("bali[700IN]"), AccountingNone)
|
||||
# check variation
|
||||
self.assertEqual(self._eval("balp[400AR]"), 100)
|
||||
self.assertEqual(self._eval("balp[][('account_id.code', '=', '400AR')]"), 100)
|
||||
self.assertEqual(
|
||||
self._eval(
|
||||
"balp[]" "[('account_id.account_type', '=', " " 'asset_receivable')]"
|
||||
),
|
||||
100,
|
||||
)
|
||||
self.assertEqual(
|
||||
self._eval("balp[('account_type', '=', " " 'asset_receivable')]"),
|
||||
100,
|
||||
)
|
||||
self.assertEqual(
|
||||
self._eval(
|
||||
"balp['&', "
|
||||
" ('account_type', '=', "
|
||||
" 'asset_receivable'), "
|
||||
" ('code', '=', '400AR')]"
|
||||
),
|
||||
100,
|
||||
)
|
||||
self.assertEqual(self._eval("balp[700IN]"), -100)
|
||||
# check ending balance
|
||||
self.assertEqual(self._eval("bale[400AR]"), 100)
|
||||
self.assertEqual(self._eval("bale[700IN]"), -100)
|
||||
|
||||
# let's query for January
|
||||
self._do_queries(
|
||||
datetime.date(self.curr_year, 1, 1), datetime.date(self.curr_year, 1, 31)
|
||||
)
|
||||
# initial balance is None for income account (it's not carried over)
|
||||
self.assertEqual(self._eval("bali[400AR]"), 100)
|
||||
self.assertIs(self._eval("bali[700IN]"), AccountingNone)
|
||||
# check variation
|
||||
self.assertEqual(self._eval("balp[400AR]"), 300)
|
||||
self.assertEqual(self._eval("balp[700IN]"), -300)
|
||||
# check ending balance
|
||||
self.assertEqual(self._eval("bale[400AR]"), 400)
|
||||
self.assertEqual(self._eval("bale[700IN]"), -300)
|
||||
# check result for non existing account
|
||||
self.assertIs(self._eval("bale[700NA]"), AccountingNone)
|
||||
# check fldp.quantity
|
||||
self.assertEqual(self._eval("fldp.quantity[700%]"), 3)
|
||||
|
||||
# let's query for March
|
||||
self._do_queries(
|
||||
datetime.date(self.curr_year, 3, 1), datetime.date(self.curr_year, 3, 31)
|
||||
)
|
||||
# initial balance is the ending balance fo January
|
||||
self.assertEqual(self._eval("bali[400AR]"), 400)
|
||||
self.assertEqual(self._eval("bali[700IN]"), -300)
|
||||
self.assertEqual(self._eval("pbali[400AR]"), 400)
|
||||
self.assertEqual(self._eval("nbali[400AR]"), 0)
|
||||
self.assertEqual(self._eval("nbali[700IN]"), -300)
|
||||
self.assertEqual(self._eval("pbali[700IN]"), 0)
|
||||
# check variation
|
||||
self.assertEqual(self._eval("balp[400AR]"), 500)
|
||||
self.assertEqual(self._eval("balp[700IN]"), -500)
|
||||
self.assertEqual(self._eval("nbalp[400AR]"), 0)
|
||||
self.assertEqual(self._eval("pbalp[400AR]"), 500)
|
||||
self.assertEqual(self._eval("nbalp[700IN]"), -500)
|
||||
self.assertEqual(self._eval("pbalp[700IN]"), 0)
|
||||
# check ending balance
|
||||
self.assertEqual(self._eval("bale[400AR]"), 900)
|
||||
self.assertEqual(self._eval("nbale[400AR]"), 0)
|
||||
self.assertEqual(self._eval("pbale[400AR]"), 900)
|
||||
self.assertEqual(self._eval("bale[700IN]"), -800)
|
||||
self.assertEqual(self._eval("nbale[700IN]"), -800)
|
||||
self.assertEqual(self._eval("pbale[700IN]"), 0)
|
||||
# check some variant expressions, for coverage
|
||||
self.assertEqual(self._eval("crdp[700I%]"), 500)
|
||||
self.assertEqual(self._eval("debp[400A%]"), 500)
|
||||
self.assertEqual(self._eval("bal_700IN"), -500)
|
||||
self.assertEqual(self._eval("bals[700IN]"), -800)
|
||||
# check fldp.quantity
|
||||
self.assertEqual(self._eval("fldp.quantity[700%]"), 0)
|
||||
|
||||
# unallocated p&l from previous year
|
||||
self.assertEqual(self._eval("balu[]"), -100)
|
||||
# TODO allocate profits, and then...
|
||||
|
||||
# let's query for December where there is no data
|
||||
self._do_queries(
|
||||
datetime.date(self.curr_year, 12, 1), datetime.date(self.curr_year, 12, 31)
|
||||
)
|
||||
self.assertIs(self._eval("balp[700IN]"), AccountingNone)
|
||||
|
||||
def test_aep_by_account(self):
|
||||
self.aep.done_parsing()
|
||||
self._do_queries(
|
||||
datetime.date(self.curr_year, 3, 1), datetime.date(self.curr_year, 3, 31)
|
||||
)
|
||||
variation = self._eval_by_account_id("balp[]")
|
||||
self.assertEqual(variation, {self.account_ar.id: 500, self.account_in.id: -500})
|
||||
variation = self._eval_by_account_id("pbalp[]")
|
||||
self.assertEqual(
|
||||
variation, {self.account_ar.id: 500, self.account_in.id: AccountingNone}
|
||||
)
|
||||
variation = self._eval_by_account_id("nbalp[]")
|
||||
self.assertEqual(
|
||||
variation, {self.account_ar.id: AccountingNone, self.account_in.id: -500}
|
||||
)
|
||||
variation = self._eval_by_account_id("balp[700IN]")
|
||||
self.assertEqual(variation, {self.account_in.id: -500})
|
||||
variation = self._eval_by_account_id("crdp[700IN] - debp[400AR]")
|
||||
self.assertEqual(variation, {self.account_ar.id: -500, self.account_in.id: 500})
|
||||
end = self._eval_by_account_id("bale[]")
|
||||
self.assertEqual(end, {self.account_ar.id: 900, self.account_in.id: -800})
|
||||
|
||||
def test_aep_by_account_no_data(self):
|
||||
"""Test that accounts with no data are not returned."""
|
||||
self.aep.done_parsing()
|
||||
self._do_queries(
|
||||
datetime.date(self.curr_year, 3, 1), datetime.date(self.curr_year, 3, 31)
|
||||
)
|
||||
variation = self._eval("balp[700I%]")
|
||||
self.assertEqual(variation, -500)
|
||||
variation_by_account = self._eval_by_account_id("balp[700I%]")
|
||||
self.assertEqual(variation_by_account, {self.account_in.id: -500})
|
||||
|
||||
def test_aep_convenience_methods(self):
|
||||
initial = AEP.get_balances_initial(self.company, time.strftime("%Y") + "-03-01")
|
||||
self.assertEqual(
|
||||
initial, {self.account_ar.id: (400, 0), self.account_in.id: (0, 300)}
|
||||
)
|
||||
variation = AEP.get_balances_variation(
|
||||
self.company,
|
||||
time.strftime("%Y") + "-03-01",
|
||||
time.strftime("%Y") + "-03-31",
|
||||
)
|
||||
self.assertEqual(
|
||||
variation, {self.account_ar.id: (500, 0), self.account_in.id: (0, 500)}
|
||||
)
|
||||
end = AEP.get_balances_end(self.company, time.strftime("%Y") + "-03-31")
|
||||
self.assertEqual(
|
||||
end, {self.account_ar.id: (900, 0), self.account_in.id: (0, 800)}
|
||||
)
|
||||
unallocated = AEP.get_unallocated_pl(
|
||||
self.company, time.strftime("%Y") + "-03-15"
|
||||
)
|
||||
self.assertEqual(unallocated, (0, 100))
|
||||
|
||||
def test_float_is_zero(self):
|
||||
dp = self.company.currency_id.decimal_places
|
||||
self.assertEqual(dp, 2)
|
||||
# make initial balance at Jan 1st equal to 0.01
|
||||
self._create_move(
|
||||
date=datetime.date(self.prev_year, 12, 1),
|
||||
amount=100.01,
|
||||
debit_acc=self.account_in,
|
||||
credit_acc=self.account_ar,
|
||||
)
|
||||
initial = AEP.get_balances_initial(self.company, time.strftime("%Y") + "-01-01")
|
||||
self.assertEqual(initial, {self.account_ar.id: (100.00, 100.01)})
|
||||
# make initial balance at Jan 1st equal to 0.001
|
||||
self._create_move(
|
||||
date=datetime.date(self.prev_year, 12, 1),
|
||||
amount=0.009,
|
||||
debit_acc=self.account_ar,
|
||||
credit_acc=self.account_in,
|
||||
)
|
||||
initial = AEP.get_balances_initial(self.company, time.strftime("%Y") + "-01-01")
|
||||
# epsilon initial balances is reported as empty
|
||||
self.assertEqual(initial, {})
|
||||
|
||||
def test_get_account_ids_for_expr(self):
|
||||
self.aep.done_parsing()
|
||||
expr = "balp[700IN]"
|
||||
account_ids = self.aep.get_account_ids_for_expr(expr)
|
||||
self.assertEqual(account_ids, {self.account_in.id})
|
||||
expr = "balp[700%]"
|
||||
account_ids = self.aep.get_account_ids_for_expr(expr)
|
||||
self.assertEqual(account_ids, {self.account_in.id, self.account_in_no_data.id})
|
||||
expr = "bali[400%], bale[700%]" # subkpis combined expression
|
||||
account_ids = self.aep.get_account_ids_for_expr(expr)
|
||||
self.assertEqual(
|
||||
account_ids,
|
||||
{self.account_in.id, self.account_ar.id, self.account_in_no_data.id},
|
||||
)
|
||||
|
||||
def test_get_aml_domain_for_expr(self):
|
||||
self.aep.done_parsing()
|
||||
expr = "balp[700IN]"
|
||||
domain = self.aep.get_aml_domain_for_expr(expr, "2017-01-01", "2017-03-31")
|
||||
self.assertEqual(
|
||||
domain,
|
||||
[
|
||||
("account_id", "in", (self.account_in.id,)),
|
||||
"&",
|
||||
("date", ">=", "2017-01-01"),
|
||||
("date", "<=", "2017-03-31"),
|
||||
],
|
||||
)
|
||||
expr = "debi[700IN] - crdi[400AR]"
|
||||
domain = self.aep.get_aml_domain_for_expr(expr, "2017-02-01", "2017-03-31")
|
||||
self.assertEqual(
|
||||
domain,
|
||||
[
|
||||
"|",
|
||||
# debi[700IN]
|
||||
"&",
|
||||
("account_id", "in", (self.account_in.id,)),
|
||||
("debit", "<>", 0.0),
|
||||
# crdi[400AR]
|
||||
"&",
|
||||
("account_id", "in", (self.account_ar.id,)),
|
||||
("credit", "<>", 0.0),
|
||||
"&",
|
||||
# for P&L accounts, only after fy start
|
||||
"|",
|
||||
("date", ">=", "2017-01-01"),
|
||||
("account_id.include_initial_balance", "=", True),
|
||||
# everything must be before from_date for initial balance
|
||||
("date", "<", "2017-02-01"),
|
||||
],
|
||||
)
|
||||
|
||||
def test_is_domain(self):
|
||||
self.assertTrue(_is_domain("('a', '=' 1)"))
|
||||
self.assertTrue(_is_domain("'&', ('a', '=' 1), ('b', '=', 1)"))
|
||||
self.assertTrue(_is_domain("'|', ('a', '=' 1), ('b', '=', 1)"))
|
||||
self.assertTrue(_is_domain("'!', ('a', '=' 1), ('b', '=', 1)"))
|
||||
self.assertTrue(_is_domain("\"&\", ('a', '=' 1), ('b', '=', 1)"))
|
||||
self.assertTrue(_is_domain("\"|\", ('a', '=' 1), ('b', '=', 1)"))
|
||||
self.assertTrue(_is_domain("\"!\", ('a', '=' 1), ('b', '=', 1)"))
|
||||
self.assertFalse(_is_domain("123%"))
|
||||
self.assertFalse(_is_domain("123%,456"))
|
||||
self.assertFalse(_is_domain(""))
|
||||
|
||||
def test_inactive_tax(self):
|
||||
expr = 'balp[][("tax_ids.name", "=", "test tax")]'
|
||||
self.aep.parse_expr(expr)
|
||||
self.aep.done_parsing()
|
||||
|
||||
tax = self.env["account.tax"].create(
|
||||
dict(name="test tax", active=True, amount=0, company_id=self.company.id)
|
||||
)
|
||||
move = self._create_move(
|
||||
date=datetime.date(self.prev_year, 12, 1),
|
||||
amount=100,
|
||||
debit_acc=self.account_ar,
|
||||
credit_acc=self.account_in,
|
||||
post=False,
|
||||
)
|
||||
for ml in move.line_ids:
|
||||
if ml.credit:
|
||||
ml.write(dict(tax_ids=[(6, 0, [tax.id])]))
|
||||
tax.active = False
|
||||
move._post()
|
||||
# let's query for december 1st
|
||||
self._do_queries(
|
||||
datetime.date(self.prev_year, 12, 1), datetime.date(self.prev_year, 12, 1)
|
||||
)
|
||||
# let's see if there was a match
|
||||
self.assertEqual(self._eval(expr), -100)
|
||||
|
||||
def test_invalid_field(self):
|
||||
expr = 'balp[][("invalid_field", "=", "...")]'
|
||||
self.aep.parse_expr(expr)
|
||||
self.aep.done_parsing()
|
||||
with self.assertRaises(UserError) as cm:
|
||||
self._do_queries(
|
||||
datetime.date(self.prev_year, 12, 1),
|
||||
datetime.date(self.prev_year, 12, 1),
|
||||
)
|
||||
assert "Error while querying move line source" in str(cm.exception)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Copyright 2014 ACSONE SA/NV (<http://acsone.eu>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from ..models import aggregate
|
||||
from .common import load_doctests
|
||||
|
||||
load_tests = load_doctests(aggregate)
|
||||
|
|
@ -0,0 +1,227 @@
|
|||
# Copyright 2016 ACSONE SA/NV (<http://acsone.eu>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
import odoo.tests.common as common
|
||||
|
||||
from ..models.accounting_none import AccountingNone
|
||||
from ..models.mis_report import CMP_DIFF
|
||||
from ..models.mis_report_instance import (
|
||||
MODE_NONE,
|
||||
SRC_ACTUALS_ALT,
|
||||
SRC_CMPCOL,
|
||||
SRC_SUMCOL,
|
||||
)
|
||||
from .common import assert_matrix
|
||||
|
||||
|
||||
class TestMisReportInstanceDataSources(common.TransactionCase):
|
||||
"""Test sum and comparison data source."""
|
||||
|
||||
def _create_move(self, date, amount, debit_acc, credit_acc):
|
||||
move = self.move_model.create(
|
||||
{
|
||||
"journal_id": self.journal.id,
|
||||
"date": date,
|
||||
"line_ids": [
|
||||
(0, 0, {"name": "/", "debit": amount, "account_id": debit_acc.id}),
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{"name": "/", "credit": amount, "account_id": credit_acc.id},
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
move._post()
|
||||
return move
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
# Perform the tests with a brand new company to avoid intrusive data from other
|
||||
# modules added to the default company
|
||||
cls.company = cls.env["res.company"].create({"name": "Company Test"})
|
||||
cls.env.user.company_id = cls.company
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.account_model = self.env["account.account"]
|
||||
self.move_model = self.env["account.move"]
|
||||
self.journal_model = self.env["account.journal"]
|
||||
# create receivable bs account
|
||||
self.account_ar = self.account_model.create(
|
||||
{
|
||||
"company_id": self.env.user.company_id.id,
|
||||
"code": "400AR",
|
||||
"name": "Receivable",
|
||||
"account_type": "asset_receivable",
|
||||
"reconcile": True,
|
||||
}
|
||||
)
|
||||
# create income account
|
||||
self.account_in = self.account_model.create(
|
||||
{
|
||||
"company_id": self.env.user.company_id.id,
|
||||
"code": "700IN",
|
||||
"name": "Income",
|
||||
"account_type": "income",
|
||||
}
|
||||
)
|
||||
self.account_in2 = self.account_model.create(
|
||||
{
|
||||
"company_id": self.env.user.company_id.id,
|
||||
"code": "700IN2",
|
||||
"name": "Income",
|
||||
"account_type": "income",
|
||||
}
|
||||
)
|
||||
# create journal
|
||||
self.journal = self.journal_model.create(
|
||||
{
|
||||
"company_id": self.env.user.company_id.id,
|
||||
"name": "Sale journal",
|
||||
"code": "VEN",
|
||||
"type": "sale",
|
||||
}
|
||||
)
|
||||
# create move
|
||||
self._create_move(
|
||||
date="2017-01-01",
|
||||
amount=11,
|
||||
debit_acc=self.account_ar,
|
||||
credit_acc=self.account_in,
|
||||
)
|
||||
# create move
|
||||
self._create_move(
|
||||
date="2017-02-01",
|
||||
amount=13,
|
||||
debit_acc=self.account_ar,
|
||||
credit_acc=self.account_in,
|
||||
)
|
||||
self._create_move(
|
||||
date="2017-02-01",
|
||||
amount=17,
|
||||
debit_acc=self.account_ar,
|
||||
credit_acc=self.account_in2,
|
||||
)
|
||||
# create report
|
||||
self.report = self.env["mis.report"].create(dict(name="test report"))
|
||||
self.kpi1 = self.env["mis.report.kpi"].create(
|
||||
dict(
|
||||
report_id=self.report.id,
|
||||
name="k1",
|
||||
description="kpi 1",
|
||||
expression="-balp[700IN]",
|
||||
compare_method=CMP_DIFF,
|
||||
)
|
||||
)
|
||||
self.expr1 = self.kpi1.expression_ids[0]
|
||||
self.kpi2 = self.env["mis.report.kpi"].create(
|
||||
dict(
|
||||
report_id=self.report.id,
|
||||
name="k2",
|
||||
description="kpi 2",
|
||||
expression="-balp[700%]",
|
||||
compare_method=CMP_DIFF,
|
||||
auto_expand_accounts=True,
|
||||
)
|
||||
)
|
||||
self.instance = self.env["mis.report.instance"].create(
|
||||
dict(name="test instance", report_id=self.report.id, comparison_mode=True)
|
||||
)
|
||||
self.p1 = self.env["mis.report.instance.period"].create(
|
||||
dict(
|
||||
name="p1",
|
||||
report_instance_id=self.instance.id,
|
||||
manual_date_from="2017-01-01",
|
||||
manual_date_to="2017-01-31",
|
||||
)
|
||||
)
|
||||
self.p2 = self.env["mis.report.instance.period"].create(
|
||||
dict(
|
||||
name="p2",
|
||||
report_instance_id=self.instance.id,
|
||||
manual_date_from="2017-02-01",
|
||||
manual_date_to="2017-02-28",
|
||||
)
|
||||
)
|
||||
|
||||
def test_sum(self):
|
||||
self.psum = self.env["mis.report.instance.period"].create(
|
||||
dict(
|
||||
name="psum",
|
||||
report_instance_id=self.instance.id,
|
||||
mode=MODE_NONE,
|
||||
source=SRC_SUMCOL,
|
||||
source_sumcol_ids=[
|
||||
(0, 0, dict(period_to_sum_id=self.p1.id, sign="+")),
|
||||
(0, 0, dict(period_to_sum_id=self.p2.id, sign="+")),
|
||||
],
|
||||
)
|
||||
)
|
||||
matrix = self.instance._compute_matrix()
|
||||
# None in last col because account details are not summed by default
|
||||
assert_matrix(
|
||||
matrix,
|
||||
[
|
||||
[11, 13, 24],
|
||||
[11, 30, 41],
|
||||
[11, 13, AccountingNone],
|
||||
[AccountingNone, 17, AccountingNone],
|
||||
],
|
||||
)
|
||||
|
||||
def test_sum_diff(self):
|
||||
self.psum = self.env["mis.report.instance.period"].create(
|
||||
dict(
|
||||
name="psum",
|
||||
report_instance_id=self.instance.id,
|
||||
mode=MODE_NONE,
|
||||
source=SRC_SUMCOL,
|
||||
source_sumcol_ids=[
|
||||
(0, 0, dict(period_to_sum_id=self.p1.id, sign="+")),
|
||||
(0, 0, dict(period_to_sum_id=self.p2.id, sign="-")),
|
||||
],
|
||||
source_sumcol_accdet=True,
|
||||
)
|
||||
)
|
||||
matrix = self.instance._compute_matrix()
|
||||
assert_matrix(
|
||||
matrix,
|
||||
[[11, 13, -2], [11, 30, -19], [11, 13, -2], [AccountingNone, 17, -17]],
|
||||
)
|
||||
|
||||
def test_cmp(self):
|
||||
self.pcmp = self.env["mis.report.instance.period"].create(
|
||||
dict(
|
||||
name="pcmp",
|
||||
report_instance_id=self.instance.id,
|
||||
mode=MODE_NONE,
|
||||
source=SRC_CMPCOL,
|
||||
source_cmpcol_from_id=self.p1.id,
|
||||
source_cmpcol_to_id=self.p2.id,
|
||||
)
|
||||
)
|
||||
matrix = self.instance._compute_matrix()
|
||||
assert_matrix(
|
||||
matrix, [[11, 13, 2], [11, 30, 19], [11, 13, 2], [AccountingNone, 17, 17]]
|
||||
)
|
||||
|
||||
def test_actuals(self):
|
||||
matrix = self.instance._compute_matrix()
|
||||
assert_matrix(matrix, [[11, 13], [11, 30], [11, 13], [AccountingNone, 17]])
|
||||
|
||||
def test_actuals_disable_auto_expand_accounts(self):
|
||||
self.instance.no_auto_expand_accounts = True
|
||||
matrix = self.instance._compute_matrix()
|
||||
assert_matrix(matrix, [[11, 13], [11, 30]])
|
||||
|
||||
def test_actuals_alt(self):
|
||||
aml_model = self.env["ir.model"].search([("name", "=", "account.move.line")])
|
||||
self.kpi2.auto_expand_accounts = False
|
||||
self.p1.source = SRC_ACTUALS_ALT
|
||||
self.p1.source_aml_model_id = aml_model.id
|
||||
self.p2.source = SRC_ACTUALS_ALT
|
||||
self.p1.source_aml_model_id = aml_model.id
|
||||
matrix = self.instance._compute_matrix()
|
||||
assert_matrix(matrix, [[11, 13], [11, 30]])
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
# Copyright 2017 ACSONE SA/NV (<http://acsone.eu>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo_test_helper import FakeModelLoader
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
from ..models.mis_kpi_data import ACC_AVG, ACC_SUM
|
||||
|
||||
|
||||
class TestKpiData(TransactionCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.loader = FakeModelLoader(cls.env, cls.__module__)
|
||||
cls.loader.backup_registry()
|
||||
from .fake_models import MisKpiDataTestItem
|
||||
|
||||
cls.loader.update_registry((MisKpiDataTestItem,))
|
||||
|
||||
report = cls.env["mis.report"].create(dict(name="test report"))
|
||||
cls.kpi1 = cls.env["mis.report.kpi"].create(
|
||||
dict(
|
||||
report_id=report.id,
|
||||
name="k1",
|
||||
description="kpi 1",
|
||||
expression="AccountingNone",
|
||||
)
|
||||
)
|
||||
cls.expr1 = cls.kpi1.expression_ids[0]
|
||||
cls.kpi2 = cls.env["mis.report.kpi"].create(
|
||||
dict(
|
||||
report_id=report.id,
|
||||
name="k2",
|
||||
description="kpi 2",
|
||||
expression="AccountingNone",
|
||||
)
|
||||
)
|
||||
cls.expr2 = cls.kpi2.expression_ids[0]
|
||||
cls.kd11 = cls.env["mis.kpi.data.test.item"].create(
|
||||
dict(
|
||||
kpi_expression_id=cls.expr1.id,
|
||||
date_from="2017-05-01",
|
||||
date_to="2017-05-10",
|
||||
amount=10,
|
||||
)
|
||||
)
|
||||
cls.kd12 = cls.env["mis.kpi.data.test.item"].create(
|
||||
dict(
|
||||
kpi_expression_id=cls.expr1.id,
|
||||
date_from="2017-05-11",
|
||||
date_to="2017-05-20",
|
||||
amount=20,
|
||||
)
|
||||
)
|
||||
cls.kd13 = cls.env["mis.kpi.data.test.item"].create(
|
||||
dict(
|
||||
kpi_expression_id=cls.expr1.id,
|
||||
date_from="2017-05-21",
|
||||
date_to="2017-05-25",
|
||||
amount=30,
|
||||
)
|
||||
)
|
||||
cls.kd21 = cls.env["mis.kpi.data.test.item"].create(
|
||||
dict(
|
||||
kpi_expression_id=cls.expr2.id,
|
||||
date_from="2017-06-01",
|
||||
date_to="2017-06-30",
|
||||
amount=3,
|
||||
)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.loader.restore_registry()
|
||||
return super().tearDownClass()
|
||||
|
||||
def test_kpi_data_name(self):
|
||||
self.assertEqual(self.kd11.name, "k1: 2017-05-01 - 2017-05-10")
|
||||
self.assertEqual(self.kd12.name, "k1: 2017-05-11 - 2017-05-20")
|
||||
|
||||
def test_kpi_data_sum(self):
|
||||
self.assertEqual(self.kpi1.accumulation_method, ACC_SUM)
|
||||
# one full
|
||||
r = self.env["mis.kpi.data.test.item"]._query_kpi_data(
|
||||
"2017-05-01", "2017-05-10", []
|
||||
)
|
||||
self.assertEqual(r, {self.expr1: 10})
|
||||
# one half
|
||||
r = self.env["mis.kpi.data.test.item"]._query_kpi_data(
|
||||
"2017-05-01", "2017-05-05", []
|
||||
)
|
||||
self.assertEqual(r, {self.expr1: 5})
|
||||
# two full
|
||||
r = self.env["mis.kpi.data.test.item"]._query_kpi_data(
|
||||
"2017-05-01", "2017-05-20", []
|
||||
)
|
||||
self.assertEqual(r, {self.expr1: 30})
|
||||
# two half
|
||||
r = self.env["mis.kpi.data.test.item"]._query_kpi_data(
|
||||
"2017-05-06", "2017-05-15", []
|
||||
)
|
||||
self.assertEqual(r, {self.expr1: 15})
|
||||
# more than covered range
|
||||
r = self.env["mis.kpi.data.test.item"]._query_kpi_data(
|
||||
"2017-01-01", "2017-05-31", []
|
||||
)
|
||||
self.assertEqual(r, {self.expr1: 60})
|
||||
# two kpis
|
||||
r = self.env["mis.kpi.data.test.item"]._query_kpi_data(
|
||||
"2017-05-21", "2017-06-30", []
|
||||
)
|
||||
self.assertEqual(r, {self.expr1: 30, self.expr2: 3})
|
||||
|
||||
def test_kpi_data_avg(self):
|
||||
self.kpi1.accumulation_method = ACC_AVG
|
||||
# one full
|
||||
r = self.env["mis.kpi.data.test.item"]._query_kpi_data(
|
||||
"2017-05-01", "2017-05-10", []
|
||||
)
|
||||
self.assertEqual(r, {self.expr1: 10})
|
||||
# one half
|
||||
r = self.env["mis.kpi.data.test.item"]._query_kpi_data(
|
||||
"2017-05-01", "2017-05-05", []
|
||||
)
|
||||
self.assertEqual(r, {self.expr1: 10})
|
||||
# two full
|
||||
r = self.env["mis.kpi.data.test.item"]._query_kpi_data(
|
||||
"2017-05-01", "2017-05-20", []
|
||||
)
|
||||
self.assertEqual(r, {self.expr1: (10 * 10 + 20 * 10) / 20})
|
||||
# two half
|
||||
r = self.env["mis.kpi.data.test.item"]._query_kpi_data(
|
||||
"2017-05-06", "2017-05-15", []
|
||||
)
|
||||
self.assertEqual(r, {self.expr1: (10 * 5 + 20 * 5) / 10})
|
||||
# more than covered range
|
||||
r = self.env["mis.kpi.data.test.item"]._query_kpi_data(
|
||||
"2017-01-01", "2017-05-31", []
|
||||
)
|
||||
self.assertEqual(r, {self.expr1: (10 * 10 + 20 * 10 + 30 * 5) / 25})
|
||||
|
|
@ -0,0 +1,635 @@
|
|||
# Copyright 2016 ACSONE SA/NV (<http://acsone.eu>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
import odoo.tests.common as common
|
||||
from odoo.tools import test_reports
|
||||
|
||||
from ..models.accounting_none import AccountingNone
|
||||
from ..models.mis_report import TYPE_STR, SubKPITupleLengthError, SubKPIUnknownTypeError
|
||||
|
||||
|
||||
class TestMisReportInstance(common.HttpCase):
|
||||
"""Basic integration test to exercise mis.report.instance.
|
||||
|
||||
We don't check the actual results here too much as computation correctness
|
||||
should be covered by lower level unit tests.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
partner_model_id = self.env.ref("base.model_res_partner").id
|
||||
partner_create_date_field_id = self.env.ref(
|
||||
"base.field_res_partner__create_date"
|
||||
).id
|
||||
partner_debit_field_id = self.env.ref("account.field_res_partner__debit").id
|
||||
# create a report with 2 subkpis and one query
|
||||
self.report = self.env["mis.report"].create(
|
||||
dict(
|
||||
name="test report",
|
||||
subkpi_ids=[
|
||||
(0, 0, dict(name="sk1", description="subkpi 1", sequence=1)),
|
||||
(0, 0, dict(name="sk2", description="subkpi 2", sequence=2)),
|
||||
],
|
||||
query_ids=[
|
||||
(
|
||||
0,
|
||||
0,
|
||||
dict(
|
||||
name="partner",
|
||||
model_id=partner_model_id,
|
||||
field_ids=[(4, partner_debit_field_id, None)],
|
||||
date_field=partner_create_date_field_id,
|
||||
aggregate="sum",
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
# create another report with 2 subkpis, no query
|
||||
self.report_2 = self.env["mis.report"].create(
|
||||
dict(
|
||||
name="another test report",
|
||||
subkpi_ids=[
|
||||
(
|
||||
0,
|
||||
0,
|
||||
dict(
|
||||
name="subkpi1_report2",
|
||||
description="subkpi 1, report 2",
|
||||
sequence=1,
|
||||
),
|
||||
),
|
||||
(
|
||||
0,
|
||||
0,
|
||||
dict(
|
||||
name="subkpi2_report2",
|
||||
description="subkpi 2, report 2",
|
||||
sequence=2,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
)
|
||||
# Third report, 2 subkpis, no query
|
||||
self.report_3 = self.env["mis.report"].create(
|
||||
dict(
|
||||
name="test report 3",
|
||||
subkpi_ids=[
|
||||
(
|
||||
0,
|
||||
0,
|
||||
dict(
|
||||
name="subkpi1_report3",
|
||||
description="subkpi 1, report 3",
|
||||
sequence=1,
|
||||
),
|
||||
),
|
||||
(
|
||||
0,
|
||||
0,
|
||||
dict(
|
||||
name="subkpi2_report3",
|
||||
description="subkpi 2, report 3",
|
||||
sequence=2,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
)
|
||||
# kpi with accounting formulas
|
||||
self.kpi1 = self.env["mis.report.kpi"].create(
|
||||
dict(
|
||||
report_id=self.report.id,
|
||||
description="kpi 1",
|
||||
name="k1",
|
||||
multi=True,
|
||||
expression_ids=[
|
||||
(
|
||||
0,
|
||||
0,
|
||||
dict(name="bale[200%]", subkpi_id=self.report.subkpi_ids[0].id),
|
||||
),
|
||||
(
|
||||
0,
|
||||
0,
|
||||
dict(name="balp[200%]", subkpi_id=self.report.subkpi_ids[1].id),
|
||||
),
|
||||
],
|
||||
)
|
||||
)
|
||||
# kpi with accounting formula and query
|
||||
self.kpi2 = self.env["mis.report.kpi"].create(
|
||||
dict(
|
||||
report_id=self.report.id,
|
||||
description="kpi 2",
|
||||
name="k2",
|
||||
multi=True,
|
||||
expression_ids=[
|
||||
(
|
||||
0,
|
||||
0,
|
||||
dict(name="balp[200%]", subkpi_id=self.report.subkpi_ids[0].id),
|
||||
),
|
||||
(
|
||||
0,
|
||||
0,
|
||||
dict(
|
||||
name="partner.debit", subkpi_id=self.report.subkpi_ids[1].id
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
)
|
||||
# kpi with a simple expression summing other multi-valued kpis
|
||||
self.env["mis.report.kpi"].create(
|
||||
dict(
|
||||
report_id=self.report.id,
|
||||
description="kpi 4",
|
||||
name="k4",
|
||||
multi=False,
|
||||
expression="k1 + k2 + k3",
|
||||
)
|
||||
)
|
||||
# kpi with 2 constants
|
||||
self.env["mis.report.kpi"].create(
|
||||
dict(
|
||||
report_id=self.report.id,
|
||||
description="kpi 3",
|
||||
name="k3",
|
||||
multi=True,
|
||||
expression_ids=[
|
||||
(
|
||||
0,
|
||||
0,
|
||||
dict(
|
||||
name="AccountingNone",
|
||||
subkpi_id=self.report.subkpi_ids[0].id,
|
||||
),
|
||||
),
|
||||
(0, 0, dict(name="1.0", subkpi_id=self.report.subkpi_ids[1].id)),
|
||||
],
|
||||
)
|
||||
)
|
||||
# kpi with a NameError (x not defined)
|
||||
self.env["mis.report.kpi"].create(
|
||||
dict(
|
||||
report_id=self.report.id,
|
||||
description="kpi 5",
|
||||
name="k5",
|
||||
multi=True,
|
||||
expression_ids=[
|
||||
(0, 0, dict(name="x", subkpi_id=self.report.subkpi_ids[0].id)),
|
||||
(0, 0, dict(name="1.0", subkpi_id=self.report.subkpi_ids[1].id)),
|
||||
],
|
||||
)
|
||||
)
|
||||
# string-type kpi
|
||||
self.env["mis.report.kpi"].create(
|
||||
dict(
|
||||
report_id=self.report.id,
|
||||
description="kpi 6",
|
||||
name="k6",
|
||||
multi=True,
|
||||
type=TYPE_STR,
|
||||
expression_ids=[
|
||||
(0, 0, dict(name='"bla"', subkpi_id=self.report.subkpi_ids[0].id)),
|
||||
(
|
||||
0,
|
||||
0,
|
||||
dict(name='"blabla"', subkpi_id=self.report.subkpi_ids[1].id),
|
||||
),
|
||||
],
|
||||
)
|
||||
)
|
||||
# kpi that references another subkpi by name
|
||||
self.env["mis.report.kpi"].create(
|
||||
dict(
|
||||
report_id=self.report.id,
|
||||
description="kpi 7",
|
||||
name="k7",
|
||||
multi=True,
|
||||
expression_ids=[
|
||||
(0, 0, dict(name="k3.sk1", subkpi_id=self.report.subkpi_ids[0].id)),
|
||||
(0, 0, dict(name="k3.sk2", subkpi_id=self.report.subkpi_ids[1].id)),
|
||||
],
|
||||
)
|
||||
)
|
||||
# Report 2 : kpi with AccountingNone value
|
||||
self.env["mis.report.kpi"].create(
|
||||
dict(
|
||||
report_id=self.report_2.id,
|
||||
description="AccountingNone kpi",
|
||||
name="AccountingNoneKPI",
|
||||
multi=False,
|
||||
)
|
||||
)
|
||||
# Report 2 : 'classic' kpi with values for each sub-KPI
|
||||
self.env["mis.report.kpi"].create(
|
||||
dict(
|
||||
report_id=self.report_2.id,
|
||||
description="Classic kpi",
|
||||
name="classic_kpi_r2",
|
||||
multi=True,
|
||||
expression_ids=[
|
||||
(
|
||||
0,
|
||||
0,
|
||||
dict(
|
||||
name="bale[200%]", subkpi_id=self.report_2.subkpi_ids[0].id
|
||||
),
|
||||
),
|
||||
(
|
||||
0,
|
||||
0,
|
||||
dict(
|
||||
name="balp[200%]", subkpi_id=self.report_2.subkpi_ids[1].id
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
)
|
||||
# Report 3 : kpi with wrong tuple length
|
||||
self.env["mis.report.kpi"].create(
|
||||
dict(
|
||||
report_id=self.report_3.id,
|
||||
description="Wrong tuple length kpi",
|
||||
name="wrongTupleLen",
|
||||
multi=False,
|
||||
expression="('hello', 'does', 'this', 'work')",
|
||||
)
|
||||
)
|
||||
# Report 3 : 'classic' kpi
|
||||
self.env["mis.report.kpi"].create(
|
||||
dict(
|
||||
report_id=self.report_3.id,
|
||||
description="Classic kpi",
|
||||
name="classic_kpi_r2",
|
||||
multi=True,
|
||||
expression_ids=[
|
||||
(
|
||||
0,
|
||||
0,
|
||||
dict(
|
||||
name="bale[200%]", subkpi_id=self.report_3.subkpi_ids[0].id
|
||||
),
|
||||
),
|
||||
(
|
||||
0,
|
||||
0,
|
||||
dict(
|
||||
name="balp[200%]", subkpi_id=self.report_3.subkpi_ids[1].id
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
)
|
||||
# create a report instance
|
||||
self.report_instance = self.env["mis.report.instance"].create(
|
||||
dict(
|
||||
name="test instance",
|
||||
report_id=self.report.id,
|
||||
company_id=self.env.ref("base.main_company").id,
|
||||
period_ids=[
|
||||
(
|
||||
0,
|
||||
0,
|
||||
dict(
|
||||
name="p1",
|
||||
mode="relative",
|
||||
type="d",
|
||||
subkpi_ids=[(4, self.report.subkpi_ids[0].id, None)],
|
||||
),
|
||||
),
|
||||
(
|
||||
0,
|
||||
0,
|
||||
dict(
|
||||
name="p2",
|
||||
mode="fix",
|
||||
manual_date_from="2014-01-01",
|
||||
manual_date_to="2014-12-31",
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
)
|
||||
# same for report 2
|
||||
self.report_instance_2 = self.env["mis.report.instance"].create(
|
||||
dict(
|
||||
name="test instance 2",
|
||||
report_id=self.report_2.id,
|
||||
company_id=self.env.ref("base.main_company").id,
|
||||
period_ids=[
|
||||
(
|
||||
0,
|
||||
0,
|
||||
dict(
|
||||
name="p3",
|
||||
mode="fix",
|
||||
manual_date_from="2019-01-01",
|
||||
manual_date_to="2019-12-31",
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
# and for report 3
|
||||
self.report_instance_3 = self.env["mis.report.instance"].create(
|
||||
dict(
|
||||
name="test instance 3",
|
||||
report_id=self.report_3.id,
|
||||
company_id=self.env.ref("base.main_company").id,
|
||||
period_ids=[
|
||||
(
|
||||
0,
|
||||
0,
|
||||
dict(
|
||||
name="p4",
|
||||
mode="fix",
|
||||
manual_date_from="2019-01-01",
|
||||
manual_date_to="2019-12-31",
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
def test_compute(self):
|
||||
matrix = self.report_instance._compute_matrix()
|
||||
for row in matrix.iter_rows():
|
||||
vals = [c.val for c in row.iter_cells()]
|
||||
if row.kpi.name == "k3":
|
||||
# k3 is constant
|
||||
self.assertEqual(vals, [AccountingNone, AccountingNone, 1.0])
|
||||
elif row.kpi.name == "k6":
|
||||
# k6 is a string kpi
|
||||
self.assertEqual(vals, ["bla", "bla", "blabla"])
|
||||
elif row.kpi.name == "k7":
|
||||
# k7 references k3 via subkpi names
|
||||
self.assertEqual(vals, [AccountingNone, AccountingNone, 1.0])
|
||||
|
||||
def test_multi_company_compute(self):
|
||||
self.report_instance.write(
|
||||
{
|
||||
"multi_company": True,
|
||||
"company_ids": [(6, 0, self.report_instance.company_id.ids)],
|
||||
}
|
||||
)
|
||||
self.report_instance.report_id.kpi_ids.write({"auto_expand_accounts": True})
|
||||
matrix = self.report_instance._compute_matrix()
|
||||
for row in matrix.iter_rows():
|
||||
if row.account_id:
|
||||
account = self.env["account.account"].browse(row.account_id)
|
||||
self.assertEqual(
|
||||
row.label,
|
||||
f"{account.code} {account.name} [{account.company_id.name}]",
|
||||
)
|
||||
self.report_instance.write({"multi_company": False})
|
||||
matrix = self.report_instance._compute_matrix()
|
||||
for row in matrix.iter_rows():
|
||||
if row.account_id:
|
||||
account = self.env["account.account"].browse(row.account_id)
|
||||
self.assertEqual(row.label, f"{account.code} {account.name}")
|
||||
|
||||
def test_evaluate(self):
|
||||
company = self.env.ref("base.main_company")
|
||||
aep = self.report._prepare_aep(company)
|
||||
r = self.report.evaluate(aep, date_from="2014-01-01", date_to="2014-12-31")
|
||||
self.assertEqual(r["k3"], (AccountingNone, 1.0))
|
||||
self.assertEqual(r["k6"], ("bla", "blabla"))
|
||||
self.assertEqual(r["k7"], (AccountingNone, 1.0))
|
||||
|
||||
def test_json(self):
|
||||
self.report_instance.compute()
|
||||
|
||||
def test_drilldown(self):
|
||||
action = self.report_instance.drilldown(
|
||||
dict(expr="balp[200%]", period_id=self.report_instance.period_ids[0].id)
|
||||
)
|
||||
account_ids = (
|
||||
self.env["account.account"]
|
||||
.search(
|
||||
[
|
||||
("code", "=like", "200%"),
|
||||
("company_id", "=", self.env.ref("base.main_company").id),
|
||||
]
|
||||
)
|
||||
.ids
|
||||
)
|
||||
self.assertTrue(("account_id", "in", tuple(account_ids)) in action["domain"])
|
||||
self.assertEqual(action["res_model"], "account.move.line")
|
||||
|
||||
def test_drilldown_action_name_with_account(self):
|
||||
period = self.report_instance.period_ids[0]
|
||||
account = self.env["account.account"].search([], limit=1)
|
||||
args = {
|
||||
"period_id": period.id,
|
||||
"kpi_id": self.kpi1.id,
|
||||
"account_id": account.id,
|
||||
}
|
||||
action_name = self.report_instance._get_drilldown_action_name(args)
|
||||
expected_name = "{kpi} - {account} - {period}".format(
|
||||
kpi=self.kpi1.description,
|
||||
account=account.display_name,
|
||||
period=period.display_name,
|
||||
)
|
||||
assert action_name == expected_name
|
||||
|
||||
def test_drilldown_action_name_without_account(self):
|
||||
period = self.report_instance.period_ids[0]
|
||||
args = {
|
||||
"period_id": period.id,
|
||||
"kpi_id": self.kpi1.id,
|
||||
}
|
||||
action_name = self.report_instance._get_drilldown_action_name(args)
|
||||
expected_name = f"{self.kpi1.description} - {period.display_name}"
|
||||
assert action_name == expected_name
|
||||
|
||||
def test_drilldown_views(self):
|
||||
IrUiView = self.env["ir.ui.view"]
|
||||
model_name = "account.move.line"
|
||||
IrUiView.search([("model", "=", model_name)]).unlink()
|
||||
IrUiView.create(
|
||||
[
|
||||
{
|
||||
"name": "mis_report_test_drilldown_views_chart",
|
||||
"model": model_name,
|
||||
"arch": "<graph><field name='name'/></graph>",
|
||||
},
|
||||
{
|
||||
"name": "mis_report_test_drilldown_views_tree",
|
||||
"model": model_name,
|
||||
"arch": "<pivot><field name='name'/></pivot>",
|
||||
},
|
||||
]
|
||||
)
|
||||
action = self.report_instance.drilldown(
|
||||
dict(expr="balp[200%]", period_id=self.report_instance.period_ids[0].id)
|
||||
)
|
||||
self.assertEqual(action["view_mode"], "pivot,graph")
|
||||
self.assertEqual(action["views"], [[False, "pivot"], [False, "graph"]])
|
||||
IrUiView.create(
|
||||
[
|
||||
{
|
||||
"name": "mis_report_test_drilldown_views_form",
|
||||
"model": model_name,
|
||||
"arch": "<form><field name='name'/></form>",
|
||||
},
|
||||
{
|
||||
"name": "mis_report_test_drilldown_views_tree",
|
||||
"model": model_name,
|
||||
"arch": "<tree><field name='name'/></tree>",
|
||||
},
|
||||
]
|
||||
)
|
||||
action = self.report_instance.drilldown(
|
||||
dict(expr="balp[200%]", period_id=self.report_instance.period_ids[0].id)
|
||||
)
|
||||
self.assertEqual(action["view_mode"], "tree,form,pivot,graph")
|
||||
self.assertEqual(
|
||||
action["views"],
|
||||
[[False, "tree"], [False, "form"], [False, "pivot"], [False, "graph"]],
|
||||
)
|
||||
|
||||
def test_qweb(self):
|
||||
self.report_instance.print_pdf() # get action
|
||||
test_reports.try_report(
|
||||
self.env.cr,
|
||||
self.env.uid,
|
||||
"mis_builder.report_mis_report_instance",
|
||||
[self.report_instance.id],
|
||||
report_type="qweb-pdf",
|
||||
)
|
||||
|
||||
def test_xlsx(self):
|
||||
self.report_instance.export_xls() # get action
|
||||
test_reports.try_report(
|
||||
self.env.cr,
|
||||
self.env.uid,
|
||||
"mis_builder.mis_report_instance_xlsx",
|
||||
[self.report_instance.id],
|
||||
report_type="xlsx",
|
||||
)
|
||||
|
||||
def test_get_kpis_by_account_id(self):
|
||||
account_ids = (
|
||||
self.env["account.account"]
|
||||
.search(
|
||||
[
|
||||
("code", "=like", "200%"),
|
||||
("company_id", "=", self.env.ref("base.main_company").id),
|
||||
]
|
||||
)
|
||||
.ids
|
||||
)
|
||||
kpi200 = {self.kpi1, self.kpi2}
|
||||
res = self.report.get_kpis_by_account_id(self.env.ref("base.main_company"))
|
||||
for account_id in account_ids:
|
||||
self.assertTrue(account_id in res)
|
||||
self.assertEqual(res[account_id], kpi200)
|
||||
|
||||
def test_kpi_name_get_name_search(self):
|
||||
r = self.env["mis.report.kpi"].name_search("k1")
|
||||
self.assertEqual(len(r), 1)
|
||||
self.assertEqual(r[0][0], self.kpi1.id)
|
||||
self.assertEqual(r[0][1], "kpi 1 (k1)")
|
||||
r = self.env["mis.report.kpi"].name_search("kpi 1")
|
||||
self.assertEqual(len(r), 1)
|
||||
self.assertEqual(r[0][0], self.kpi1.id)
|
||||
self.assertEqual(r[0][1], "kpi 1 (k1)")
|
||||
|
||||
def test_kpi_expr_name_get_name_search(self):
|
||||
r = self.env["mis.report.kpi.expression"].name_search("k1")
|
||||
self.assertEqual(
|
||||
[i[1] for i in r],
|
||||
["kpi 1 / subkpi 1 (k1.sk1)", "kpi 1 / subkpi 2 (k1.sk2)"],
|
||||
)
|
||||
r = self.env["mis.report.kpi.expression"].name_search("k1.sk1")
|
||||
self.assertEqual([i[1] for i in r], ["kpi 1 / subkpi 1 (k1.sk1)"])
|
||||
r = self.env["mis.report.kpi.expression"].name_search("k4")
|
||||
self.assertEqual([i[1] for i in r], ["kpi 4 (k4)"])
|
||||
|
||||
def test_query_company_ids(self):
|
||||
# sanity check single company mode
|
||||
assert not self.report_instance.multi_company
|
||||
assert self.report_instance.company_id
|
||||
assert self.report_instance.query_company_ids == self.report_instance.company_id
|
||||
# create a second company
|
||||
c1 = self.report_instance.company_id
|
||||
c2 = self.env["res.company"].create(
|
||||
dict(
|
||||
name="company 2",
|
||||
)
|
||||
)
|
||||
self.report_instance.write(dict(multi_company=True, company_id=False))
|
||||
self.report_instance.company_ids |= c1
|
||||
self.report_instance.company_ids |= c2
|
||||
assert len(self.report_instance.company_ids) == 2
|
||||
self.assertFalse(self.report_instance.query_company_ids - self.env.companies)
|
||||
# In a user context where there is only one company, ensure
|
||||
# query_company_ids only has one company too.
|
||||
assert (
|
||||
self.report_instance.with_context(
|
||||
allowed_company_ids=(c1.id,)
|
||||
).query_company_ids
|
||||
== c1
|
||||
)
|
||||
|
||||
def test_multi_company_onchange(self):
|
||||
# not multi company
|
||||
self.assertTrue(self.report_instance.company_id)
|
||||
self.assertFalse(self.report_instance.multi_company)
|
||||
self.assertFalse(self.report_instance.company_ids)
|
||||
self.assertEqual(
|
||||
self.report_instance.query_company_ids[0], self.report_instance.company_id
|
||||
)
|
||||
# create a child company
|
||||
self.env["res.company"].create(
|
||||
dict(name="company 2", parent_id=self.report_instance.company_id.id)
|
||||
)
|
||||
self.report_instance.multi_company = True
|
||||
# multi company, company_ids not set
|
||||
self.assertEqual(self.report_instance.query_company_ids, self.env.companies)
|
||||
# set company_ids
|
||||
previous_company = self.report_instance.company_id
|
||||
self.report_instance._onchange_company()
|
||||
self.assertFalse(self.report_instance.company_id)
|
||||
self.assertTrue(self.report_instance.multi_company)
|
||||
self.assertEqual(self.report_instance.company_ids, previous_company)
|
||||
self.assertEqual(self.report_instance.query_company_ids, previous_company)
|
||||
# reset single company mode
|
||||
self.report_instance.multi_company = False
|
||||
self.report_instance._onchange_company()
|
||||
self.assertEqual(
|
||||
self.report_instance.query_company_ids[0], self.report_instance.company_id
|
||||
)
|
||||
self.assertFalse(self.report_instance.company_ids)
|
||||
|
||||
def test_mis_report_analytic_filters(self):
|
||||
# Check that matrix has no values when using a filter with a non existing value
|
||||
matrix = self.report_instance.with_context(
|
||||
analytic_domain=[("partner_id", "=", -1)]
|
||||
)._compute_matrix()
|
||||
for row in matrix.iter_rows():
|
||||
vals = [c.val for c in row.iter_cells()]
|
||||
if row.kpi.name == "k1":
|
||||
self.assertEqual(vals, [AccountingNone, AccountingNone, AccountingNone])
|
||||
elif row.kpi.name == "k2":
|
||||
self.assertEqual(vals, [AccountingNone, AccountingNone, None])
|
||||
elif row.kpi.name == "k4":
|
||||
self.assertEqual(vals, [AccountingNone, AccountingNone, 1.0])
|
||||
|
||||
def test_raise_when_unknown_kpi_value_type(self):
|
||||
with self.assertRaises(SubKPIUnknownTypeError):
|
||||
self.report_instance_2.compute()
|
||||
|
||||
def test_raise_when_wrong_tuple_length_with_subkpis(self):
|
||||
with self.assertRaises(SubKPITupleLengthError):
|
||||
self.report_instance_3.compute()
|
||||
|
||||
def test_unprivileged(self):
|
||||
test_user = common.new_test_user(
|
||||
self.env, "mis_you", groups="base.group_user,account.group_account_readonly"
|
||||
)
|
||||
self.report_instance.with_user(test_user).compute()
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
# Copyright 2025 ACSONE SA/NV
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import Command
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestMisReportInstanceAnnotation(TransactionCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.report = self.env["mis.report"].create(
|
||||
dict(
|
||||
name="test report",
|
||||
subkpi_ids=[
|
||||
Command.create(
|
||||
dict(
|
||||
name="subkpi1_report2",
|
||||
description="subkpi 1, report 2",
|
||||
sequence=1,
|
||||
)
|
||||
),
|
||||
Command.create(
|
||||
dict(
|
||||
name="subkpi2_report2",
|
||||
description="subkpi 2, report 2",
|
||||
sequence=2,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
self.kpi = self.env["mis.report.kpi"].create(
|
||||
dict(
|
||||
report_id=self.report.id,
|
||||
description="kpi 1",
|
||||
name="k1",
|
||||
multi=True,
|
||||
expression_ids=[
|
||||
Command.create(
|
||||
dict(name="bale[200%]", subkpi_id=self.report.subkpi_ids[0].id),
|
||||
),
|
||||
Command.create(
|
||||
dict(name="balp[200%]", subkpi_id=self.report.subkpi_ids[1].id),
|
||||
),
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
self.report_instance = self.env["mis.report.instance"].create(
|
||||
dict(
|
||||
name="test instance",
|
||||
report_id=self.report.id,
|
||||
company_id=self.env.ref("base.main_company").id,
|
||||
period_ids=[
|
||||
Command.create(
|
||||
dict(
|
||||
name="p1",
|
||||
mode="fix",
|
||||
manual_date_from="2013-01-01",
|
||||
manual_date_to="2013-12-31",
|
||||
sequence=1,
|
||||
),
|
||||
),
|
||||
Command.create(
|
||||
dict(
|
||||
name="p2",
|
||||
mode="fix",
|
||||
manual_date_from="2014-01-01",
|
||||
manual_date_to="2014-12-31",
|
||||
sequence=2,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
def test_adding_note(self):
|
||||
notes = self.report_instance.get_notes_by_cell_id()
|
||||
|
||||
self.assertEqual({}, notes)
|
||||
|
||||
# report with 4 cells, 2 periods and 2 subkpis
|
||||
matrix = self.report_instance._compute_matrix()
|
||||
cell_ids = [c.cell_id for row in matrix.iter_rows() for c in row.iter_cells()]
|
||||
self.assertEqual(len(cell_ids), 4)
|
||||
|
||||
first_cell_id, second_cell_id, third_cell_id, _fourth_cell_id = cell_ids
|
||||
|
||||
# adding one note
|
||||
self.env["mis.report.instance.annotation"].set_annotation(
|
||||
first_cell_id, self.report_instance.id, "This is a note"
|
||||
)
|
||||
notes = self.report_instance.get_notes_by_cell_id()
|
||||
self.assertDictEqual(
|
||||
{first_cell_id: {"text": "This is a note", "sequence": 1}}, notes
|
||||
)
|
||||
|
||||
# adding another note
|
||||
self.env["mis.report.instance.annotation"].set_annotation(
|
||||
third_cell_id, self.report_instance.id, "This is another note"
|
||||
)
|
||||
notes = self.report_instance.get_notes_by_cell_id()
|
||||
self.assertDictEqual(
|
||||
{
|
||||
first_cell_id: {"text": "This is a note", "sequence": 1},
|
||||
third_cell_id: {"text": "This is another note", "sequence": 2},
|
||||
},
|
||||
notes,
|
||||
)
|
||||
|
||||
self.env["mis.report.instance.annotation"].set_annotation(
|
||||
second_cell_id, self.report_instance.id, "This is third note"
|
||||
)
|
||||
|
||||
notes = self.report_instance.get_notes_by_cell_id()
|
||||
# Last note added should have a sequence of
|
||||
# 2 since it is deplayed in the second cell
|
||||
self.assertDictEqual(
|
||||
{
|
||||
first_cell_id: {"text": "This is a note", "sequence": 1},
|
||||
second_cell_id: {"text": "This is third note", "sequence": 2},
|
||||
third_cell_id: {"text": "This is another note", "sequence": 3},
|
||||
},
|
||||
notes,
|
||||
)
|
||||
|
||||
def test_remove_note(self):
|
||||
notes = self.report_instance.get_notes_by_cell_id()
|
||||
|
||||
self.assertEqual({}, notes)
|
||||
|
||||
# report with 4 cells, 2 periods and 2 subkpis
|
||||
matrix = self.report_instance._compute_matrix()
|
||||
cell_ids = [c.cell_id for row in matrix.iter_rows() for c in row.iter_cells()]
|
||||
self.assertEqual(len(cell_ids), 4)
|
||||
|
||||
first_cell_id = cell_ids[0]
|
||||
|
||||
# adding one note
|
||||
self.env["mis.report.instance.annotation"].set_annotation(
|
||||
first_cell_id, self.report_instance.id, "This is a note"
|
||||
)
|
||||
notes = self.report_instance.get_notes_by_cell_id()
|
||||
self.assertDictEqual(
|
||||
{first_cell_id: {"text": "This is a note", "sequence": 1}}, notes
|
||||
)
|
||||
|
||||
# remove note
|
||||
self.env["mis.report.instance.annotation"].remove_annotation(
|
||||
first_cell_id, self.report_instance.id
|
||||
)
|
||||
notes = self.report_instance.get_notes_by_cell_id()
|
||||
self.assertEqual({}, notes)
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# Copyright 2016 ACSONE SA/NV (<http://acsone.eu>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
import odoo.tests.common as common
|
||||
|
||||
from ..models.mis_safe_eval import DataError, NameDataError, mis_safe_eval
|
||||
|
||||
|
||||
class TestMisSafeEval(common.TransactionCase):
|
||||
def test_nominal(self):
|
||||
val = mis_safe_eval("a + 1", {"a": 1})
|
||||
self.assertEqual(val, 2)
|
||||
|
||||
def test_exceptions(self):
|
||||
val = mis_safe_eval("1/0", {}) # division by zero
|
||||
self.assertTrue(isinstance(val, DataError))
|
||||
self.assertEqual(val.name, "#DIV/0")
|
||||
val = mis_safe_eval("1a", {}) # syntax error
|
||||
self.assertTrue(isinstance(val, DataError))
|
||||
self.assertEqual(val.name, "#ERR")
|
||||
|
||||
def test_name_error(self):
|
||||
val = mis_safe_eval("a + 1", {})
|
||||
self.assertTrue(isinstance(val, NameDataError))
|
||||
self.assertEqual(val.name, "#NAME")
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
# Copyright 2014 ACSONE SA/NV (<http://acsone.eu>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
import datetime
|
||||
|
||||
import odoo.tests.common as common
|
||||
from odoo import fields
|
||||
from odoo.tools.safe_eval import safe_eval
|
||||
|
||||
from ..models.accounting_none import AccountingNone
|
||||
from ..models.aep import AccountingExpressionProcessor as AEP
|
||||
|
||||
|
||||
class TestMultiCompanyAEP(common.TransactionCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.res_company = self.env["res.company"]
|
||||
self.account_model = self.env["account.account"]
|
||||
self.move_model = self.env["account.move"]
|
||||
self.journal_model = self.env["account.journal"]
|
||||
self.currency_model = self.env["res.currency"]
|
||||
self.curr_year = datetime.date.today().year
|
||||
self.prev_year = self.curr_year - 1
|
||||
self.usd = self.currency_model.with_context(active_test=False).search(
|
||||
[("name", "=", "USD")]
|
||||
)
|
||||
self.eur = self.currency_model.with_context(active_test=False).search(
|
||||
[("name", "=", "EUR")]
|
||||
)
|
||||
# create company A and B
|
||||
self.company_eur = self.res_company.create(
|
||||
{"name": "CYEUR", "currency_id": self.eur.id}
|
||||
)
|
||||
self.company_usd = self.res_company.create(
|
||||
{"name": "CYUSD", "currency_id": self.usd.id}
|
||||
)
|
||||
self.env["res.currency.rate"].search([]).unlink()
|
||||
for company, divider in [(self.company_eur, 1.0), (self.company_usd, 2.0)]:
|
||||
# create receivable bs account
|
||||
company_key = company.name
|
||||
setattr(
|
||||
self,
|
||||
"account_ar_" + company_key,
|
||||
self.account_model.create(
|
||||
{
|
||||
"company_id": company.id,
|
||||
"code": "400AR",
|
||||
"name": "Receivable",
|
||||
"account_type": "asset_receivable",
|
||||
"reconcile": True,
|
||||
}
|
||||
),
|
||||
)
|
||||
# create income pl account
|
||||
setattr(
|
||||
self,
|
||||
"account_in_" + company_key,
|
||||
self.account_model.create(
|
||||
{
|
||||
"company_id": company.id,
|
||||
"code": "700IN",
|
||||
"name": "Income",
|
||||
"account_type": "income",
|
||||
}
|
||||
),
|
||||
)
|
||||
# create journal
|
||||
setattr(
|
||||
self,
|
||||
"journal" + company_key,
|
||||
self.journal_model.create(
|
||||
{
|
||||
"company_id": company.id,
|
||||
"name": "Sale journal",
|
||||
"code": "VEN",
|
||||
"type": "sale",
|
||||
}
|
||||
),
|
||||
)
|
||||
# create move in december last year
|
||||
self._create_move(
|
||||
journal=getattr(self, "journal" + company_key),
|
||||
date=datetime.date(self.prev_year, 12, 1),
|
||||
amount=100 / divider,
|
||||
debit_acc=getattr(self, "account_ar_" + company_key),
|
||||
credit_acc=getattr(self, "account_in_" + company_key),
|
||||
)
|
||||
# create move in january this year
|
||||
self._create_move(
|
||||
journal=getattr(self, "journal" + company_key),
|
||||
date=datetime.date(self.curr_year, 1, 1),
|
||||
amount=300 / divider,
|
||||
debit_acc=getattr(self, "account_ar_" + company_key),
|
||||
credit_acc=getattr(self, "account_in_" + company_key),
|
||||
)
|
||||
# create move in february this year
|
||||
self._create_move(
|
||||
journal=getattr(self, "journal" + company_key),
|
||||
date=datetime.date(self.curr_year, 3, 1),
|
||||
amount=500 / divider,
|
||||
debit_acc=getattr(self, "account_ar_" + company_key),
|
||||
credit_acc=getattr(self, "account_in_" + company_key),
|
||||
)
|
||||
|
||||
def _create_move(self, journal, date, amount, debit_acc, credit_acc):
|
||||
move = self.move_model.create(
|
||||
{
|
||||
"journal_id": journal.id,
|
||||
"date": fields.Date.to_string(date),
|
||||
"line_ids": [
|
||||
(0, 0, {"name": "/", "debit": amount, "account_id": debit_acc.id}),
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{"name": "/", "credit": amount, "account_id": credit_acc.id},
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
move._post()
|
||||
return move
|
||||
|
||||
def _do_queries(self, companies, currency, date_from, date_to):
|
||||
# create the AEP, and prepare the expressions we'll need
|
||||
aep = AEP(companies, currency)
|
||||
aep.parse_expr("bali[]")
|
||||
aep.parse_expr("bale[]")
|
||||
aep.parse_expr("balp[]")
|
||||
aep.parse_expr("balu[]")
|
||||
aep.parse_expr("bali[700IN]")
|
||||
aep.parse_expr("bale[700IN]")
|
||||
aep.parse_expr("balp[700IN]")
|
||||
aep.parse_expr("bali[400AR]")
|
||||
aep.parse_expr("bale[400AR]")
|
||||
aep.parse_expr("balp[400AR]")
|
||||
aep.parse_expr("debp[400A%]")
|
||||
aep.parse_expr("crdp[700I%]")
|
||||
aep.parse_expr("bali[400%]")
|
||||
aep.parse_expr("bale[700%]")
|
||||
aep.done_parsing()
|
||||
aep.do_queries(
|
||||
date_from=fields.Date.to_string(date_from),
|
||||
date_to=fields.Date.to_string(date_to),
|
||||
)
|
||||
return aep
|
||||
|
||||
def _eval(self, aep, expr):
|
||||
eval_dict = {"AccountingNone": AccountingNone}
|
||||
return safe_eval(aep.replace_expr(expr), eval_dict)
|
||||
|
||||
def _eval_by_account_id(self, aep, expr):
|
||||
res = {}
|
||||
eval_dict = {"AccountingNone": AccountingNone}
|
||||
for account_id, replaced_exprs in aep.replace_exprs_by_account_id([expr]):
|
||||
res[account_id] = safe_eval(replaced_exprs[0], eval_dict)
|
||||
return res
|
||||
|
||||
def test_aep_basic(self):
|
||||
# let's query for december, one company
|
||||
aep = self._do_queries(
|
||||
self.company_eur,
|
||||
None,
|
||||
datetime.date(self.prev_year, 12, 1),
|
||||
datetime.date(self.prev_year, 12, 31),
|
||||
)
|
||||
self.assertEqual(self._eval(aep, "balp[700IN]"), -100)
|
||||
aep = self._do_queries(
|
||||
self.company_usd,
|
||||
None,
|
||||
datetime.date(self.prev_year, 12, 1),
|
||||
datetime.date(self.prev_year, 12, 31),
|
||||
)
|
||||
self.assertEqual(self._eval(aep, "balp[700IN]"), -50)
|
||||
# let's query for december, two companies
|
||||
aep = self._do_queries(
|
||||
self.company_eur | self.company_usd,
|
||||
self.eur,
|
||||
datetime.date(self.prev_year, 12, 1),
|
||||
datetime.date(self.prev_year, 12, 31),
|
||||
)
|
||||
self.assertEqual(self._eval(aep, "balp[700IN]"), -150)
|
||||
|
||||
def test_aep_multi_currency(self):
|
||||
date_from = datetime.date(self.prev_year, 12, 1)
|
||||
date_to = datetime.date(self.prev_year, 12, 31)
|
||||
today = datetime.date.today()
|
||||
self.env["res.currency.rate"].create(
|
||||
dict(currency_id=self.usd.id, name=date_to, rate=1.1)
|
||||
)
|
||||
self.env["res.currency.rate"].create(
|
||||
dict(currency_id=self.usd.id, name=today, rate=1.2)
|
||||
)
|
||||
# let's query for december, one company, default currency = eur
|
||||
aep = self._do_queries(self.company_eur, None, date_from, date_to)
|
||||
self.assertEqual(self._eval(aep, "balp[700IN]"), -100)
|
||||
# let's query for december, two companies
|
||||
aep = self._do_queries(
|
||||
self.company_eur | self.company_usd, self.eur, date_from, date_to
|
||||
)
|
||||
self.assertAlmostEqual(self._eval(aep, "balp[700IN]"), -100 - 50 / 1.1)
|
||||
# let's query for december, one company, currency = usd
|
||||
aep = self._do_queries(self.company_eur, self.usd, date_from, date_to)
|
||||
self.assertAlmostEqual(self._eval(aep, "balp[700IN]"), -100 * 1.1)
|
||||
# let's query for december, two companies, currency = usd
|
||||
aep = self._do_queries(
|
||||
self.company_eur | self.company_usd, self.usd, date_from, date_to
|
||||
)
|
||||
self.assertAlmostEqual(self._eval(aep, "balp[700IN]"), -100 * 1.1 - 50)
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
# Copyright 2017 ACSONE SA/NV (<http://acsone.eu>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
import odoo.tests.common as common
|
||||
from odoo import fields
|
||||
|
||||
from ..models.mis_report_instance import (
|
||||
MODE_FIX,
|
||||
MODE_NONE,
|
||||
MODE_REL,
|
||||
SRC_SUMCOL,
|
||||
DateFilterForbidden,
|
||||
DateFilterRequired,
|
||||
)
|
||||
from .common import assert_matrix
|
||||
|
||||
|
||||
class TestPeriodDates(common.TransactionCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.report_obj = self.env["mis.report"]
|
||||
self.instance_obj = self.env["mis.report.instance"]
|
||||
self.period_obj = self.env["mis.report.instance.period"]
|
||||
self.report = self.report_obj.create(dict(name="test-report"))
|
||||
self.instance = self.instance_obj.create(
|
||||
dict(name="test-instance", report_id=self.report.id, comparison_mode=False)
|
||||
)
|
||||
self.assertEqual(len(self.instance.period_ids), 1)
|
||||
self.period = self.instance.period_ids[0]
|
||||
|
||||
def assertDateEqual(self, first, second, msg=None):
|
||||
self.assertEqual(first, fields.Date.from_string(second), msg)
|
||||
|
||||
def test_date_filter_constraints(self):
|
||||
self.instance.comparison_mode = True
|
||||
with self.assertRaises(DateFilterRequired):
|
||||
self.period.write(dict(mode=MODE_NONE))
|
||||
with self.assertRaises(DateFilterForbidden):
|
||||
self.period.write(dict(mode=MODE_FIX, source=SRC_SUMCOL))
|
||||
|
||||
def test_simple_mode(self):
|
||||
# not comparison_mode
|
||||
self.assertFalse(self.instance.comparison_mode)
|
||||
period = self.instance.period_ids[0]
|
||||
self.assertEqual(period.date_from, self.instance.date_from)
|
||||
self.assertEqual(period.date_to, self.instance.date_to)
|
||||
|
||||
def tests_mode_none(self):
|
||||
self.instance.comparison_mode = True
|
||||
self.period.write(dict(mode=MODE_NONE, source=SRC_SUMCOL))
|
||||
self.assertFalse(self.period.date_from)
|
||||
self.assertFalse(self.period.date_to)
|
||||
self.assertTrue(self.period.valid)
|
||||
|
||||
def tests_mode_fix(self):
|
||||
self.instance.comparison_mode = True
|
||||
self.period.write(
|
||||
dict(
|
||||
mode=MODE_FIX,
|
||||
manual_date_from="2017-01-01",
|
||||
manual_date_to="2017-12-31",
|
||||
)
|
||||
)
|
||||
self.assertDateEqual(self.period.date_from, "2017-01-01")
|
||||
self.assertDateEqual(self.period.date_to, "2017-12-31")
|
||||
self.assertTrue(self.period.valid)
|
||||
|
||||
def test_rel_day(self):
|
||||
self.instance.write(dict(comparison_mode=True, date="2017-01-01"))
|
||||
self.period.write(dict(mode=MODE_REL, type="d", offset="-2"))
|
||||
self.assertDateEqual(self.period.date_from, "2016-12-30")
|
||||
self.assertDateEqual(self.period.date_to, "2016-12-30")
|
||||
self.assertTrue(self.period.valid)
|
||||
|
||||
def test_rel_day_ytd(self):
|
||||
self.instance.write(dict(comparison_mode=True, date="2019-05-03"))
|
||||
self.period.write(dict(mode=MODE_REL, type="d", offset="-2", is_ytd=True))
|
||||
self.assertDateEqual(self.period.date_from, "2019-01-01")
|
||||
self.assertDateEqual(self.period.date_to, "2019-05-01")
|
||||
self.assertTrue(self.period.valid)
|
||||
|
||||
def test_rel_week(self):
|
||||
self.instance.write(dict(comparison_mode=True, date="2016-12-30"))
|
||||
self.period.write(dict(mode=MODE_REL, type="w", offset="1", duration=2))
|
||||
# from Monday to Sunday, the week after 2016-12-30
|
||||
self.assertDateEqual(self.period.date_from, "2017-01-02")
|
||||
self.assertDateEqual(self.period.date_to, "2017-01-15")
|
||||
self.assertTrue(self.period.valid)
|
||||
|
||||
def test_rel_week_ytd(self):
|
||||
self.instance.write(dict(comparison_mode=True, date="2019-05-27"))
|
||||
self.period.write(
|
||||
dict(mode=MODE_REL, type="w", offset="1", duration=2, is_ytd=True)
|
||||
)
|
||||
self.assertDateEqual(self.period.date_from, "2019-01-01")
|
||||
self.assertDateEqual(self.period.date_to, "2019-06-16")
|
||||
self.assertTrue(self.period.valid)
|
||||
|
||||
def test_rel_month(self):
|
||||
self.instance.write(dict(comparison_mode=True, date="2017-01-05"))
|
||||
self.period.write(dict(mode=MODE_REL, type="m", offset="1"))
|
||||
self.assertDateEqual(self.period.date_from, "2017-02-01")
|
||||
self.assertDateEqual(self.period.date_to, "2017-02-28")
|
||||
self.assertTrue(self.period.valid)
|
||||
|
||||
def test_rel_month_ytd(self):
|
||||
self.instance.write(dict(comparison_mode=True, date="2019-05-15"))
|
||||
self.period.write(dict(mode=MODE_REL, type="m", offset="-1", is_ytd=True))
|
||||
self.assertDateEqual(self.period.date_from, "2019-01-01")
|
||||
self.assertDateEqual(self.period.date_to, "2019-04-30")
|
||||
self.assertTrue(self.period.valid)
|
||||
|
||||
def test_rel_year(self):
|
||||
self.instance.write(dict(comparison_mode=True, date="2017-05-06"))
|
||||
self.period.write(dict(mode=MODE_REL, type="y", offset="1"))
|
||||
self.assertDateEqual(self.period.date_from, "2018-01-01")
|
||||
self.assertDateEqual(self.period.date_to, "2018-12-31")
|
||||
self.assertTrue(self.period.valid)
|
||||
|
||||
def test_rel_date_range(self):
|
||||
# create a few date ranges
|
||||
date_range_type = self.env["date.range.type"].create(dict(name="Year"))
|
||||
for year in (2016, 2017, 2018):
|
||||
self.env["date.range"].create(
|
||||
dict(
|
||||
type_id=date_range_type.id,
|
||||
name="%d" % year,
|
||||
date_start="%d-01-01" % year,
|
||||
date_end="%d-12-31" % year,
|
||||
company_id=date_range_type.company_id.id,
|
||||
)
|
||||
)
|
||||
self.instance.write(dict(comparison_mode=True, date="2017-06-15"))
|
||||
self.period.write(
|
||||
dict(
|
||||
mode=MODE_REL,
|
||||
type="date_range",
|
||||
date_range_type_id=date_range_type.id,
|
||||
offset="-1",
|
||||
duration=3,
|
||||
)
|
||||
)
|
||||
self.assertDateEqual(self.period.date_from, "2016-01-01")
|
||||
self.assertDateEqual(self.period.date_to, "2018-12-31")
|
||||
self.assertTrue(self.period.valid)
|
||||
|
||||
def test_dates_in_expr(self):
|
||||
self.env["mis.report.kpi"].create(
|
||||
dict(
|
||||
report_id=self.report.id,
|
||||
name="k1",
|
||||
description="kpi 1",
|
||||
expression="(date_to - date_from).days + 1",
|
||||
)
|
||||
)
|
||||
self.instance.date_from = "2017-01-01"
|
||||
self.instance.date_to = "2017-01-31"
|
||||
matrix = self.instance._compute_matrix()
|
||||
assert_matrix(matrix, [[31]])
|
||||
|
|
@ -0,0 +1,315 @@
|
|||
# Copyright 2016 ACSONE SA/NV (<http://acsone.eu>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
import odoo.tests.common as common
|
||||
|
||||
from ..models.accounting_none import AccountingNone
|
||||
from ..models.data_error import DataError
|
||||
from ..models.mis_report_style import CMP_DIFF, CMP_PCT, TYPE_NUM, TYPE_PCT, TYPE_STR
|
||||
|
||||
|
||||
class TestRendering(common.TransactionCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.style_obj = self.env["mis.report.style"]
|
||||
self.kpi_obj = self.env["mis.report.kpi"]
|
||||
self.style = self.style_obj.create(dict(name="teststyle"))
|
||||
self.lang = (
|
||||
self.env["res.lang"]
|
||||
.with_context(active_test=False)
|
||||
.search([("code", "=", "en_US")])[0]
|
||||
)
|
||||
|
||||
def _render(self, value, var_type=TYPE_NUM):
|
||||
style_props = self.style_obj.merge([self.style])
|
||||
return self.style_obj.render(self.lang, style_props, var_type, value)
|
||||
|
||||
def _compare_and_render(
|
||||
self, value, base_value, var_type=TYPE_NUM, compare_method=CMP_PCT
|
||||
):
|
||||
style_props = self.style_obj.merge([self.style])
|
||||
r = self.style_obj.compare_and_render(
|
||||
self.lang, style_props, var_type, compare_method, value, base_value
|
||||
)[:2]
|
||||
if r[0]:
|
||||
return (round(r[0], 8), r[1])
|
||||
else:
|
||||
return r
|
||||
|
||||
def test_render(self):
|
||||
self.assertEqual("1", self._render(1))
|
||||
self.assertEqual("1", self._render(1.1))
|
||||
self.assertEqual("2", self._render(1.6))
|
||||
self.style.dp_inherit = False
|
||||
self.style.dp = 2
|
||||
self.assertEqual("1.00", self._render(1))
|
||||
self.assertEqual("1.10", self._render(1.1))
|
||||
self.assertEqual("1.60", self._render(1.6))
|
||||
self.assertEqual("1.61", self._render(1.606))
|
||||
self.assertEqual("12,345.67", self._render(12345.67))
|
||||
|
||||
def test_render_negative(self):
|
||||
# non breaking hyphen
|
||||
self.assertEqual("\u20111", self._render(-1))
|
||||
|
||||
def test_render_zero(self):
|
||||
self.assertEqual("0", self._render(0))
|
||||
self.assertEqual("", self._render(None))
|
||||
self.assertEqual("", self._render(AccountingNone))
|
||||
|
||||
def test_render_suffix(self):
|
||||
self.style.suffix_inherit = False
|
||||
self.style.suffix = "€"
|
||||
self.assertEqual("1\xa0€", self._render(1))
|
||||
self.style.suffix = "k€"
|
||||
self.style.divider_inherit = False
|
||||
self.style.divider = "1e3"
|
||||
self.assertEqual("1\xa0k€", self._render(1000))
|
||||
|
||||
def test_render_prefix(self):
|
||||
self.style.prefix_inherit = False
|
||||
self.style.prefix = "$"
|
||||
self.assertEqual("$\xa01", self._render(1))
|
||||
self.style.prefix = "k$"
|
||||
self.style.divider_inherit = False
|
||||
self.style.divider = "1e3"
|
||||
self.assertEqual("k$\xa01", self._render(1000))
|
||||
|
||||
def test_render_divider(self):
|
||||
self.style.divider_inherit = False
|
||||
self.style.divider = "1e3"
|
||||
self.style.dp_inherit = False
|
||||
self.style.dp = 0
|
||||
self.assertEqual("1", self._render(1000))
|
||||
self.style.divider = "1e6"
|
||||
self.style.dp = 3
|
||||
self.assertEqual("0.001", self._render(1000))
|
||||
self.style.divider = "1e-3"
|
||||
self.style.dp = 0
|
||||
self.assertEqual("1,000", self._render(1))
|
||||
self.style.divider = "1e-6"
|
||||
self.style.dp = 0
|
||||
self.assertEqual("1,000,000", self._render(1))
|
||||
|
||||
def test_render_pct(self):
|
||||
self.assertEqual("100\xa0%", self._render(1, TYPE_PCT))
|
||||
self.assertEqual("50\xa0%", self._render(0.5, TYPE_PCT))
|
||||
self.style.dp_inherit = False
|
||||
self.style.dp = 2
|
||||
self.assertEqual("51.23\xa0%", self._render(0.5123, TYPE_PCT))
|
||||
|
||||
def test_render_string(self):
|
||||
self.assertEqual("", self._render("", TYPE_STR))
|
||||
self.assertEqual("", self._render(None, TYPE_STR))
|
||||
self.assertEqual("abcdé", self._render("abcdé", TYPE_STR))
|
||||
|
||||
def test_compare_num_pct(self):
|
||||
self.assertEqual((1.0, "+100.0\xa0%"), self._compare_and_render(100, 50))
|
||||
self.assertEqual((0.5, "+50.0\xa0%"), self._compare_and_render(75, 50))
|
||||
self.assertEqual((0.5, "+50.0\xa0%"), self._compare_and_render(-25, -50))
|
||||
self.assertEqual((1.0, "+100.0\xa0%"), self._compare_and_render(0, -50))
|
||||
self.assertEqual((2.0, "+200.0\xa0%"), self._compare_and_render(50, -50))
|
||||
self.assertEqual((-0.5, "\u201150.0\xa0%"), self._compare_and_render(25, 50))
|
||||
self.assertEqual((-1.0, "\u2011100.0\xa0%"), self._compare_and_render(0, 50))
|
||||
self.assertEqual((-2.0, "\u2011200.0\xa0%"), self._compare_and_render(-50, 50))
|
||||
self.assertEqual((-0.5, "\u201150.0\xa0%"), self._compare_and_render(-75, -50))
|
||||
self.assertEqual(
|
||||
(AccountingNone, ""), self._compare_and_render(50, AccountingNone)
|
||||
)
|
||||
self.assertEqual((AccountingNone, ""), self._compare_and_render(50, None))
|
||||
self.assertEqual((AccountingNone, ""), self._compare_and_render(50, 50))
|
||||
self.assertEqual((0.002, "+0.2\xa0%"), self._compare_and_render(50.1, 50))
|
||||
self.assertEqual((AccountingNone, ""), self._compare_and_render(50.01, 50))
|
||||
self.assertEqual(
|
||||
(-1.0, "\u2011100.0\xa0%"), self._compare_and_render(AccountingNone, 50)
|
||||
)
|
||||
self.assertEqual((-1.0, "\u2011100.0\xa0%"), self._compare_and_render(None, 50))
|
||||
self.assertEqual(
|
||||
(AccountingNone, ""), self._compare_and_render(DataError("#ERR", "."), 1)
|
||||
)
|
||||
self.assertEqual(
|
||||
(AccountingNone, ""), self._compare_and_render(1, DataError("#ERR", "."))
|
||||
)
|
||||
|
||||
def test_compare_num_diff(self):
|
||||
self.assertEqual(
|
||||
(25, "+25"), self._compare_and_render(75, 50, TYPE_NUM, CMP_DIFF)
|
||||
)
|
||||
self.assertEqual(
|
||||
(-25, "\u201125"), self._compare_and_render(25, 50, TYPE_NUM, CMP_DIFF)
|
||||
)
|
||||
self.style.suffix_inherit = False
|
||||
self.style.suffix = "€"
|
||||
self.assertEqual(
|
||||
(-25, "\u201125\xa0€"),
|
||||
self._compare_and_render(25, 50, TYPE_NUM, CMP_DIFF),
|
||||
)
|
||||
self.style.suffix = ""
|
||||
self.assertEqual(
|
||||
(50.0, "+50"),
|
||||
self._compare_and_render(50, AccountingNone, TYPE_NUM, CMP_DIFF),
|
||||
)
|
||||
self.assertEqual(
|
||||
(50.0, "+50"), self._compare_and_render(50, None, TYPE_NUM, CMP_DIFF)
|
||||
)
|
||||
self.assertEqual(
|
||||
(-50.0, "\u201150"),
|
||||
self._compare_and_render(AccountingNone, 50, TYPE_NUM, CMP_DIFF),
|
||||
)
|
||||
self.assertEqual(
|
||||
(-50.0, "\u201150"), self._compare_and_render(None, 50, TYPE_NUM, CMP_DIFF)
|
||||
)
|
||||
self.style.dp_inherit = False
|
||||
self.style.dp = 2
|
||||
self.assertEqual(
|
||||
(0.1, "+0.10"), self._compare_and_render(1.1, 1.0, TYPE_NUM, CMP_DIFF)
|
||||
)
|
||||
self.assertEqual(
|
||||
(AccountingNone, ""),
|
||||
self._compare_and_render(1.001, 1.0, TYPE_NUM, CMP_DIFF),
|
||||
)
|
||||
|
||||
def test_compare_pct(self):
|
||||
self.assertEqual(
|
||||
(0.25, "+25\xa0pp"), self._compare_and_render(0.75, 0.50, TYPE_PCT)
|
||||
)
|
||||
self.assertEqual(
|
||||
(AccountingNone, ""), self._compare_and_render(0.751, 0.750, TYPE_PCT)
|
||||
)
|
||||
|
||||
def test_compare_pct_result_type(self):
|
||||
style_props = self.style_obj.merge([self.style])
|
||||
result = self.style_obj.compare_and_render(
|
||||
self.lang, style_props, TYPE_PCT, CMP_DIFF, 0.75, 0.50
|
||||
)
|
||||
self.assertEqual(result[3], TYPE_NUM)
|
||||
|
||||
def test_merge(self):
|
||||
self.style.color = "#FF0000"
|
||||
self.style.color_inherit = False
|
||||
style_props = self.style_obj.merge([self.style])
|
||||
self.assertEqual(style_props, {"color": "#FF0000"})
|
||||
style_dict = {"color": "#00FF00", "dp": 0}
|
||||
style_props = self.style_obj.merge([self.style, style_dict])
|
||||
self.assertEqual(style_props, {"color": "#00FF00", "dp": 0})
|
||||
style2 = self.style_obj.create(
|
||||
dict(
|
||||
name="teststyle2",
|
||||
dp_inherit=False,
|
||||
dp=1,
|
||||
# color_inherit=True: will not be applied
|
||||
color="#0000FF",
|
||||
)
|
||||
)
|
||||
style_props = self.style_obj.merge([self.style, style_dict, style2])
|
||||
self.assertEqual(style_props, {"color": "#00FF00", "dp": 1})
|
||||
|
||||
def test_css(self):
|
||||
self.style.color_inherit = False
|
||||
self.style.color = "#FF0000"
|
||||
self.style.background_color_inherit = False
|
||||
self.style.background_color = "#0000FF"
|
||||
self.style.suffix_inherit = False
|
||||
self.style.suffix = "s"
|
||||
self.style.prefix_inherit = False
|
||||
self.style.prefix = "p"
|
||||
self.style.font_style_inherit = False
|
||||
self.style.font_style = "italic"
|
||||
self.style.font_weight_inherit = False
|
||||
self.style.font_weight = "bold"
|
||||
self.style.font_size_inherit = False
|
||||
self.style.font_size = "small"
|
||||
self.style.indent_level_inherit = False
|
||||
self.style.indent_level = 2
|
||||
style_props = self.style_obj.merge([self.style])
|
||||
css = self.style_obj.to_css_style(style_props)
|
||||
self.assertEqual(
|
||||
css,
|
||||
"font-style: italic; "
|
||||
"font-weight: bold; "
|
||||
"font-size: small; "
|
||||
"color: #FF0000; "
|
||||
"background-color: #0000FF; "
|
||||
"text-indent: 2em",
|
||||
)
|
||||
css = self.style_obj.to_css_style(style_props, no_indent=True)
|
||||
self.assertEqual(
|
||||
css,
|
||||
"font-style: italic; "
|
||||
"font-weight: bold; "
|
||||
"font-size: small; "
|
||||
"color: #FF0000; "
|
||||
"background-color: #0000FF",
|
||||
)
|
||||
|
||||
def test_xslx(self):
|
||||
self.style.color_inherit = False
|
||||
self.style.color = "#FF0000"
|
||||
self.style.background_color_inherit = False
|
||||
self.style.background_color = "#0000FF"
|
||||
self.style.suffix_inherit = False
|
||||
self.style.suffix = "s"
|
||||
self.style.prefix_inherit = False
|
||||
self.style.prefix = "p"
|
||||
self.style.dp_inherit = False
|
||||
self.style.dp = 2
|
||||
self.style.font_style_inherit = False
|
||||
self.style.font_style = "italic"
|
||||
self.style.font_weight_inherit = False
|
||||
self.style.font_weight = "bold"
|
||||
self.style.font_size_inherit = False
|
||||
self.style.font_size = "small"
|
||||
self.style.indent_level_inherit = False
|
||||
self.style.indent_level = 2
|
||||
style_props = self.style_obj.merge([self.style])
|
||||
xlsx = self.style_obj.to_xlsx_style(TYPE_NUM, style_props)
|
||||
self.assertEqual(
|
||||
xlsx,
|
||||
{
|
||||
"italic": True,
|
||||
"bold": True,
|
||||
"font_size": 9,
|
||||
"font_color": "#FF0000",
|
||||
"bg_color": "#0000FF",
|
||||
"num_format": '"p "#,##0.00" s"',
|
||||
"indent": 2,
|
||||
},
|
||||
)
|
||||
xlsx = self.style_obj.to_xlsx_style(TYPE_NUM, style_props, no_indent=True)
|
||||
self.assertEqual(
|
||||
xlsx,
|
||||
{
|
||||
"italic": True,
|
||||
"bold": True,
|
||||
"font_size": 9,
|
||||
"font_color": "#FF0000",
|
||||
"bg_color": "#0000FF",
|
||||
"num_format": '"p "#,##0.00" s"',
|
||||
},
|
||||
)
|
||||
# percent type ignore prefix and suffix
|
||||
xlsx = self.style_obj.to_xlsx_style(TYPE_PCT, style_props, no_indent=True)
|
||||
self.assertEqual(
|
||||
xlsx,
|
||||
{
|
||||
"italic": True,
|
||||
"bold": True,
|
||||
"font_size": 9,
|
||||
"font_color": "#FF0000",
|
||||
"bg_color": "#0000FF",
|
||||
"num_format": "0.00%",
|
||||
},
|
||||
)
|
||||
|
||||
# str type have no num_format style
|
||||
xlsx = self.style_obj.to_xlsx_style(TYPE_STR, style_props, no_indent=True)
|
||||
self.assertEqual(
|
||||
xlsx,
|
||||
{
|
||||
"italic": True,
|
||||
"bold": True,
|
||||
"font_size": 9,
|
||||
"font_color": "#FF0000",
|
||||
"bg_color": "#0000FF",
|
||||
},
|
||||
)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Copyright 2014 ACSONE SA/NV (<http://acsone.eu>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from ..models import simple_array
|
||||
from .common import load_doctests
|
||||
|
||||
load_tests = load_doctests(simple_array)
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
# Copyright 2020 ACSONE SA/NV (<http://acsone.eu>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
from odoo.addons.mis_builder.models.expression_evaluator import ExpressionEvaluator
|
||||
from odoo.addons.mis_builder.models.mis_report_subreport import (
|
||||
InvalidNameError,
|
||||
ParentLoopError,
|
||||
)
|
||||
|
||||
|
||||
class TestMisSubreport(TransactionCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
# create report
|
||||
cls.subreport = cls.env["mis.report"].create(dict(name="test subreport"))
|
||||
cls.subreport_kpi1 = cls.env["mis.report.kpi"].create(
|
||||
dict(
|
||||
report_id=cls.subreport.id,
|
||||
name="sk1",
|
||||
description="subreport kpi 1",
|
||||
expression="11",
|
||||
)
|
||||
)
|
||||
cls.report = cls.env["mis.report"].create(
|
||||
dict(
|
||||
name="test report",
|
||||
subreport_ids=[
|
||||
(0, 0, dict(name="subreport", subreport_id=cls.subreport.id))
|
||||
],
|
||||
)
|
||||
)
|
||||
cls.report_kpi1 = cls.env["mis.report.kpi"].create(
|
||||
dict(
|
||||
report_id=cls.report.id,
|
||||
name="k1",
|
||||
description="report kpi 1",
|
||||
expression="subreport.sk1 + 1",
|
||||
)
|
||||
)
|
||||
cls.parent_report = cls.env["mis.report"].create(
|
||||
dict(
|
||||
name="parent report",
|
||||
subreport_ids=[(0, 0, dict(name="report", subreport_id=cls.report.id))],
|
||||
)
|
||||
)
|
||||
cls.parent_report_kpi1 = cls.env["mis.report.kpi"].create(
|
||||
dict(
|
||||
report_id=cls.parent_report.id,
|
||||
name="pk1",
|
||||
description="parent report kpi 1",
|
||||
expression="report.k1 + 1",
|
||||
)
|
||||
)
|
||||
|
||||
def test_basic(self):
|
||||
ee = ExpressionEvaluator(aep=None, date_from="2017-01-01", date_to="2017-01-16")
|
||||
d = self.report._evaluate(ee)
|
||||
assert d["k1"] == 12
|
||||
|
||||
def test_two_levels(self):
|
||||
ee = ExpressionEvaluator(aep=None, date_from="2017-01-01", date_to="2017-01-16")
|
||||
d = self.parent_report._evaluate(ee)
|
||||
assert d["pk1"] == 13
|
||||
|
||||
def test_detect_loop(self):
|
||||
with self.assertRaises(ParentLoopError):
|
||||
self.report.write(
|
||||
dict(
|
||||
subreport_ids=[
|
||||
(
|
||||
0,
|
||||
0,
|
||||
dict(name="preport1", subreport_id=self.parent_report.id),
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
with self.assertRaises(ParentLoopError):
|
||||
self.report.write(
|
||||
dict(
|
||||
subreport_ids=[
|
||||
(
|
||||
0,
|
||||
0,
|
||||
dict(name="preport2", subreport_id=self.report.id),
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
def test_invalid_name(self):
|
||||
with self.assertRaises(InvalidNameError):
|
||||
self.report.subreport_ids[0].name = "ab c"
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
# Copyright 2016 ACSONE SA/NV (<http://acsone.eu>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
import odoo.tests.common as common
|
||||
|
||||
|
||||
class TestMisReportInstance(common.TransactionCase):
|
||||
def test_supports_target_move_filter(self):
|
||||
self.assertTrue(
|
||||
self.env["mis.report"]._supports_target_move_filter("account.move.line")
|
||||
)
|
||||
|
||||
def test_supports_target_move_filter_no_parent_state(self):
|
||||
self.assertFalse(
|
||||
self.env["mis.report"]._supports_target_move_filter("account.move")
|
||||
)
|
||||
|
||||
def test_target_move_domain_posted(self):
|
||||
self.assertEqual(
|
||||
self.env["mis.report"]._get_target_move_domain(
|
||||
"posted", "account.move.line"
|
||||
),
|
||||
[("parent_state", "=", "posted")],
|
||||
)
|
||||
|
||||
def test_target_move_domain_all(self):
|
||||
self.assertEqual(
|
||||
self.env["mis.report"]._get_target_move_domain("all", "account.move.line"),
|
||||
[("parent_state", "in", ("posted", "draft"))],
|
||||
)
|
||||
|
||||
def test_target_move_domain_no_parent_state(self):
|
||||
"""Test get_target_move_domain on a model that has no parent_state."""
|
||||
self.assertEqual(
|
||||
self.env["mis.report"]._get_target_move_domain("all", "account.move"), []
|
||||
)
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# Copyright 2014 ACSONE SA/NV (<http://acsone.eu>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
import odoo.tests.common as common
|
||||
|
||||
from ..models.mis_report import _utc_midnight
|
||||
|
||||
|
||||
class TestUtcMidnight(common.TransactionCase):
|
||||
def test_utc_midnight(self):
|
||||
date_to_convert = "2014-07-05"
|
||||
date_time_convert = _utc_midnight(date_to_convert, "Europe/Brussels")
|
||||
self.assertEqual(date_time_convert, "2014-07-04 22:00:00")
|
||||
date_time_convert = _utc_midnight(date_to_convert, "Europe/Brussels", add_day=1)
|
||||
self.assertEqual(date_time_convert, "2014-07-05 22:00:00")
|
||||
date_time_convert = _utc_midnight(date_to_convert, "US/Pacific")
|
||||
self.assertEqual(date_time_convert, "2014-07-05 07:00:00")
|
||||
date_time_convert = _utc_midnight(date_to_convert, "US/Pacific", add_day=1)
|
||||
self.assertEqual(date_time_convert, "2014-07-06 07:00:00")
|
||||
Loading…
Add table
Add a link
Reference in a new issue