odoo-addon-mis-builder 16.0.5.1.13__py3-none-any.whl → 16.0.5.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- odoo/addons/mis_builder/README.rst +1 -1
- odoo/addons/mis_builder/__manifest__.py +1 -1
- odoo/addons/mis_builder/i18n/mis_builder.pot +32 -3
- odoo/addons/mis_builder/models/aep.py +135 -35
- odoo/addons/mis_builder/models/aggregate.py +4 -4
- odoo/addons/mis_builder/static/description/index.html +1 -1
- odoo/addons/mis_builder/tests/test_aep.py +42 -3
- odoo/addons/mis_builder/views/mis_report.xml +9 -4
- {odoo_addon_mis_builder-16.0.5.1.13.dist-info → odoo_addon_mis_builder-16.0.5.2.0.dist-info}/METADATA +2 -2
- {odoo_addon_mis_builder-16.0.5.1.13.dist-info → odoo_addon_mis_builder-16.0.5.2.0.dist-info}/RECORD +12 -12
- {odoo_addon_mis_builder-16.0.5.1.13.dist-info → odoo_addon_mis_builder-16.0.5.2.0.dist-info}/WHEEL +0 -0
- {odoo_addon_mis_builder-16.0.5.1.13.dist-info → odoo_addon_mis_builder-16.0.5.2.0.dist-info}/top_level.txt +0 -0
@@ -7,7 +7,7 @@ MIS Builder
|
|
7
7
|
!! This file is generated by oca-gen-addon-readme !!
|
8
8
|
!! changes will be overwritten. !!
|
9
9
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
10
|
-
!! source digest: sha256:
|
10
|
+
!! source digest: sha256:2c8b76b489e5323dae1133488deb91d0bcbf86be319044b51399b56b566e3c8b
|
11
11
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
12
12
|
|
13
13
|
.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png
|
@@ -64,8 +64,9 @@ msgstr ""
|
|
64
64
|
#: model_terms:ir.ui.view,arch_db:mis_builder.mis_report_view_kpi_form
|
65
65
|
msgid ""
|
66
66
|
"<code>bal</code>, <code>crd</code>, <code>deb</code>, <code>\n"
|
67
|
-
" pbal</code>, <code>nbal</code> : balance, debit, credit,\n"
|
68
|
-
" positive balance, negative balance
|
67
|
+
" pbal</code>, <code>nbal</code>, <code>fld</code> : balance, debit, credit,\n"
|
68
|
+
" positive balance, negative balance,\n"
|
69
|
+
" other numerical field."
|
69
70
|
msgstr ""
|
70
71
|
|
71
72
|
#. module: mis_builder
|
@@ -1603,7 +1604,7 @@ msgstr ""
|
|
1603
1604
|
#: model_terms:ir.ui.view,arch_db:mis_builder.mis_report_view_kpi_form
|
1604
1605
|
msgid ""
|
1605
1606
|
"The following special elements are recognized in the expressions\n"
|
1606
|
-
" to compute accounting data: <code>{bal|crd|deb|pbal|nbal}{pieu}[account\n"
|
1607
|
+
" to compute accounting data: <code>{bal|crd|deb|pbal|nbal|fld}{pieu}(.fieldname)[account\n"
|
1607
1608
|
" selector][journal items domain]</code>."
|
1608
1609
|
msgstr ""
|
1609
1610
|
|
@@ -1704,6 +1705,27 @@ msgstr ""
|
|
1704
1705
|
msgid "You cannot sum period %s with itself."
|
1705
1706
|
msgstr ""
|
1706
1707
|
|
1708
|
+
#. module: mis_builder
|
1709
|
+
#. odoo-python
|
1710
|
+
#: code:addons/mis_builder/models/aep.py:0
|
1711
|
+
#, python-format
|
1712
|
+
msgid "`%(field)s` cannot have a field name in expression %(expr)s"
|
1713
|
+
msgstr ""
|
1714
|
+
|
1715
|
+
#. module: mis_builder
|
1716
|
+
#. odoo-python
|
1717
|
+
#: code:addons/mis_builder/models/aep.py:0
|
1718
|
+
#, python-format
|
1719
|
+
msgid "`fld` can only be used with mode `p` (variation) in expression %s"
|
1720
|
+
msgstr ""
|
1721
|
+
|
1722
|
+
#. module: mis_builder
|
1723
|
+
#. odoo-python
|
1724
|
+
#: code:addons/mis_builder/models/aep.py:0
|
1725
|
+
#, python-format
|
1726
|
+
msgid "`fld` must have a field name in exression %s"
|
1727
|
+
msgstr ""
|
1728
|
+
|
1707
1729
|
#. module: mis_builder
|
1708
1730
|
#. odoo-python
|
1709
1731
|
#: code:addons/mis_builder/models/mis_report_instance.py:0
|
@@ -1744,6 +1766,13 @@ msgstr ""
|
|
1744
1766
|
msgid "versus"
|
1745
1767
|
msgstr ""
|
1746
1768
|
|
1769
|
+
#. module: mis_builder
|
1770
|
+
#: model_terms:ir.ui.view,arch_db:mis_builder.mis_report_view_kpi_form
|
1771
|
+
msgid ""
|
1772
|
+
"when <code>fld</code> is used : a field name specifier\n"
|
1773
|
+
" must be provided (e.g. <code>fldp.quantity</code>"
|
1774
|
+
msgstr ""
|
1775
|
+
|
1747
1776
|
#. module: mis_builder
|
1748
1777
|
#: model:ir.model.fields.selection,name:mis_builder.selection__mis_report_style__font_size__x-large
|
1749
1778
|
msgid "x-large"
|
@@ -12,7 +12,6 @@ from odoo.tools.float_utils import float_is_zero
|
|
12
12
|
from odoo.tools.safe_eval import datetime, dateutil, safe_eval, time
|
13
13
|
|
14
14
|
from .accounting_none import AccountingNone
|
15
|
-
from .simple_array import SimpleArray
|
16
15
|
|
17
16
|
_logger = logging.getLogger(__name__)
|
18
17
|
|
@@ -24,15 +23,65 @@ def _is_domain(s):
|
|
24
23
|
return _DOMAIN_START_RE.match(s)
|
25
24
|
|
26
25
|
|
26
|
+
class Accumulator:
|
27
|
+
"""A simple class to accumulate debit, credit and custom field values.
|
28
|
+
|
29
|
+
>>> acc1 = Accumulator(["f1", "f2"])
|
30
|
+
>>> acc1.debit
|
31
|
+
AccountingNone
|
32
|
+
>>> acc1.credit
|
33
|
+
AccountingNone
|
34
|
+
>>> acc1.custom_fields
|
35
|
+
{'f1': AccountingNone, 'f2': AccountingNone}
|
36
|
+
>>> acc1.add_debit_credit(10, 20)
|
37
|
+
>>> acc1.debit, acc1.credit
|
38
|
+
(10, 20)
|
39
|
+
>>> acc1.add_custom_field("f1", 10)
|
40
|
+
>>> acc1.custom_fields
|
41
|
+
{'f1': 10, 'f2': AccountingNone}
|
42
|
+
>>> acc2 = Accumulator(["f1", "f2"])
|
43
|
+
>>> acc2.add_debit_credit(21, 31)
|
44
|
+
>>> acc2.add_custom_field("f2", 41)
|
45
|
+
>>> acc1 += acc2
|
46
|
+
>>> acc1.debit, acc1.credit
|
47
|
+
(31, 51)
|
48
|
+
>>> acc1.custom_fields
|
49
|
+
{'f1': 10, 'f2': 41}
|
50
|
+
"""
|
51
|
+
|
52
|
+
def __init__(self, custom_field_names=()):
|
53
|
+
self.debit = AccountingNone
|
54
|
+
self.credit = AccountingNone
|
55
|
+
self.custom_fields = {
|
56
|
+
custom_field: AccountingNone for custom_field in custom_field_names
|
57
|
+
}
|
58
|
+
|
59
|
+
def add_debit_credit(self, debit, credit):
|
60
|
+
self.debit += debit
|
61
|
+
self.credit += credit
|
62
|
+
|
63
|
+
def add_custom_field(self, field, value):
|
64
|
+
self.custom_fields[field] += value
|
65
|
+
|
66
|
+
def __iadd__(self, other):
|
67
|
+
self.debit += other.debit
|
68
|
+
self.credit += other.credit
|
69
|
+
for field in self.custom_fields:
|
70
|
+
self.custom_fields[field] += other.custom_fields[field]
|
71
|
+
return self
|
72
|
+
|
73
|
+
|
27
74
|
class AccountingExpressionProcessor:
|
28
75
|
"""Processor for accounting expressions.
|
29
76
|
|
30
|
-
Expressions of the form
|
77
|
+
Expressions of the form
|
78
|
+
<field><mode>(.fieldname)?[accounts][optional move line domain]
|
31
79
|
are supported, where:
|
32
80
|
* field is bal, crd, deb, pbal (positive balances only),
|
33
|
-
nbal (negative balance only)
|
81
|
+
nbal (negative balance only), fld (custom field)
|
34
82
|
* mode is i (initial balance), e (ending balance),
|
35
83
|
p (moves over period)
|
84
|
+
* .fieldname is used only with fldp and specifies the field name to sum
|
36
85
|
* there is also a special u mode (unallocated P&L) which computes
|
37
86
|
the sum from the beginning until the beginning of the fiscal year
|
38
87
|
of the period; it is only meaningful for P&L accounts
|
@@ -46,6 +95,7 @@ class AccountingExpressionProcessor:
|
|
46
95
|
over the period (it is the same as balp[70]);
|
47
96
|
* bali[70,60]: balance of accounts 70 and 60 at the start of period;
|
48
97
|
* bale[1%]: balance of accounts starting with 1 at end of period.
|
98
|
+
* fldp.quantity[60%]: sum of the quantity field of moves on accounts 60
|
49
99
|
|
50
100
|
How to use:
|
51
101
|
* repeatedly invoke parse_expr() for each expression containing
|
@@ -77,8 +127,9 @@ class AccountingExpressionProcessor:
|
|
77
127
|
MODE_UNALLOCATED = "u"
|
78
128
|
|
79
129
|
_ACC_RE = re.compile(
|
80
|
-
r"(?P<field>\bbal|\bpbal|\bnbal|\bcrd|\bdeb)"
|
130
|
+
r"(?P<field>\bbal|\bpbal|\bnbal|\bcrd|\bdeb|\bfld)"
|
81
131
|
r"(?P<mode>[piseu])?"
|
132
|
+
r"(?P<fld_name>\.[a-zA-Z0-9_]+)?"
|
82
133
|
r"\s*"
|
83
134
|
r"(?P<account_sel>_[a-zA-Z0-9]+|\[.*?\])"
|
84
135
|
r"\s*"
|
@@ -110,6 +161,8 @@ class AccountingExpressionProcessor:
|
|
110
161
|
# a first query to get the initial balance and another
|
111
162
|
# to get the variation, so it's a bit slower
|
112
163
|
self.smart_end = True
|
164
|
+
# custom field to query and sum
|
165
|
+
self._custom_fields = set()
|
113
166
|
# Account model
|
114
167
|
self._account_model = self.env[account_model].with_context(active_test=False)
|
115
168
|
|
@@ -129,7 +182,7 @@ class AccountingExpressionProcessor:
|
|
129
182
|
def _parse_match_object(self, mo):
|
130
183
|
"""Split a match object corresponding to an accounting variable
|
131
184
|
|
132
|
-
Returns field, mode, account domain, move line domain.
|
185
|
+
Returns field, mode, fld_name, account domain, move line domain.
|
133
186
|
"""
|
134
187
|
domain_eval_context = {
|
135
188
|
"ref": self.env.ref,
|
@@ -138,12 +191,16 @@ class AccountingExpressionProcessor:
|
|
138
191
|
"datetime": datetime,
|
139
192
|
"dateutil": dateutil,
|
140
193
|
}
|
141
|
-
field, mode, account_sel, ml_domain = mo.groups()
|
194
|
+
field, mode, fld_name, account_sel, ml_domain = mo.groups()
|
142
195
|
# handle some legacy modes
|
143
196
|
if not mode:
|
144
197
|
mode = self.MODE_VARIATION
|
145
198
|
elif mode == "s":
|
146
199
|
mode = self.MODE_END
|
200
|
+
# custom fields
|
201
|
+
if fld_name:
|
202
|
+
assert fld_name[0] == "."
|
203
|
+
fld_name = fld_name[1:] # strip leading dot
|
147
204
|
# convert account selector to account domain
|
148
205
|
if account_sel.startswith("_"):
|
149
206
|
# legacy bal_NNN%
|
@@ -166,7 +223,7 @@ class AccountingExpressionProcessor:
|
|
166
223
|
ml_domain = tuple(safe_eval(ml_domain, domain_eval_context))
|
167
224
|
else:
|
168
225
|
ml_domain = tuple()
|
169
|
-
return field, mode, acc_domain, ml_domain
|
226
|
+
return field, mode, fld_name, acc_domain, ml_domain
|
170
227
|
|
171
228
|
def parse_expr(self, expr):
|
172
229
|
"""Parse an expression, extracting accounting variables.
|
@@ -177,7 +234,7 @@ class AccountingExpressionProcessor:
|
|
177
234
|
and mode.
|
178
235
|
"""
|
179
236
|
for mo in self._ACC_RE.finditer(expr):
|
180
|
-
|
237
|
+
field, mode, fld_name, acc_domain, ml_domain = self._parse_match_object(mo)
|
181
238
|
if mode == self.MODE_END and self.smart_end:
|
182
239
|
modes = (self.MODE_INITIAL, self.MODE_VARIATION, self.MODE_END)
|
183
240
|
else:
|
@@ -185,6 +242,30 @@ class AccountingExpressionProcessor:
|
|
185
242
|
for mode in modes:
|
186
243
|
key = (ml_domain, mode)
|
187
244
|
self._map_account_ids[key].add(acc_domain)
|
245
|
+
if field == "fld":
|
246
|
+
if mode != self.MODE_VARIATION:
|
247
|
+
raise UserError(
|
248
|
+
_(
|
249
|
+
"`fld` can only be used with mode `p` (variation) "
|
250
|
+
"in expression %s",
|
251
|
+
expr,
|
252
|
+
)
|
253
|
+
)
|
254
|
+
if not fld_name:
|
255
|
+
raise UserError(
|
256
|
+
_("`fld` must have a field name in exression %s", expr)
|
257
|
+
)
|
258
|
+
self._custom_fields.add(fld_name)
|
259
|
+
else:
|
260
|
+
if fld_name:
|
261
|
+
raise UserError(
|
262
|
+
_(
|
263
|
+
"`%(field)s` cannot have a field name "
|
264
|
+
"in expression %(expr)s",
|
265
|
+
field=field,
|
266
|
+
expr=expr,
|
267
|
+
)
|
268
|
+
)
|
188
269
|
|
189
270
|
def done_parsing(self):
|
190
271
|
"""Replace account domains by account ids in map"""
|
@@ -211,7 +292,7 @@ class AccountingExpressionProcessor:
|
|
211
292
|
"""
|
212
293
|
account_ids = set()
|
213
294
|
for mo in self._ACC_RE.finditer(expr):
|
214
|
-
|
295
|
+
_, _, _, acc_domain, _ = self._parse_match_object(mo)
|
215
296
|
account_ids.update(self._account_ids_by_acc_domain[acc_domain])
|
216
297
|
return account_ids
|
217
298
|
|
@@ -225,7 +306,7 @@ class AccountingExpressionProcessor:
|
|
225
306
|
aml_domains = []
|
226
307
|
date_domain_by_mode = {}
|
227
308
|
for mo in self._ACC_RE.finditer(expr):
|
228
|
-
field, mode, acc_domain, ml_domain = self._parse_match_object(mo)
|
309
|
+
field, mode, fld_name, acc_domain, ml_domain = self._parse_match_object(mo)
|
229
310
|
aml_domain = list(ml_domain)
|
230
311
|
account_ids = set()
|
231
312
|
account_ids.update(self._account_ids_by_acc_domain[acc_domain])
|
@@ -241,6 +322,8 @@ class AccountingExpressionProcessor:
|
|
241
322
|
aml_domain.append(("credit", "<>", 0.0))
|
242
323
|
elif field == "deb":
|
243
324
|
aml_domain.append(("debit", "<>", 0.0))
|
325
|
+
elif fld_name:
|
326
|
+
aml_domain.append((fld_name, "!=", False))
|
244
327
|
aml_domains.append(expression.normalize_domain(aml_domain))
|
245
328
|
if mode not in date_domain_by_mode:
|
246
329
|
date_domain_by_mode[mode] = self.get_aml_domain_for_dates(
|
@@ -316,10 +399,10 @@ class AccountingExpressionProcessor:
|
|
316
399
|
aml_model = self.env[aml_model]
|
317
400
|
aml_model = aml_model.with_context(active_test=False)
|
318
401
|
company_rates = self._get_company_rates(date_to)
|
319
|
-
# {(domain, mode): {account_id:
|
402
|
+
# {(domain, mode): {account_id: Accumulator}}
|
320
403
|
self._data = defaultdict(
|
321
404
|
lambda: defaultdict(
|
322
|
-
lambda:
|
405
|
+
lambda: Accumulator(self._custom_fields),
|
323
406
|
)
|
324
407
|
)
|
325
408
|
domain_by_mode = {}
|
@@ -343,7 +426,13 @@ class AccountingExpressionProcessor:
|
|
343
426
|
try:
|
344
427
|
accs = aml_model.read_group(
|
345
428
|
domain,
|
346
|
-
[
|
429
|
+
[
|
430
|
+
"debit",
|
431
|
+
"credit",
|
432
|
+
"account_id",
|
433
|
+
"company_id",
|
434
|
+
*self._custom_fields,
|
435
|
+
],
|
347
436
|
["account_id", "company_id"],
|
348
437
|
lazy=False,
|
349
438
|
)
|
@@ -369,9 +458,15 @@ class AccountingExpressionProcessor:
|
|
369
458
|
):
|
370
459
|
# in initial mode, ignore accounts with 0 balance
|
371
460
|
continue
|
372
|
-
# due to branches, it's possible to have multiple
|
373
|
-
# with the same account_id
|
374
|
-
|
461
|
+
# due to branches, it's possible to have multiple groups
|
462
|
+
# with the same account_id, because multiple companies can
|
463
|
+
# use the same account
|
464
|
+
account_data = self._data[key][acc["account_id"][0]]
|
465
|
+
account_data.add_debit_credit(debit * rate, credit * rate)
|
466
|
+
for field_name in self._custom_fields:
|
467
|
+
account_data.add_custom_field(
|
468
|
+
field_name, acc[field_name] or AccountingNone
|
469
|
+
)
|
375
470
|
# compute ending balances by summing initial and variation
|
376
471
|
for key in ends:
|
377
472
|
domain, mode = key
|
@@ -379,11 +474,8 @@ class AccountingExpressionProcessor:
|
|
379
474
|
variation_data = self._data[(domain, self.MODE_VARIATION)]
|
380
475
|
account_ids = set(initial_data.keys()) | set(variation_data.keys())
|
381
476
|
for account_id in account_ids:
|
382
|
-
|
383
|
-
|
384
|
-
account_id, (AccountingNone, AccountingNone)
|
385
|
-
)
|
386
|
-
self._data[key][account_id] = (di + dv, ci + cv)
|
477
|
+
self._data[key][account_id] += initial_data[account_id]
|
478
|
+
self._data[key][account_id] += variation_data[account_id]
|
387
479
|
|
388
480
|
def replace_expr(self, expr):
|
389
481
|
"""Replace accounting variables in an expression by their amount.
|
@@ -394,25 +486,30 @@ class AccountingExpressionProcessor:
|
|
394
486
|
"""
|
395
487
|
|
396
488
|
def f(mo):
|
397
|
-
field, mode, acc_domain, ml_domain = self._parse_match_object(mo)
|
489
|
+
field, mode, fld_name, acc_domain, ml_domain = self._parse_match_object(mo)
|
398
490
|
key = (ml_domain, mode)
|
399
491
|
account_ids_data = self._data[key]
|
400
492
|
v = AccountingNone
|
401
493
|
account_ids = self._account_ids_by_acc_domain[acc_domain]
|
402
494
|
for account_id in account_ids:
|
403
|
-
|
404
|
-
|
405
|
-
|
495
|
+
entry = account_ids_data[account_id]
|
496
|
+
debit = entry.debit
|
497
|
+
credit = entry.credit
|
406
498
|
if field == "bal":
|
407
499
|
v += debit - credit
|
408
|
-
elif field == "pbal"
|
409
|
-
|
410
|
-
|
411
|
-
|
500
|
+
elif field == "pbal":
|
501
|
+
if debit >= credit:
|
502
|
+
v += debit - credit
|
503
|
+
elif field == "nbal":
|
504
|
+
if debit < credit:
|
505
|
+
v += debit - credit
|
412
506
|
elif field == "deb":
|
413
507
|
v += debit
|
414
508
|
elif field == "crd":
|
415
509
|
v += credit
|
510
|
+
else:
|
511
|
+
assert field == "fld"
|
512
|
+
v += entry.custom_fields[fld_name]
|
416
513
|
# in initial balance mode, assume 0 is None
|
417
514
|
# as it does not make sense to distinguish 0 from "no data"
|
418
515
|
if (
|
@@ -435,7 +532,7 @@ class AccountingExpressionProcessor:
|
|
435
532
|
"""
|
436
533
|
|
437
534
|
def f(mo):
|
438
|
-
field, mode, acc_domain, ml_domain = self._parse_match_object(mo)
|
535
|
+
field, mode, fld_name, acc_domain, ml_domain = self._parse_match_object(mo)
|
439
536
|
key = (ml_domain, mode)
|
440
537
|
# first check if account_id is involved in
|
441
538
|
# the current expression part
|
@@ -443,9 +540,9 @@ class AccountingExpressionProcessor:
|
|
443
540
|
return "(AccountingNone)"
|
444
541
|
# here we know account_id is involved in acc_domain
|
445
542
|
account_ids_data = self._data[key]
|
446
|
-
|
447
|
-
|
448
|
-
|
543
|
+
entry = account_ids_data[account_id]
|
544
|
+
debit = entry.debit
|
545
|
+
credit = entry.credit
|
449
546
|
if field == "bal":
|
450
547
|
v = debit - credit
|
451
548
|
elif field == "pbal":
|
@@ -462,6 +559,9 @@ class AccountingExpressionProcessor:
|
|
462
559
|
v = debit
|
463
560
|
elif field == "crd":
|
464
561
|
v = credit
|
562
|
+
else:
|
563
|
+
assert field == "fld"
|
564
|
+
v = entry.custom_fields[fld_name]
|
465
565
|
# in initial balance mode, assume 0 is None
|
466
566
|
# as it does not make sense to distinguish 0 from "no data"
|
467
567
|
if (
|
@@ -475,7 +575,7 @@ class AccountingExpressionProcessor:
|
|
475
575
|
account_ids = set()
|
476
576
|
for expr in exprs:
|
477
577
|
for mo in self._ACC_RE.finditer(expr):
|
478
|
-
|
578
|
+
_, mode, _, acc_domain, ml_domain = self._parse_match_object(mo)
|
479
579
|
key = (ml_domain, mode)
|
480
580
|
account_ids_data = self._data[key]
|
481
581
|
for account_id in self._account_ids_by_acc_domain[acc_domain]:
|
@@ -495,7 +595,7 @@ class AccountingExpressionProcessor:
|
|
495
595
|
aep.parse_expr(expr)
|
496
596
|
aep.done_parsing()
|
497
597
|
aep.do_queries(date_from, date_to)
|
498
|
-
return aep._data[((), mode)]
|
598
|
+
return {k: (v.debit, v.credit) for k, v in aep._data[((), mode)].items()}
|
499
599
|
|
500
600
|
@classmethod
|
501
601
|
def get_balances_initial(cls, companies, date):
|
@@ -64,11 +64,11 @@ def _min(*args):
|
|
64
64
|
>>> min()
|
65
65
|
Traceback (most recent call last):
|
66
66
|
File "<stdin>", line 1, in ?
|
67
|
-
TypeError: min expected 1
|
67
|
+
TypeError: min expected at least 1 argument, got 0
|
68
68
|
>>> _min()
|
69
69
|
Traceback (most recent call last):
|
70
70
|
File "<stdin>", line 1, in ?
|
71
|
-
TypeError: min expected 1
|
71
|
+
TypeError: min expected at least 1 argument, got 0
|
72
72
|
>>> min([])
|
73
73
|
Traceback (most recent call last):
|
74
74
|
File "<stdin>", line 1, in ?
|
@@ -107,11 +107,11 @@ def _max(*args):
|
|
107
107
|
>>> max()
|
108
108
|
Traceback (most recent call last):
|
109
109
|
File "<stdin>", line 1, in ?
|
110
|
-
TypeError: max expected 1
|
110
|
+
TypeError: max expected at least 1 argument, got 0
|
111
111
|
>>> _max()
|
112
112
|
Traceback (most recent call last):
|
113
113
|
File "<stdin>", line 1, in ?
|
114
|
-
TypeError: max expected 1
|
114
|
+
TypeError: max expected at least 1 argument, got 0
|
115
115
|
>>> max([])
|
116
116
|
Traceback (most recent call last):
|
117
117
|
File "<stdin>", line 1, in ?
|
@@ -367,7 +367,7 @@ ul.auto-toc {
|
|
367
367
|
!! This file is generated by oca-gen-addon-readme !!
|
368
368
|
!! changes will be overwritten. !!
|
369
369
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
370
|
-
!! source digest: sha256:
|
370
|
+
!! source digest: sha256:2c8b76b489e5323dae1133488deb91d0bcbf86be319044b51399b56b566e3c8b
|
371
371
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
|
372
372
|
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Production/Stable" src="https://img.shields.io/badge/maturity-Production%2FStable-green.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/mis-builder/tree/16.0/mis_builder"><img alt="OCA/mis-builder" src="https://img.shields.io/badge/github-OCA%2Fmis--builder-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/mis-builder-16-0/mis-builder-16-0-mis_builder"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/mis-builder&target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
|
373
373
|
<p>This module allows you to build Management Information Systems dashboards.
|
@@ -9,9 +9,13 @@ from odoo import fields
|
|
9
9
|
from odoo.exceptions import UserError
|
10
10
|
from odoo.tools.safe_eval import safe_eval
|
11
11
|
|
12
|
+
from ..models import aep
|
12
13
|
from ..models.accounting_none import AccountingNone
|
13
14
|
from ..models.aep import AccountingExpressionProcessor as AEP
|
14
15
|
from ..models.aep import _is_domain
|
16
|
+
from .common import load_doctests
|
17
|
+
|
18
|
+
load_tests = load_doctests(aep)
|
15
19
|
|
16
20
|
|
17
21
|
class TestAEP(common.TransactionCase):
|
@@ -66,6 +70,7 @@ class TestAEP(common.TransactionCase):
|
|
66
70
|
amount=300,
|
67
71
|
debit_acc=self.account_ar,
|
68
72
|
credit_acc=self.account_in,
|
73
|
+
credit_quantity=3,
|
69
74
|
)
|
70
75
|
# create move in March this year
|
71
76
|
self._create_move(
|
@@ -91,6 +96,7 @@ class TestAEP(common.TransactionCase):
|
|
91
96
|
self.aep.parse_expr("crdp[700I%]")
|
92
97
|
self.aep.parse_expr("bali[400%]")
|
93
98
|
self.aep.parse_expr("bale[700%]")
|
99
|
+
self.aep.parse_expr("fldp.quantity[700%]")
|
94
100
|
self.aep.parse_expr("balp[]" "[('account_id.code', '=', '400AR')]")
|
95
101
|
self.aep.parse_expr(
|
96
102
|
"balp[]" "[('account_id.account_type', '=', " " 'asset_receivable')]"
|
@@ -105,17 +111,32 @@ class TestAEP(common.TransactionCase):
|
|
105
111
|
self.aep.parse_expr("bal_700IN") # deprecated
|
106
112
|
self.aep.parse_expr("bals[700IN]") # deprecated
|
107
113
|
|
108
|
-
def _create_move(
|
114
|
+
def _create_move(
|
115
|
+
self, date, amount, debit_acc, credit_acc, post=True, credit_quantity=0
|
116
|
+
):
|
109
117
|
move = self.move_model.create(
|
110
118
|
{
|
111
119
|
"journal_id": self.journal.id,
|
112
120
|
"date": fields.Date.to_string(date),
|
113
121
|
"line_ids": [
|
114
|
-
(0, 0, {"name": "/", "debit": amount, "account_id": debit_acc.id}),
|
115
122
|
(
|
116
123
|
0,
|
117
124
|
0,
|
118
|
-
{
|
125
|
+
{
|
126
|
+
"name": "/",
|
127
|
+
"debit": amount,
|
128
|
+
"account_id": debit_acc.id,
|
129
|
+
},
|
130
|
+
),
|
131
|
+
(
|
132
|
+
0,
|
133
|
+
0,
|
134
|
+
{
|
135
|
+
"name": "/",
|
136
|
+
"credit": amount,
|
137
|
+
"account_id": credit_acc.id,
|
138
|
+
"quantity": credit_quantity,
|
139
|
+
},
|
119
140
|
),
|
120
141
|
],
|
121
142
|
}
|
@@ -145,6 +166,20 @@ class TestAEP(common.TransactionCase):
|
|
145
166
|
self.assertEqual(self.company.fiscalyear_last_day, 31)
|
146
167
|
self.assertEqual(self.company.fiscalyear_last_month, "12")
|
147
168
|
|
169
|
+
def test_parse_expr_error_handling(self):
|
170
|
+
aep = AEP(self.company)
|
171
|
+
with self.assertRaises(UserError) as cm:
|
172
|
+
aep.parse_expr("fldi.quantity[700%]")
|
173
|
+
self.assertIn(
|
174
|
+
"`fld` can only be used with mode `p` (variation)", str(cm.exception)
|
175
|
+
)
|
176
|
+
with self.assertRaises(UserError) as cm:
|
177
|
+
aep.parse_expr("fldp[700%]")
|
178
|
+
self.assertIn("`fld` must have a field name", str(cm.exception))
|
179
|
+
with self.assertRaises(UserError) as cm:
|
180
|
+
aep.parse_expr("balp.quantity[700%]")
|
181
|
+
self.assertIn("`bal` cannot have a field name", str(cm.exception))
|
182
|
+
|
148
183
|
def test_aep_basic(self):
|
149
184
|
self.aep.done_parsing()
|
150
185
|
# let's query for december
|
@@ -196,6 +231,8 @@ class TestAEP(common.TransactionCase):
|
|
196
231
|
self.assertEqual(self._eval("bale[700IN]"), -300)
|
197
232
|
# check result for non existing account
|
198
233
|
self.assertIs(self._eval("bale[700NA]"), AccountingNone)
|
234
|
+
# check fldp.quantity
|
235
|
+
self.assertEqual(self._eval("fldp.quantity[700%]"), 3)
|
199
236
|
|
200
237
|
# let's query for March
|
201
238
|
self._do_queries(
|
@@ -227,6 +264,8 @@ class TestAEP(common.TransactionCase):
|
|
227
264
|
self.assertEqual(self._eval("debp[400A%]"), 500)
|
228
265
|
self.assertEqual(self._eval("bal_700IN"), -500)
|
229
266
|
self.assertEqual(self._eval("bals[700IN]"), -800)
|
267
|
+
# check fldp.quantity
|
268
|
+
self.assertEqual(self._eval("fldp.quantity[700%]"), 0)
|
230
269
|
|
231
270
|
# unallocated p&l from previous year
|
232
271
|
self.assertEqual(self._eval("balu[]"), -100)
|
@@ -170,19 +170,24 @@
|
|
170
170
|
<p
|
171
171
|
> The following special elements are recognized in the expressions
|
172
172
|
to compute accounting data: <code
|
173
|
-
>{bal|crd|deb|pbal|nbal}{pieu}[account
|
173
|
+
>{bal|crd|deb|pbal|nbal|fld}{pieu}(.fieldname)[account
|
174
174
|
selector][journal items domain]</code>. </p>
|
175
175
|
<ul>
|
176
176
|
<li>
|
177
177
|
<code>bal</code>, <code>crd</code>, <code
|
178
178
|
>deb</code>, <code>
|
179
|
-
pbal</code>, <code
|
180
|
-
>
|
181
|
-
positive balance, negative balance
|
179
|
+
pbal</code>, <code>nbal</code>, <code
|
180
|
+
>fld</code> : balance, debit, credit,
|
181
|
+
positive balance, negative balance,
|
182
|
+
other numerical field. </li>
|
182
183
|
<li>
|
183
184
|
<code>p</code>, <code>i</code>, <code
|
184
185
|
>e</code> : respectively variation over the period,
|
185
186
|
initial balance, ending balance </li>
|
187
|
+
<li>when <code
|
188
|
+
>fld</code> is used : a field name specifier
|
189
|
+
must be provided (e.g. <code
|
190
|
+
>fldp.quantity</code></li>
|
186
191
|
<li> The <b
|
187
192
|
>account selector</b> is a like expression on the
|
188
193
|
account code (eg <code
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: odoo-addon-mis_builder
|
3
|
-
Version: 16.0.5.
|
3
|
+
Version: 16.0.5.2.0
|
4
4
|
Requires-Python: >=3.10
|
5
5
|
Requires-Dist: odoo-addon-date_range>=16.0dev,<16.1dev
|
6
6
|
Requires-Dist: odoo-addon-report_xlsx>=16.0dev,<16.1dev
|
@@ -25,7 +25,7 @@ MIS Builder
|
|
25
25
|
!! This file is generated by oca-gen-addon-readme !!
|
26
26
|
!! changes will be overwritten. !!
|
27
27
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
28
|
-
!! source digest: sha256:
|
28
|
+
!! source digest: sha256:2c8b76b489e5323dae1133488deb91d0bcbf86be319044b51399b56b566e3c8b
|
29
29
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
30
30
|
|
31
31
|
.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png
|
{odoo_addon_mis_builder-16.0.5.1.13.dist-info → odoo_addon_mis_builder-16.0.5.2.0.dist-info}/RECORD
RENAMED
@@ -1,6 +1,6 @@
|
|
1
|
-
odoo/addons/mis_builder/README.rst,sha256=
|
1
|
+
odoo/addons/mis_builder/README.rst,sha256=cex0nZCQC3_G89UAQr0diGkhEKrnekmbHMHyahqu0Ck,29747
|
2
2
|
odoo/addons/mis_builder/__init__.py,sha256=-8wG-57WxKfMMA5_TFzpubBcfthcsAP5vjTkhXKf7ds,185
|
3
|
-
odoo/addons/mis_builder/__manifest__.py,sha256=
|
3
|
+
odoo/addons/mis_builder/__manifest__.py,sha256=nK2bf1OJhoQD7PKtyEeGQBYg_gTaKFIDIc8POvwf--I,1556
|
4
4
|
odoo/addons/mis_builder/datas/ir_cron.xml,sha256=H5nnz-4CqZPVveJdRoJ_DrBOOsihK4XwOjWcSMYv68A,558
|
5
5
|
odoo/addons/mis_builder/i18n/ca.po,sha256=_hw-pIoOudeREKA5F6I3ZzNFoi5ScbAjt-n8w-K0-TI,89958
|
6
6
|
odoo/addons/mis_builder/i18n/de.po,sha256=N_3JJJ-4ofgF3CyPVcRmBu--FS9OlNcauLwrzB1R7Ok,65267
|
@@ -10,7 +10,7 @@ odoo/addons/mis_builder/i18n/es.po,sha256=Xa_UQEBXkHapuBf8iQeKTD4JyAAGgKzeov7N5O
|
|
10
10
|
odoo/addons/mis_builder/i18n/fr.po,sha256=Na0W0U_mP8bi8tedcvzn8LPglIacwH7GR1K82xrEyUk,82680
|
11
11
|
odoo/addons/mis_builder/i18n/hr.po,sha256=t7Z4FbdbdkDXRFKqhVNz2dNnabwv1725hJoG8VMrAvE,67345
|
12
12
|
odoo/addons/mis_builder/i18n/it.po,sha256=5DDJtgum-XgFaLX6IFm1omM1JdKUE2PEzZUkUqZVKMo,76184
|
13
|
-
odoo/addons/mis_builder/i18n/mis_builder.pot,sha256
|
13
|
+
odoo/addons/mis_builder/i18n/mis_builder.pot,sha256=aO4Ce34mTfRmLpCjpYfUWRa7uqXWBNBtLThScqz-R0M,63001
|
14
14
|
odoo/addons/mis_builder/i18n/nl.po,sha256=2xbRSRD-PZAQZC54RwlKQUIBFsGdGdfsfbb_EWqg2Nw,74688
|
15
15
|
odoo/addons/mis_builder/i18n/nl_NL.po,sha256=d3RKauD0_rcouw1Na_BOAdceBniIXRG414iGfkgTbGo,74780
|
16
16
|
odoo/addons/mis_builder/i18n/pt.po,sha256=2wzm81ssyHtsfrZCNcM8PibuY8J8VgfoizvNM74NbX4,64056
|
@@ -21,8 +21,8 @@ odoo/addons/mis_builder/migrations/16.0.1.0.1/post-migration.py,sha256=kSvTOKc5R
|
|
21
21
|
odoo/addons/mis_builder/migrations/16.0.5.0.0/end-migrate.py,sha256=YquWWej_lW3mhkYTSQPqEC_BucmxtLRgwGWU1My7fo8,326
|
22
22
|
odoo/addons/mis_builder/models/__init__.py,sha256=_h9tbt-H0JjtHL6kI7-wiEnd5u2qkP855DevyjxLBbI,331
|
23
23
|
odoo/addons/mis_builder/models/accounting_none.py,sha256=6EE6x4slUbyP2TVnHpilsWu8XqWhGZMt79uBOmNLcVs,4156
|
24
|
-
odoo/addons/mis_builder/models/aep.py,sha256=
|
25
|
-
odoo/addons/mis_builder/models/aggregate.py,sha256=
|
24
|
+
odoo/addons/mis_builder/models/aep.py,sha256=hPO7mVTfYLKSvexEBWRDE8hWvod6KwzyobSg2k4r69U,26306
|
25
|
+
odoo/addons/mis_builder/models/aggregate.py,sha256=ffOZq2-R7st-1Hsw-lFfiwkMa46ToJZ7m0K6Z9P_gwQ,2892
|
26
26
|
odoo/addons/mis_builder/models/data_error.py,sha256=ltTIrFCbsxYXmdPTVFVJj3ejZxyc83NEsIFQs2s3-L0,442
|
27
27
|
odoo/addons/mis_builder/models/expression_evaluator.py,sha256=honBPA9VOg0q_BOPuB8CkMA_wcoMUMxDbB-ULnjRpb4,2459
|
28
28
|
odoo/addons/mis_builder/models/kpimatrix.py,sha256=otTvqqvLOINVL1jbudn2PhvWwURDtmsoNN5TzM0hIwI,19812
|
@@ -53,7 +53,7 @@ odoo/addons/mis_builder/static/description/ex_report_preview.png,sha256=NKBRn70E
|
|
53
53
|
odoo/addons/mis_builder/static/description/ex_report_settings.png,sha256=T7j_3DUUtdZ6XKsAvJ3uKsK_-GtRl0sRPD68ZOa6ebA,103790
|
54
54
|
odoo/addons/mis_builder/static/description/ex_report_template.png,sha256=c8KgzyGD5QiZNCmxaVR4u3h1K0TJN50uXZWoNsieN8A,100278
|
55
55
|
odoo/addons/mis_builder/static/description/icon.png,sha256=0OCahdqDvaS_CHV97ZCbajW0_AcBLVVvja_EJA2mC6s,4770
|
56
|
-
odoo/addons/mis_builder/static/description/index.html,sha256=
|
56
|
+
odoo/addons/mis_builder/static/description/index.html,sha256=ctFlUJJKjeQ17RuWqBGKJDBwRzAL66wHR0vEWSpPzMc,55964
|
57
57
|
odoo/addons/mis_builder/static/src/components/mis_report_widget.css,sha256=ew7iozGAv9ue3KPf_8qA_AhHlNGis7pJu8A7tc6CO4Y,1198
|
58
58
|
odoo/addons/mis_builder/static/src/components/mis_report_widget.esm.js,sha256=UzKMeuffo1BT5VmOi0Um9xtplbvdjcrc-_KOkZrTc_A,5528
|
59
59
|
odoo/addons/mis_builder/static/src/components/mis_report_widget.xml,sha256=eKbIbOWmsc_qdT_zYuFdqxZIYpf6JEQGTcXE0vcLax4,5679
|
@@ -62,7 +62,7 @@ odoo/addons/mis_builder/tests/__init__.py,sha256=ilgojed90T4NL_YXgVf3CACd8fo7nxp
|
|
62
62
|
odoo/addons/mis_builder/tests/common.py,sha256=7LqnpPyJ8yOi5gA4CwiKZaep6NGY_vIDnK36u8WVC1o,1918
|
63
63
|
odoo/addons/mis_builder/tests/fake_models.py,sha256=DWJsXiIVVXZwXfLQ-9TmUs3eM5KBCXibS2qRXgkFt1c,177
|
64
64
|
odoo/addons/mis_builder/tests/test_accounting_none.py,sha256=satnhbRuI4DdWIOpe-mG7sW9KlamgnyvoVaPQlqZ9FA,239
|
65
|
-
odoo/addons/mis_builder/tests/test_aep.py,sha256=
|
65
|
+
odoo/addons/mis_builder/tests/test_aep.py,sha256=o931AKWU-VlPGNMocslyMCng8k7JZ5pkPoJdyykkGQM,17701
|
66
66
|
odoo/addons/mis_builder/tests/test_aggregate.py,sha256=ZXEVW0hMZYUIJM6ikQpmBH9Xc9TaY46xYbBsH8vcXDQ,226
|
67
67
|
odoo/addons/mis_builder/tests/test_data_sources.py,sha256=ao0FFFxvgvDfGZJzQ_ErTIRc8HM9JD_-8nr8OGeSu_g,7461
|
68
68
|
odoo/addons/mis_builder/tests/test_kpi_data.py,sha256=PrgfJIkrw5liJ8InLoXCGXMD-uc7y23J-7R-UYxWIO8,4860
|
@@ -75,13 +75,13 @@ odoo/addons/mis_builder/tests/test_simple_array.py,sha256=PHzSr_vb4Wn7dfwjaFSAlu
|
|
75
75
|
odoo/addons/mis_builder/tests/test_subreport.py,sha256=I7tQdhjaei15lzhXQLrbL80mlFtAyeoV-gHcs6xaEUo,3202
|
76
76
|
odoo/addons/mis_builder/tests/test_target_move.py,sha256=Jw7F9QMHVekOr-hoAy6kKCNcYAZAmfjOUbqcgjPw9Ps,1296
|
77
77
|
odoo/addons/mis_builder/tests/test_utc_midnight.py,sha256=mET_qOOC3qsOJ4MK_yTRocicW_NvKdRhjtpVJjLYaaU,917
|
78
|
-
odoo/addons/mis_builder/views/mis_report.xml,sha256=
|
78
|
+
odoo/addons/mis_builder/views/mis_report.xml,sha256=9iacm2GDBaINfypwgb3mxQYkz-412g3eku3E3bhrWbY,15906
|
79
79
|
odoo/addons/mis_builder/views/mis_report_instance.xml,sha256=dhOu5S7NwT4Yf5xSTOF0gcliwJN77fQTPJhqd4zpSaU,19213
|
80
80
|
odoo/addons/mis_builder/views/mis_report_style.xml,sha256=HRsXhEZZYbuZg7qJyfhdnERiTplX7uaN3e0dMunGoD0,4896
|
81
81
|
odoo/addons/mis_builder/wizard/__init__.py,sha256=qlc_LcwA6U3Wgv-qC9uacPjZXV3jShrV_RBiuKCBNPA,158
|
82
82
|
odoo/addons/mis_builder/wizard/mis_builder_dashboard.py,sha256=DIas3iie9fb2WzJ2eGcrVc7y64IuRyxSG7tJrcV-53g,3404
|
83
83
|
odoo/addons/mis_builder/wizard/mis_builder_dashboard.xml,sha256=ArG86hdyRWm531nK0hYU-j1N87yXtpDVpSuKTmOB47I,1440
|
84
|
-
odoo_addon_mis_builder-16.0.5.
|
85
|
-
odoo_addon_mis_builder-16.0.5.
|
86
|
-
odoo_addon_mis_builder-16.0.5.
|
87
|
-
odoo_addon_mis_builder-16.0.5.
|
84
|
+
odoo_addon_mis_builder-16.0.5.2.0.dist-info/METADATA,sha256=HyBplR9zkov3Lk6w8HoikWWzSjvzNKDbjSMs2s8Kk_I,30461
|
85
|
+
odoo_addon_mis_builder-16.0.5.2.0.dist-info/WHEEL,sha256=9fEMia4zL7ZuZbnCOrcYogUhmn4XFIVaJ8G4YGI31xc,81
|
86
|
+
odoo_addon_mis_builder-16.0.5.2.0.dist-info/top_level.txt,sha256=QE6RBQ0QX5f4eFuUcGgU5Kbq1A_qJcDs-e_vpr6pmfU,4
|
87
|
+
odoo_addon_mis_builder-16.0.5.2.0.dist-info/RECORD,,
|
{odoo_addon_mis_builder-16.0.5.1.13.dist-info → odoo_addon_mis_builder-16.0.5.2.0.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|