Initial commit: OCA Report packages (45 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:43:05 +02:00
commit 2f4db400df
2543 changed files with 469120 additions and 0 deletions

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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]])

View file

@ -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})

View file

@ -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()

View file

@ -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)

View file

@ -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")

View file

@ -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)

View file

@ -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]])

View file

@ -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",
},
)

View file

@ -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)

View file

@ -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"

View file

@ -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"), []
)

View file

@ -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")