odoo-addon-mis-builder 17.0.1.3.0__py3-none-any.whl → 18.0.1.1.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 +7 -6
- odoo/addons/mis_builder/__manifest__.py +1 -1
- odoo/addons/mis_builder/datas/ir_cron.xml +1 -3
- odoo/addons/mis_builder/i18n/ca.po +8 -51
- odoo/addons/mis_builder/i18n/de.po +4 -39
- odoo/addons/mis_builder/i18n/el.po +4 -39
- odoo/addons/mis_builder/i18n/el_GR.po +4 -39
- odoo/addons/mis_builder/i18n/es.po +12 -65
- odoo/addons/mis_builder/i18n/fr.po +12 -65
- odoo/addons/mis_builder/i18n/hr.po +4 -39
- odoo/addons/mis_builder/i18n/it.po +13 -80
- odoo/addons/mis_builder/i18n/mis_builder.pot +18 -125
- odoo/addons/mis_builder/i18n/nl.po +4 -39
- odoo/addons/mis_builder/i18n/nl_NL.po +4 -39
- odoo/addons/mis_builder/i18n/pt.po +4 -39
- odoo/addons/mis_builder/i18n/pt_BR.po +13 -64
- odoo/addons/mis_builder/i18n/sv.po +12 -64
- odoo/addons/mis_builder/i18n/tr.po +4 -39
- odoo/addons/mis_builder/i18n/zh_CN.po +78 -154
- odoo/addons/mis_builder/models/aep.py +62 -160
- odoo/addons/mis_builder/models/aggregate.py +4 -4
- odoo/addons/mis_builder/models/kpimatrix.py +9 -10
- odoo/addons/mis_builder/models/mis_kpi_data.py +5 -7
- odoo/addons/mis_builder/models/mis_report.py +47 -58
- odoo/addons/mis_builder/models/mis_report_instance.py +43 -24
- odoo/addons/mis_builder/models/mis_report_style.py +9 -12
- odoo/addons/mis_builder/models/mis_report_subreport.py +5 -4
- odoo/addons/mis_builder/models/prorata_read_group_mixin.py +51 -31
- odoo/addons/mis_builder/models/simple_array.py +2 -2
- odoo/addons/mis_builder/readme/CONTRIBUTORS.md +1 -0
- odoo/addons/mis_builder/report/mis_report_instance_xlsx.py +2 -2
- odoo/addons/mis_builder/static/description/index.html +5 -4
- odoo/addons/mis_builder/static/src/components/mis_report_widget.esm.js +38 -22
- odoo/addons/mis_builder/static/src/components/mis_report_widget.scss +9 -0
- odoo/addons/mis_builder/static/src/components/mis_report_widget.xml +16 -14
- odoo/addons/mis_builder/tests/__init__.py +1 -0
- odoo/addons/mis_builder/tests/common.py +2 -4
- odoo/addons/mis_builder/tests/fake_models.py +18 -1
- odoo/addons/mis_builder/tests/test_aep.py +7 -69
- odoo/addons/mis_builder/tests/test_data_sources.py +4 -11
- odoo/addons/mis_builder/tests/test_kpi_data.py +1 -5
- odoo/addons/mis_builder/tests/test_mis_report_instance.py +21 -17
- odoo/addons/mis_builder/tests/test_multi_company_aep.py +3 -3
- odoo/addons/mis_builder/tests/test_pro_rata_read_group.py +105 -0
- odoo/addons/mis_builder/views/mis_report.xml +38 -43
- odoo/addons/mis_builder/views/mis_report_instance.xml +38 -40
- odoo/addons/mis_builder/views/mis_report_style.xml +6 -6
- odoo/addons/mis_builder/wizard/mis_builder_dashboard.py +3 -3
- odoo/addons/mis_builder/wizard/mis_builder_dashboard.xml +6 -6
- {odoo_addon_mis_builder-17.0.1.3.0.dist-info → odoo_addon_mis_builder-18.0.1.1.0.dist-info}/METADATA +12 -11
- odoo_addon_mis_builder-18.0.1.1.0.dist-info/RECORD +87 -0
- odoo/addons/mis_builder/static/src/components/mis_report_widget.css +0 -67
- odoo_addon_mis_builder-17.0.1.3.0.dist-info/RECORD +0 -87
- {odoo_addon_mis_builder-17.0.1.3.0.dist-info → odoo_addon_mis_builder-18.0.1.1.0.dist-info}/WHEEL +0 -0
- {odoo_addon_mis_builder-17.0.1.3.0.dist-info → odoo_addon_mis_builder-18.0.1.1.0.dist-info}/top_level.txt +0 -0
@@ -5,13 +5,14 @@ import logging
|
|
5
5
|
import re
|
6
6
|
from collections import defaultdict
|
7
7
|
|
8
|
-
from odoo import
|
8
|
+
from odoo import fields
|
9
9
|
from odoo.exceptions import UserError
|
10
10
|
from odoo.models import expression
|
11
11
|
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
|
15
16
|
|
16
17
|
_logger = logging.getLogger(__name__)
|
17
18
|
|
@@ -23,72 +24,15 @@ def _is_domain(s):
|
|
23
24
|
return _DOMAIN_START_RE.match(s)
|
24
25
|
|
25
26
|
|
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 has_data(self):
|
60
|
-
return (
|
61
|
-
self.debit is not AccountingNone
|
62
|
-
or self.credit is not AccountingNone
|
63
|
-
or any(v is not AccountingNone for v in self.custom_fields.values())
|
64
|
-
)
|
65
|
-
|
66
|
-
def add_debit_credit(self, debit, credit):
|
67
|
-
self.debit += debit
|
68
|
-
self.credit += credit
|
69
|
-
|
70
|
-
def add_custom_field(self, field, value):
|
71
|
-
self.custom_fields[field] += value
|
72
|
-
|
73
|
-
def __iadd__(self, other):
|
74
|
-
self.debit += other.debit
|
75
|
-
self.credit += other.credit
|
76
|
-
for field in self.custom_fields:
|
77
|
-
self.custom_fields[field] += other.custom_fields[field]
|
78
|
-
return self
|
79
|
-
|
80
|
-
|
81
27
|
class AccountingExpressionProcessor:
|
82
28
|
"""Processor for accounting expressions.
|
83
29
|
|
84
|
-
Expressions of the form
|
85
|
-
<field><mode>(.fieldname)?[accounts][optional move line domain]
|
30
|
+
Expressions of the form <field><mode>[accounts][optional move line domain]
|
86
31
|
are supported, where:
|
87
32
|
* field is bal, crd, deb, pbal (positive balances only),
|
88
|
-
nbal (negative balance only)
|
33
|
+
nbal (negative balance only)
|
89
34
|
* mode is i (initial balance), e (ending balance),
|
90
35
|
p (moves over period)
|
91
|
-
* .fieldname is used only with fldp and specifies the field name to sum
|
92
36
|
* there is also a special u mode (unallocated P&L) which computes
|
93
37
|
the sum from the beginning until the beginning of the fiscal year
|
94
38
|
of the period; it is only meaningful for P&L accounts
|
@@ -102,7 +46,6 @@ class AccountingExpressionProcessor:
|
|
102
46
|
over the period (it is the same as balp[70]);
|
103
47
|
* bali[70,60]: balance of accounts 70 and 60 at the start of period;
|
104
48
|
* bale[1%]: balance of accounts starting with 1 at end of period.
|
105
|
-
* fldp.quantity[60%]: sum of the quantity field of moves on accounts 60
|
106
49
|
|
107
50
|
How to use:
|
108
51
|
* repeatedly invoke parse_expr() for each expression containing
|
@@ -134,9 +77,8 @@ class AccountingExpressionProcessor:
|
|
134
77
|
MODE_UNALLOCATED = "u"
|
135
78
|
|
136
79
|
_ACC_RE = re.compile(
|
137
|
-
r"(?P<field>\bbal|\bpbal|\bnbal|\bcrd|\bdeb
|
80
|
+
r"(?P<field>\bbal|\bpbal|\bnbal|\bcrd|\bdeb)"
|
138
81
|
r"(?P<mode>[piseu])?"
|
139
|
-
r"(?P<fld_name>\.[a-zA-Z0-9_]+)?"
|
140
82
|
r"\s*"
|
141
83
|
r"(?P<account_sel>_[a-zA-Z0-9]+|\[.*?\])"
|
142
84
|
r"\s*"
|
@@ -150,7 +92,7 @@ class AccountingExpressionProcessor:
|
|
150
92
|
self.currency = companies.mapped("currency_id")
|
151
93
|
if len(self.currency) > 1:
|
152
94
|
raise UserError(
|
153
|
-
_(
|
95
|
+
self.env._(
|
154
96
|
"If currency_id is not provided, "
|
155
97
|
"all companies must have the same currency."
|
156
98
|
)
|
@@ -168,8 +110,6 @@ class AccountingExpressionProcessor:
|
|
168
110
|
# a first query to get the initial balance and another
|
169
111
|
# to get the variation, so it's a bit slower
|
170
112
|
self.smart_end = True
|
171
|
-
# custom field to query and sum
|
172
|
-
self._custom_fields = set()
|
173
113
|
# Account model
|
174
114
|
self._account_model = self.env[account_model].with_context(active_test=False)
|
175
115
|
|
@@ -189,7 +129,7 @@ class AccountingExpressionProcessor:
|
|
189
129
|
def _parse_match_object(self, mo):
|
190
130
|
"""Split a match object corresponding to an accounting variable
|
191
131
|
|
192
|
-
Returns field, mode,
|
132
|
+
Returns field, mode, account domain, move line domain.
|
193
133
|
"""
|
194
134
|
domain_eval_context = {
|
195
135
|
"ref": self.env.ref,
|
@@ -198,16 +138,12 @@ class AccountingExpressionProcessor:
|
|
198
138
|
"datetime": datetime,
|
199
139
|
"dateutil": dateutil,
|
200
140
|
}
|
201
|
-
field, mode,
|
141
|
+
field, mode, account_sel, ml_domain = mo.groups()
|
202
142
|
# handle some legacy modes
|
203
143
|
if not mode:
|
204
144
|
mode = self.MODE_VARIATION
|
205
145
|
elif mode == "s":
|
206
146
|
mode = self.MODE_END
|
207
|
-
# custom fields
|
208
|
-
if fld_name:
|
209
|
-
assert fld_name[0] == "."
|
210
|
-
fld_name = fld_name[1:] # strip leading dot
|
211
147
|
# convert account selector to account domain
|
212
148
|
if account_sel.startswith("_"):
|
213
149
|
# legacy bal_NNN%
|
@@ -230,7 +166,7 @@ class AccountingExpressionProcessor:
|
|
230
166
|
ml_domain = tuple(safe_eval(ml_domain, domain_eval_context))
|
231
167
|
else:
|
232
168
|
ml_domain = tuple()
|
233
|
-
return field, mode,
|
169
|
+
return field, mode, acc_domain, ml_domain
|
234
170
|
|
235
171
|
def parse_expr(self, expr):
|
236
172
|
"""Parse an expression, extracting accounting variables.
|
@@ -241,7 +177,7 @@ class AccountingExpressionProcessor:
|
|
241
177
|
and mode.
|
242
178
|
"""
|
243
179
|
for mo in self._ACC_RE.finditer(expr):
|
244
|
-
|
180
|
+
_, mode, acc_domain, ml_domain = self._parse_match_object(mo)
|
245
181
|
if mode == self.MODE_END and self.smart_end:
|
246
182
|
modes = (self.MODE_INITIAL, self.MODE_VARIATION, self.MODE_END)
|
247
183
|
else:
|
@@ -249,40 +185,26 @@ class AccountingExpressionProcessor:
|
|
249
185
|
for mode in modes:
|
250
186
|
key = (ml_domain, mode)
|
251
187
|
self._map_account_ids[key].add(acc_domain)
|
252
|
-
if field == "fld":
|
253
|
-
if mode != self.MODE_VARIATION:
|
254
|
-
raise UserError(
|
255
|
-
_(
|
256
|
-
"`fld` can only be used with mode `p` (variation) "
|
257
|
-
"in expression %s",
|
258
|
-
expr,
|
259
|
-
)
|
260
|
-
)
|
261
|
-
if not fld_name:
|
262
|
-
raise UserError(
|
263
|
-
_("`fld` must have a field name in exression %s", expr)
|
264
|
-
)
|
265
|
-
self._custom_fields.add(fld_name)
|
266
|
-
else:
|
267
|
-
if fld_name:
|
268
|
-
raise UserError(
|
269
|
-
_(
|
270
|
-
"`%(field)s` cannot have a field name "
|
271
|
-
"in expression %(expr)s",
|
272
|
-
field=field,
|
273
|
-
expr=expr,
|
274
|
-
)
|
275
|
-
)
|
276
188
|
|
277
189
|
def done_parsing(self):
|
278
190
|
"""Replace account domains by account ids in map"""
|
279
191
|
for key, acc_domains in self._map_account_ids.items():
|
280
192
|
all_account_ids = set()
|
281
193
|
for acc_domain in acc_domains:
|
282
|
-
|
283
|
-
|
284
|
-
)
|
285
|
-
|
194
|
+
# XXX It is apparently not possible to search accounts by code
|
195
|
+
# across multiple companies at once (due to how _search_code is
|
196
|
+
# implemented for instance), so we have to search each company
|
197
|
+
# separately.
|
198
|
+
account_ids = []
|
199
|
+
for company in self.companies:
|
200
|
+
acc_domain_with_company = expression.AND(
|
201
|
+
[acc_domain, [("company_ids", "=", company.id)]]
|
202
|
+
)
|
203
|
+
account_ids += (
|
204
|
+
self._account_model.with_company(company)
|
205
|
+
.search(acc_domain_with_company)
|
206
|
+
.ids
|
207
|
+
)
|
286
208
|
self._account_ids_by_acc_domain[acc_domain].update(account_ids)
|
287
209
|
all_account_ids.update(account_ids)
|
288
210
|
self._map_account_ids[key] = list(all_account_ids)
|
@@ -299,7 +221,7 @@ class AccountingExpressionProcessor:
|
|
299
221
|
"""
|
300
222
|
account_ids = set()
|
301
223
|
for mo in self._ACC_RE.finditer(expr):
|
302
|
-
|
224
|
+
field, mode, acc_domain, ml_domain = self._parse_match_object(mo)
|
303
225
|
account_ids.update(self._account_ids_by_acc_domain[acc_domain])
|
304
226
|
return account_ids
|
305
227
|
|
@@ -313,7 +235,7 @@ class AccountingExpressionProcessor:
|
|
313
235
|
aml_domains = []
|
314
236
|
date_domain_by_mode = {}
|
315
237
|
for mo in self._ACC_RE.finditer(expr):
|
316
|
-
field, mode,
|
238
|
+
field, mode, acc_domain, ml_domain = self._parse_match_object(mo)
|
317
239
|
aml_domain = list(ml_domain)
|
318
240
|
account_ids = set()
|
319
241
|
account_ids.update(self._account_ids_by_acc_domain[acc_domain])
|
@@ -329,8 +251,6 @@ class AccountingExpressionProcessor:
|
|
329
251
|
aml_domain.append(("credit", "<>", 0.0))
|
330
252
|
elif field == "deb":
|
331
253
|
aml_domain.append(("debit", "<>", 0.0))
|
332
|
-
elif fld_name:
|
333
|
-
aml_domain.append((fld_name, "!=", False))
|
334
254
|
aml_domains.append(expression.normalize_domain(aml_domain))
|
335
255
|
if mode not in date_domain_by_mode:
|
336
256
|
date_domain_by_mode[mode] = self.get_aml_domain_for_dates(
|
@@ -406,10 +326,10 @@ class AccountingExpressionProcessor:
|
|
406
326
|
aml_model = self.env[aml_model]
|
407
327
|
aml_model = aml_model.with_context(active_test=False)
|
408
328
|
company_rates = self._get_company_rates(date_to)
|
409
|
-
# {(domain, mode): {account_id:
|
329
|
+
# {(domain, mode): {account_id: (debit, credit)}}
|
410
330
|
self._data = defaultdict(
|
411
331
|
lambda: defaultdict(
|
412
|
-
lambda:
|
332
|
+
lambda: SimpleArray((AccountingNone, AccountingNone)),
|
413
333
|
)
|
414
334
|
)
|
415
335
|
domain_by_mode = {}
|
@@ -431,49 +351,36 @@ class AccountingExpressionProcessor:
|
|
431
351
|
# fetch sum of debit/credit, grouped by account_id
|
432
352
|
_logger.debug("read_group domain: %s", domain)
|
433
353
|
try:
|
434
|
-
accs = aml_model.
|
354
|
+
accs = aml_model.with_context(
|
355
|
+
allowed_company_ids=self.companies.ids
|
356
|
+
)._read_group(
|
435
357
|
domain,
|
436
|
-
|
437
|
-
|
438
|
-
"credit",
|
439
|
-
"account_id",
|
440
|
-
"company_id",
|
441
|
-
*self._custom_fields,
|
442
|
-
],
|
443
|
-
["account_id", "company_id"],
|
444
|
-
lazy=False,
|
358
|
+
groupby=("account_id", "company_id"),
|
359
|
+
aggregates=("debit:sum", "credit:sum"),
|
445
360
|
)
|
446
361
|
except ValueError as e:
|
447
362
|
raise UserError(
|
448
|
-
_(
|
363
|
+
self.env._(
|
449
364
|
'Error while querying move line source "%(model_name)s". '
|
450
365
|
"This is likely due to a filter or expression referencing "
|
451
366
|
"a field that does not exist in the model.\n\n"
|
452
|
-
"The technical error message is: %(exception)s. "
|
453
|
-
)
|
454
|
-
% dict(
|
367
|
+
"The technical error message is: %(exception)s. ",
|
455
368
|
model_name=aml_model._description,
|
456
369
|
exception=e,
|
457
370
|
)
|
458
371
|
) from e
|
459
|
-
for
|
460
|
-
rate, dp = company_rates[
|
461
|
-
debit =
|
462
|
-
credit =
|
372
|
+
for account_id, company_id, debit, credit in accs:
|
373
|
+
rate, dp = company_rates[company_id.id]
|
374
|
+
debit = debit or 0.0
|
375
|
+
credit = credit or 0.0
|
463
376
|
if mode in (self.MODE_INITIAL, self.MODE_UNALLOCATED) and float_is_zero(
|
464
377
|
debit - credit, precision_digits=self.dp
|
465
378
|
):
|
466
379
|
# in initial mode, ignore accounts with 0 balance
|
467
380
|
continue
|
468
|
-
# due to branches, it's possible to have multiple
|
469
|
-
# with the same account_id
|
470
|
-
|
471
|
-
account_data = self._data[key][acc["account_id"][0]]
|
472
|
-
account_data.add_debit_credit(debit * rate, credit * rate)
|
473
|
-
for field_name in self._custom_fields:
|
474
|
-
account_data.add_custom_field(
|
475
|
-
field_name, acc[field_name] or AccountingNone
|
476
|
-
)
|
381
|
+
# due to branches, it's possible to have multiple acc
|
382
|
+
# with the same account_id
|
383
|
+
self._data[key][account_id.id] += (debit * rate, credit * rate)
|
477
384
|
# compute ending balances by summing initial and variation
|
478
385
|
for key in ends:
|
479
386
|
domain, mode = key
|
@@ -481,8 +388,11 @@ class AccountingExpressionProcessor:
|
|
481
388
|
variation_data = self._data[(domain, self.MODE_VARIATION)]
|
482
389
|
account_ids = set(initial_data.keys()) | set(variation_data.keys())
|
483
390
|
for account_id in account_ids:
|
484
|
-
|
485
|
-
|
391
|
+
di, ci = initial_data.get(account_id, (AccountingNone, AccountingNone))
|
392
|
+
dv, cv = variation_data.get(
|
393
|
+
account_id, (AccountingNone, AccountingNone)
|
394
|
+
)
|
395
|
+
self._data[key][account_id] = (di + dv, ci + cv)
|
486
396
|
|
487
397
|
def replace_expr(self, expr):
|
488
398
|
"""Replace accounting variables in an expression by their amount.
|
@@ -493,30 +403,25 @@ class AccountingExpressionProcessor:
|
|
493
403
|
"""
|
494
404
|
|
495
405
|
def f(mo):
|
496
|
-
field, mode,
|
406
|
+
field, mode, acc_domain, ml_domain = self._parse_match_object(mo)
|
497
407
|
key = (ml_domain, mode)
|
498
408
|
account_ids_data = self._data[key]
|
499
409
|
v = AccountingNone
|
500
410
|
account_ids = self._account_ids_by_acc_domain[acc_domain]
|
501
411
|
for account_id in account_ids:
|
502
|
-
|
503
|
-
|
504
|
-
|
412
|
+
debit, credit = account_ids_data.get(
|
413
|
+
account_id, (AccountingNone, AccountingNone)
|
414
|
+
)
|
505
415
|
if field == "bal":
|
506
416
|
v += debit - credit
|
507
|
-
elif field == "pbal":
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
if debit < credit:
|
512
|
-
v += debit - credit
|
417
|
+
elif field == "pbal" and debit >= credit:
|
418
|
+
v += debit - credit
|
419
|
+
elif field == "nbal" and debit < credit:
|
420
|
+
v += debit - credit
|
513
421
|
elif field == "deb":
|
514
422
|
v += debit
|
515
423
|
elif field == "crd":
|
516
424
|
v += credit
|
517
|
-
else:
|
518
|
-
assert field == "fld"
|
519
|
-
v += entry.custom_fields[fld_name]
|
520
425
|
# in initial balance mode, assume 0 is None
|
521
426
|
# as it does not make sense to distinguish 0 from "no data"
|
522
427
|
if (
|
@@ -539,7 +444,7 @@ class AccountingExpressionProcessor:
|
|
539
444
|
"""
|
540
445
|
|
541
446
|
def f(mo):
|
542
|
-
field, mode,
|
447
|
+
field, mode, acc_domain, ml_domain = self._parse_match_object(mo)
|
543
448
|
key = (ml_domain, mode)
|
544
449
|
# first check if account_id is involved in
|
545
450
|
# the current expression part
|
@@ -547,9 +452,9 @@ class AccountingExpressionProcessor:
|
|
547
452
|
return "(AccountingNone)"
|
548
453
|
# here we know account_id is involved in acc_domain
|
549
454
|
account_ids_data = self._data[key]
|
550
|
-
|
551
|
-
|
552
|
-
|
455
|
+
debit, credit = account_ids_data.get(
|
456
|
+
account_id, (AccountingNone, AccountingNone)
|
457
|
+
)
|
553
458
|
if field == "bal":
|
554
459
|
v = debit - credit
|
555
460
|
elif field == "pbal":
|
@@ -566,9 +471,6 @@ class AccountingExpressionProcessor:
|
|
566
471
|
v = debit
|
567
472
|
elif field == "crd":
|
568
473
|
v = credit
|
569
|
-
else:
|
570
|
-
assert field == "fld"
|
571
|
-
v = entry.custom_fields[fld_name]
|
572
474
|
# in initial balance mode, assume 0 is None
|
573
475
|
# as it does not make sense to distinguish 0 from "no data"
|
574
476
|
if (
|
@@ -582,11 +484,11 @@ class AccountingExpressionProcessor:
|
|
582
484
|
account_ids = set()
|
583
485
|
for expr in exprs:
|
584
486
|
for mo in self._ACC_RE.finditer(expr):
|
585
|
-
|
487
|
+
field, mode, acc_domain, ml_domain = self._parse_match_object(mo)
|
586
488
|
key = (ml_domain, mode)
|
587
489
|
account_ids_data = self._data[key]
|
588
490
|
for account_id in self._account_ids_by_acc_domain[acc_domain]:
|
589
|
-
if account_ids_data
|
491
|
+
if account_id in account_ids_data:
|
590
492
|
account_ids.add(account_id)
|
591
493
|
|
592
494
|
for account_id in account_ids:
|
@@ -602,7 +504,7 @@ class AccountingExpressionProcessor:
|
|
602
504
|
aep.parse_expr(expr)
|
603
505
|
aep.done_parsing()
|
604
506
|
aep.do_queries(date_from, date_to)
|
605
|
-
return
|
507
|
+
return aep._data[((), mode)]
|
606
508
|
|
607
509
|
@classmethod
|
608
510
|
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
|
67
|
+
TypeError: min expected 1 arguments, got 0
|
68
68
|
>>> _min()
|
69
69
|
Traceback (most recent call last):
|
70
70
|
File "<stdin>", line 1, in ?
|
71
|
-
TypeError: min expected
|
71
|
+
TypeError: min expected 1 arguments, 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
|
110
|
+
TypeError: max expected 1 arguments, got 0
|
111
111
|
>>> _max()
|
112
112
|
Traceback (most recent call last):
|
113
113
|
File "<stdin>", line 1, in ?
|
114
|
-
TypeError: max expected
|
114
|
+
TypeError: max expected 1 arguments, got 0
|
115
115
|
>>> max([])
|
116
116
|
Traceback (most recent call last):
|
117
117
|
File "<stdin>", line 1, in ?
|
@@ -4,7 +4,6 @@
|
|
4
4
|
import logging
|
5
5
|
from collections import OrderedDict, defaultdict
|
6
6
|
|
7
|
-
from odoo import _
|
8
7
|
from odoo.exceptions import UserError
|
9
8
|
|
10
9
|
from .accounting_none import AccountingNone
|
@@ -245,10 +244,9 @@ class KpiMatrix:
|
|
245
244
|
self.lang, row.style_props, kpi.type, val
|
246
245
|
)
|
247
246
|
if row.kpi.multi and subcol.subkpi:
|
248
|
-
val_comment =
|
249
|
-
row.kpi.name
|
250
|
-
subcol.subkpi
|
251
|
-
row.kpi._get_expression_str_for_subkpi(subcol.subkpi),
|
247
|
+
val_comment = (
|
248
|
+
f"{row.kpi.name}.{subcol.subkpi.name} = "
|
249
|
+
f"{row.kpi._get_expression_str_for_subkpi(subcol.subkpi)}"
|
252
250
|
)
|
253
251
|
else:
|
254
252
|
val_comment = f"{row.kpi.name} = {row.kpi.expression}"
|
@@ -309,7 +307,7 @@ class KpiMatrix:
|
|
309
307
|
common_subkpis = self._common_subkpis([col, base_col])
|
310
308
|
if (col.subkpis or base_col.subkpis) and not common_subkpis:
|
311
309
|
raise UserError(
|
312
|
-
_(
|
310
|
+
self.env._(
|
313
311
|
"Columns %(descr)s and %(base_descr)s are not comparable",
|
314
312
|
descr=col.description,
|
315
313
|
base_descr=base_col.description,
|
@@ -395,11 +393,12 @@ class KpiMatrix:
|
|
395
393
|
common_subkpis = self._common_subkpis(sumcols)
|
396
394
|
if any(c.subkpis for c in sumcols) and not common_subkpis:
|
397
395
|
raise UserError(
|
398
|
-
_(
|
399
|
-
"Sum cannot be computed in column
|
396
|
+
self.env._(
|
397
|
+
"Sum cannot be computed in column %s "
|
400
398
|
"because the columns to sum have no "
|
401
|
-
"common subkpis"
|
402
|
-
|
399
|
+
"common subkpis",
|
400
|
+
label,
|
401
|
+
)
|
403
402
|
)
|
404
403
|
sum_col = KpiMatrixCol(
|
405
404
|
sumcol_key,
|
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
from collections import defaultdict
|
5
5
|
|
6
|
-
from odoo import
|
6
|
+
from odoo import api, fields, models
|
7
7
|
from odoo.exceptions import UserError
|
8
8
|
from odoo.osv import expression
|
9
9
|
|
@@ -60,11 +60,9 @@ class MisKpiData(models.AbstractModel):
|
|
60
60
|
subkpi_name = "." + subkpi_name
|
61
61
|
else:
|
62
62
|
subkpi_name = ""
|
63
|
-
rec.name =
|
64
|
-
rec.kpi_expression_id.kpi_id.name
|
65
|
-
|
66
|
-
rec.date_from,
|
67
|
-
rec.date_to,
|
63
|
+
rec.name = (
|
64
|
+
f"{rec.kpi_expression_id.kpi_id.name}{subkpi_name}: "
|
65
|
+
f"{rec.date_from} - {rec.date_to}"
|
68
66
|
)
|
69
67
|
|
70
68
|
@api.model
|
@@ -99,7 +97,7 @@ class MisKpiData(models.AbstractModel):
|
|
99
97
|
res_avg[item.kpi_expression_id].append((i_days, item.amount))
|
100
98
|
else:
|
101
99
|
raise UserError(
|
102
|
-
_(
|
100
|
+
self.env._(
|
103
101
|
"Unexpected accumulation method %(method)s for %(name)s.",
|
104
102
|
method=item.kpi_expression_id.kpi_id.accumulation_method,
|
105
103
|
name=item.name,
|