odoo-addon-mis-builder 17.0.1.1.0__py3-none-any.whl → 17.0.1.2.0.1__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/ca.po +51 -8
- odoo/addons/mis_builder/i18n/de.po +39 -4
- odoo/addons/mis_builder/i18n/el.po +39 -4
- odoo/addons/mis_builder/i18n/el_GR.po +39 -4
- odoo/addons/mis_builder/i18n/es.po +65 -12
- odoo/addons/mis_builder/i18n/fr.po +65 -12
- odoo/addons/mis_builder/i18n/hr.po +39 -4
- odoo/addons/mis_builder/i18n/it.po +66 -14
- odoo/addons/mis_builder/i18n/mis_builder.pot +32 -3
- odoo/addons/mis_builder/i18n/nl.po +39 -4
- odoo/addons/mis_builder/i18n/nl_NL.po +39 -4
- odoo/addons/mis_builder/i18n/pt.po +39 -4
- odoo/addons/mis_builder/i18n/pt_BR.po +64 -13
- odoo/addons/mis_builder/i18n/sv.po +64 -12
- odoo/addons/mis_builder/i18n/tr.po +39 -4
- odoo/addons/mis_builder/i18n/zh_CN.po +154 -78
- 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-17.0.1.1.0.dist-info → odoo_addon_mis_builder-17.0.1.2.0.1.dist-info}/METADATA +2 -2
- {odoo_addon_mis_builder-17.0.1.1.0.dist-info → odoo_addon_mis_builder-17.0.1.2.0.1.dist-info}/RECORD +27 -27
- {odoo_addon_mis_builder-17.0.1.1.0.dist-info → odoo_addon_mis_builder-17.0.1.2.0.1.dist-info}/WHEEL +0 -0
- {odoo_addon_mis_builder-17.0.1.1.0.dist-info → odoo_addon_mis_builder-17.0.1.2.0.1.dist-info}/top_level.txt +0 -0
@@ -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:a2cbe7d88f7c0d1b932bbfb184b37bc218fd9a523c1e1b0a904448f996590942
|
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/17.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-17-0/mis-builder-17-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=17.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
|
@@ -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):
|
@@ -73,6 +77,7 @@ class TestAEP(common.TransactionCase):
|
|
73
77
|
amount=300,
|
74
78
|
debit_acc=self.account_ar,
|
75
79
|
credit_acc=self.account_in,
|
80
|
+
credit_quantity=3,
|
76
81
|
)
|
77
82
|
# create move in March this year
|
78
83
|
self._create_move(
|
@@ -98,6 +103,7 @@ class TestAEP(common.TransactionCase):
|
|
98
103
|
self.aep.parse_expr("crdp[700I%]")
|
99
104
|
self.aep.parse_expr("bali[400%]")
|
100
105
|
self.aep.parse_expr("bale[700%]")
|
106
|
+
self.aep.parse_expr("fldp.quantity[700%]")
|
101
107
|
self.aep.parse_expr("balp[]" "[('account_id.code', '=', '400AR')]")
|
102
108
|
self.aep.parse_expr(
|
103
109
|
"balp[]" "[('account_id.account_type', '=', " " 'asset_receivable')]"
|
@@ -112,17 +118,32 @@ class TestAEP(common.TransactionCase):
|
|
112
118
|
self.aep.parse_expr("bal_700IN") # deprecated
|
113
119
|
self.aep.parse_expr("bals[700IN]") # deprecated
|
114
120
|
|
115
|
-
def _create_move(
|
121
|
+
def _create_move(
|
122
|
+
self, date, amount, debit_acc, credit_acc, post=True, credit_quantity=0
|
123
|
+
):
|
116
124
|
move = self.move_model.create(
|
117
125
|
{
|
118
126
|
"journal_id": self.journal.id,
|
119
127
|
"date": fields.Date.to_string(date),
|
120
128
|
"line_ids": [
|
121
|
-
(0, 0, {"name": "/", "debit": amount, "account_id": debit_acc.id}),
|
122
129
|
(
|
123
130
|
0,
|
124
131
|
0,
|
125
|
-
{
|
132
|
+
{
|
133
|
+
"name": "/",
|
134
|
+
"debit": amount,
|
135
|
+
"account_id": debit_acc.id,
|
136
|
+
},
|
137
|
+
),
|
138
|
+
(
|
139
|
+
0,
|
140
|
+
0,
|
141
|
+
{
|
142
|
+
"name": "/",
|
143
|
+
"credit": amount,
|
144
|
+
"account_id": credit_acc.id,
|
145
|
+
"quantity": credit_quantity,
|
146
|
+
},
|
126
147
|
),
|
127
148
|
],
|
128
149
|
}
|
@@ -152,6 +173,20 @@ class TestAEP(common.TransactionCase):
|
|
152
173
|
self.assertEqual(self.company.fiscalyear_last_day, 31)
|
153
174
|
self.assertEqual(self.company.fiscalyear_last_month, "12")
|
154
175
|
|
176
|
+
def test_parse_expr_error_handling(self):
|
177
|
+
aep = AEP(self.company)
|
178
|
+
with self.assertRaises(UserError) as cm:
|
179
|
+
aep.parse_expr("fldi.quantity[700%]")
|
180
|
+
self.assertIn(
|
181
|
+
"`fld` can only be used with mode `p` (variation)", str(cm.exception)
|
182
|
+
)
|
183
|
+
with self.assertRaises(UserError) as cm:
|
184
|
+
aep.parse_expr("fldp[700%]")
|
185
|
+
self.assertIn("`fld` must have a field name", str(cm.exception))
|
186
|
+
with self.assertRaises(UserError) as cm:
|
187
|
+
aep.parse_expr("balp.quantity[700%]")
|
188
|
+
self.assertIn("`bal` cannot have a field name", str(cm.exception))
|
189
|
+
|
155
190
|
def test_aep_basic(self):
|
156
191
|
self.aep.done_parsing()
|
157
192
|
# let's query for december
|
@@ -203,6 +238,8 @@ class TestAEP(common.TransactionCase):
|
|
203
238
|
self.assertEqual(self._eval("bale[700IN]"), -300)
|
204
239
|
# check result for non existing account
|
205
240
|
self.assertIs(self._eval("bale[700NA]"), AccountingNone)
|
241
|
+
# check fldp.quantity
|
242
|
+
self.assertEqual(self._eval("fldp.quantity[700%]"), 3)
|
206
243
|
|
207
244
|
# let's query for March
|
208
245
|
self._do_queries(
|
@@ -234,6 +271,8 @@ class TestAEP(common.TransactionCase):
|
|
234
271
|
self.assertEqual(self._eval("debp[400A%]"), 500)
|
235
272
|
self.assertEqual(self._eval("bal_700IN"), -500)
|
236
273
|
self.assertEqual(self._eval("bals[700IN]"), -800)
|
274
|
+
# check fldp.quantity
|
275
|
+
self.assertEqual(self._eval("fldp.quantity[700%]"), 0)
|
237
276
|
|
238
277
|
# unallocated p&l from previous year
|
239
278
|
self.assertEqual(self._eval("balu[]"), -100)
|
@@ -166,19 +166,24 @@
|
|
166
166
|
<p
|
167
167
|
> The following special elements are recognized in the expressions
|
168
168
|
to compute accounting data: <code
|
169
|
-
>{bal|crd|deb|pbal|nbal}{pieu}[account
|
169
|
+
>{bal|crd|deb|pbal|nbal|fld}{pieu}(.fieldname)[account
|
170
170
|
selector][journal items domain]</code>. </p>
|
171
171
|
<ul>
|
172
172
|
<li>
|
173
173
|
<code>bal</code>, <code>crd</code>, <code
|
174
174
|
>deb</code>, <code>
|
175
|
-
pbal</code>, <code
|
176
|
-
>
|
177
|
-
positive balance, negative balance
|
175
|
+
pbal</code>, <code>nbal</code>, <code
|
176
|
+
>fld</code> : balance, debit, credit,
|
177
|
+
positive balance, negative balance,
|
178
|
+
other numerical field. </li>
|
178
179
|
<li>
|
179
180
|
<code>p</code>, <code>i</code>, <code
|
180
181
|
>e</code> : respectively variation over the period,
|
181
182
|
initial balance, ending balance </li>
|
183
|
+
<li>when <code
|
184
|
+
>fld</code> is used : a field name specifier
|
185
|
+
must be provided (e.g. <code
|
186
|
+
>fldp.quantity</code></li>
|
182
187
|
<li> The <b
|
183
188
|
>account selector</b> is a like expression on the
|
184
189
|
account code (eg <code
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: odoo-addon-mis_builder
|
3
|
-
Version: 17.0.1.
|
3
|
+
Version: 17.0.1.2.0.1
|
4
4
|
Requires-Python: >=3.10
|
5
5
|
Requires-Dist: odoo-addon-date_range>=17.0dev,<17.1dev
|
6
6
|
Requires-Dist: odoo-addon-report_xlsx>=17.0dev,<17.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:a2cbe7d88f7c0d1b932bbfb184b37bc218fd9a523c1e1b0a904448f996590942
|
29
29
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
30
30
|
|
31
31
|
.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png
|
{odoo_addon_mis_builder-17.0.1.1.0.dist-info → odoo_addon_mis_builder-17.0.1.2.0.1.dist-info}/RECORD
RENAMED
@@ -1,27 +1,27 @@
|
|
1
|
-
odoo/addons/mis_builder/README.rst,sha256=
|
1
|
+
odoo/addons/mis_builder/README.rst,sha256=_UWAM-W4BCGuRm9Za3zUQ1o6M3UazO61YAS2JWmTwc0,30016
|
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=c9qmMqRjfOEUwDSp7cDLd06Ii-Ygn9nVmDLXkOvMTFg,1556
|
4
4
|
odoo/addons/mis_builder/datas/ir_cron.xml,sha256=H5nnz-4CqZPVveJdRoJ_DrBOOsihK4XwOjWcSMYv68A,558
|
5
|
-
odoo/addons/mis_builder/i18n/ca.po,sha256=
|
6
|
-
odoo/addons/mis_builder/i18n/de.po,sha256=
|
7
|
-
odoo/addons/mis_builder/i18n/el.po,sha256=
|
8
|
-
odoo/addons/mis_builder/i18n/el_GR.po,sha256=
|
9
|
-
odoo/addons/mis_builder/i18n/es.po,sha256=
|
10
|
-
odoo/addons/mis_builder/i18n/fr.po,sha256=
|
11
|
-
odoo/addons/mis_builder/i18n/hr.po,sha256=
|
12
|
-
odoo/addons/mis_builder/i18n/it.po,sha256=
|
13
|
-
odoo/addons/mis_builder/i18n/mis_builder.pot,sha256=
|
14
|
-
odoo/addons/mis_builder/i18n/nl.po,sha256=
|
15
|
-
odoo/addons/mis_builder/i18n/nl_NL.po,sha256=
|
16
|
-
odoo/addons/mis_builder/i18n/pt.po,sha256=
|
17
|
-
odoo/addons/mis_builder/i18n/pt_BR.po,sha256=
|
18
|
-
odoo/addons/mis_builder/i18n/sv.po,sha256=
|
19
|
-
odoo/addons/mis_builder/i18n/tr.po,sha256=
|
20
|
-
odoo/addons/mis_builder/i18n/zh_CN.po,sha256=
|
5
|
+
odoo/addons/mis_builder/i18n/ca.po,sha256=b51cobs_hMSWOzwBtOfAybGK6XkkQ8i5wDFrp907IOQ,91900
|
6
|
+
odoo/addons/mis_builder/i18n/de.po,sha256=oz0ECpiPHS-sSR6UUJJSW5GnmFPUn1FRLG6yUCI2d1U,65115
|
7
|
+
odoo/addons/mis_builder/i18n/el.po,sha256=9A7Cnl9RteS2EW-zsydVL2ziLeZWwwp-H5cjr0KQIzg,61903
|
8
|
+
odoo/addons/mis_builder/i18n/el_GR.po,sha256=Nv4x6ZcKNr--9E7KMQopn6J1lBtNXL-uFNbZ8qQOqo8,61906
|
9
|
+
odoo/addons/mis_builder/i18n/es.po,sha256=bcf51qLSVlZ60kXYv9tNThHTuxLI5YrXh4Mt5mVhZL0,86967
|
10
|
+
odoo/addons/mis_builder/i18n/fr.po,sha256=1JXGkOkuRc4foE7vqAJ2kCUsF-Q1AAS2mLcnTJveV9Q,83109
|
11
|
+
odoo/addons/mis_builder/i18n/hr.po,sha256=QMuV4xwpyrP4FiWa5T_SNTvvswjoYUIaVPJpdISbN_8,67193
|
12
|
+
odoo/addons/mis_builder/i18n/it.po,sha256=Gl2eLfa0233w9OBm0mfibH3ZQKytkxfl_Ky2ILUdueU,76735
|
13
|
+
odoo/addons/mis_builder/i18n/mis_builder.pot,sha256=b_NjpXhpt8zzPZasqCqfzQJ7t_c7eVzCJZkDmSeVlPg,61958
|
14
|
+
odoo/addons/mis_builder/i18n/nl.po,sha256=swse_VzVTiOgmoVQLef3JVB8hw4_1ogut7bJ3Kt-TxE,74578
|
15
|
+
odoo/addons/mis_builder/i18n/nl_NL.po,sha256=4v5QtZYssunT8KDQs84ICQWOcWbWsAgCYAPUPyNX3DA,74670
|
16
|
+
odoo/addons/mis_builder/i18n/pt.po,sha256=woGekBodCI7Bkl2Q8VlEGivMDW2krzrqb82tLiC49jE,62409
|
17
|
+
odoo/addons/mis_builder/i18n/pt_BR.po,sha256=psnvGhcNcg3vHbmElLof7anm1XTeIc6Uvq9ylQUdYHo,83747
|
18
|
+
odoo/addons/mis_builder/i18n/sv.po,sha256=kEeoHW4LjKL9EjOnuI2WqcPqloHUCcjJIroXDMFWVyY,74568
|
19
|
+
odoo/addons/mis_builder/i18n/tr.po,sha256=NVl2xJmSb5fSf-AGBQfDEB6QvJefHN7KFT3z3F97p2g,64060
|
20
|
+
odoo/addons/mis_builder/i18n/zh_CN.po,sha256=oSLy8YOF5LV2p3iFiWtQNn8QWNdKTkuIpjCIFf661AA,72558
|
21
21
|
odoo/addons/mis_builder/models/__init__.py,sha256=_h9tbt-H0JjtHL6kI7-wiEnd5u2qkP855DevyjxLBbI,331
|
22
22
|
odoo/addons/mis_builder/models/accounting_none.py,sha256=6EE6x4slUbyP2TVnHpilsWu8XqWhGZMt79uBOmNLcVs,4156
|
23
|
-
odoo/addons/mis_builder/models/aep.py,sha256=
|
24
|
-
odoo/addons/mis_builder/models/aggregate.py,sha256=
|
23
|
+
odoo/addons/mis_builder/models/aep.py,sha256=2quKRHL2mvbMaTQlnhN_YbAxit9gXhTYb10d0qxbZ0s,26305
|
24
|
+
odoo/addons/mis_builder/models/aggregate.py,sha256=ffOZq2-R7st-1Hsw-lFfiwkMa46ToJZ7m0K6Z9P_gwQ,2892
|
25
25
|
odoo/addons/mis_builder/models/data_error.py,sha256=ltTIrFCbsxYXmdPTVFVJj3ejZxyc83NEsIFQs2s3-L0,442
|
26
26
|
odoo/addons/mis_builder/models/expression_evaluator.py,sha256=bMCqqVwQ3_8JED2ddZPAaUz0jWaYRK1VcjYp9DXm_Kk,2458
|
27
27
|
odoo/addons/mis_builder/models/kpimatrix.py,sha256=daXS0k2VHLJO3_0cBcSQUBuJrvBBPxwnDJkXHTgvzFM,19852
|
@@ -52,7 +52,7 @@ odoo/addons/mis_builder/static/description/ex_report_preview.png,sha256=NKBRn70E
|
|
52
52
|
odoo/addons/mis_builder/static/description/ex_report_settings.png,sha256=T7j_3DUUtdZ6XKsAvJ3uKsK_-GtRl0sRPD68ZOa6ebA,103790
|
53
53
|
odoo/addons/mis_builder/static/description/ex_report_template.png,sha256=c8KgzyGD5QiZNCmxaVR4u3h1K0TJN50uXZWoNsieN8A,100278
|
54
54
|
odoo/addons/mis_builder/static/description/icon.png,sha256=0OCahdqDvaS_CHV97ZCbajW0_AcBLVVvja_EJA2mC6s,4770
|
55
|
-
odoo/addons/mis_builder/static/description/index.html,sha256=
|
55
|
+
odoo/addons/mis_builder/static/description/index.html,sha256=4OtrUMI5Ec-RZim_uclTDQ4L9qyim5JVOBoewDVZHVc,56611
|
56
56
|
odoo/addons/mis_builder/static/src/components/mis_report_widget.css,sha256=ew7iozGAv9ue3KPf_8qA_AhHlNGis7pJu8A7tc6CO4Y,1198
|
57
57
|
odoo/addons/mis_builder/static/src/components/mis_report_widget.esm.js,sha256=RmXA9UKreBrKOT4RxjCumskqZMV0O8p4NiWD3ereCOg,5529
|
58
58
|
odoo/addons/mis_builder/static/src/components/mis_report_widget.xml,sha256=mKgFff56wLRgJzq8Cdc9oZVajkTY73u_S1S0-Z-oHhQ,5648
|
@@ -61,7 +61,7 @@ odoo/addons/mis_builder/tests/__init__.py,sha256=ilgojed90T4NL_YXgVf3CACd8fo7nxp
|
|
61
61
|
odoo/addons/mis_builder/tests/common.py,sha256=7LqnpPyJ8yOi5gA4CwiKZaep6NGY_vIDnK36u8WVC1o,1918
|
62
62
|
odoo/addons/mis_builder/tests/fake_models.py,sha256=DWJsXiIVVXZwXfLQ-9TmUs3eM5KBCXibS2qRXgkFt1c,177
|
63
63
|
odoo/addons/mis_builder/tests/test_accounting_none.py,sha256=satnhbRuI4DdWIOpe-mG7sW9KlamgnyvoVaPQlqZ9FA,239
|
64
|
-
odoo/addons/mis_builder/tests/test_aep.py,sha256=
|
64
|
+
odoo/addons/mis_builder/tests/test_aep.py,sha256=jS0YSV3YUGdB5ljvjb8yiQNhCKvbvNZQcBeCGZ3LLnk,19641
|
65
65
|
odoo/addons/mis_builder/tests/test_aggregate.py,sha256=ZXEVW0hMZYUIJM6ikQpmBH9Xc9TaY46xYbBsH8vcXDQ,226
|
66
66
|
odoo/addons/mis_builder/tests/test_data_sources.py,sha256=KK8sycuYtaa6o8s2Lzehi3XbYwGVViL-cE3eReOggAk,7792
|
67
67
|
odoo/addons/mis_builder/tests/test_kpi_data.py,sha256=PrgfJIkrw5liJ8InLoXCGXMD-uc7y23J-7R-UYxWIO8,4860
|
@@ -74,13 +74,13 @@ odoo/addons/mis_builder/tests/test_simple_array.py,sha256=PHzSr_vb4Wn7dfwjaFSAlu
|
|
74
74
|
odoo/addons/mis_builder/tests/test_subreport.py,sha256=I7tQdhjaei15lzhXQLrbL80mlFtAyeoV-gHcs6xaEUo,3202
|
75
75
|
odoo/addons/mis_builder/tests/test_target_move.py,sha256=Jw7F9QMHVekOr-hoAy6kKCNcYAZAmfjOUbqcgjPw9Ps,1296
|
76
76
|
odoo/addons/mis_builder/tests/test_utc_midnight.py,sha256=mET_qOOC3qsOJ4MK_yTRocicW_NvKdRhjtpVJjLYaaU,917
|
77
|
-
odoo/addons/mis_builder/views/mis_report.xml,sha256=
|
77
|
+
odoo/addons/mis_builder/views/mis_report.xml,sha256=U-BGql4Ohgn1P9RHLWoqFcyYKOGhpZ1omMxOiCpuKA8,15649
|
78
78
|
odoo/addons/mis_builder/views/mis_report_instance.xml,sha256=fvSaQTAZuzmc2OloYO3US7Fg8gPD4_wZ1zEpwRwTlYo,18465
|
79
79
|
odoo/addons/mis_builder/views/mis_report_style.xml,sha256=5cK60KTio96_roE0Xcm91WKGdKcVULrO8qhzgVtYc9A,3652
|
80
80
|
odoo/addons/mis_builder/wizard/__init__.py,sha256=qlc_LcwA6U3Wgv-qC9uacPjZXV3jShrV_RBiuKCBNPA,158
|
81
81
|
odoo/addons/mis_builder/wizard/mis_builder_dashboard.py,sha256=DIas3iie9fb2WzJ2eGcrVc7y64IuRyxSG7tJrcV-53g,3404
|
82
82
|
odoo/addons/mis_builder/wizard/mis_builder_dashboard.xml,sha256=ArG86hdyRWm531nK0hYU-j1N87yXtpDVpSuKTmOB47I,1440
|
83
|
-
odoo_addon_mis_builder-17.0.1.
|
84
|
-
odoo_addon_mis_builder-17.0.1.
|
85
|
-
odoo_addon_mis_builder-17.0.1.
|
86
|
-
odoo_addon_mis_builder-17.0.1.
|
83
|
+
odoo_addon_mis_builder-17.0.1.2.0.1.dist-info/METADATA,sha256=Q-J0NJBiHf7-NCUvLzZcxM94MOiRu3CW8BNN03OY2XY,30732
|
84
|
+
odoo_addon_mis_builder-17.0.1.2.0.1.dist-info/WHEEL,sha256=9fEMia4zL7ZuZbnCOrcYogUhmn4XFIVaJ8G4YGI31xc,81
|
85
|
+
odoo_addon_mis_builder-17.0.1.2.0.1.dist-info/top_level.txt,sha256=QE6RBQ0QX5f4eFuUcGgU5Kbq1A_qJcDs-e_vpr6pmfU,4
|
86
|
+
odoo_addon_mis_builder-17.0.1.2.0.1.dist-info/RECORD,,
|
{odoo_addon_mis_builder-17.0.1.1.0.dist-info → odoo_addon_mis_builder-17.0.1.2.0.1.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|