odoo-addon-mis-builder 17.0.1.1.0__py3-none-any.whl → 17.0.1.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.
@@ -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 <field><mode>[accounts][optional move line domain]
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
- _, mode, acc_domain, ml_domain = self._parse_match_object(mo)
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
- field, mode, acc_domain, ml_domain = self._parse_match_object(mo)
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: (debit, credit)}}
402
+ # {(domain, mode): {account_id: Accumulator}}
320
403
  self._data = defaultdict(
321
404
  lambda: defaultdict(
322
- lambda: SimpleArray((AccountingNone, AccountingNone)),
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
- ["debit", "credit", "account_id", "company_id"],
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 acc
373
- # with the same account_id
374
- self._data[key][acc["account_id"][0]] += (debit * rate, credit * rate)
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
- di, ci = initial_data.get(account_id, (AccountingNone, AccountingNone))
383
- dv, cv = variation_data.get(
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
- debit, credit = account_ids_data.get(
404
- account_id, (AccountingNone, AccountingNone)
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" and debit >= credit:
409
- v += debit - credit
410
- elif field == "nbal" and debit < credit:
411
- v += debit - credit
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
- debit, credit = account_ids_data.get(
447
- account_id, (AccountingNone, AccountingNone)
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
- field, mode, acc_domain, ml_domain = self._parse_match_object(mo)
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 arguments, got 0
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 arguments, got 0
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 arguments, got 0
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 arguments, got 0
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:15b809694d0df33ce04aec8e8ed16625a3442496a11aa63c0da77e303caf1abc
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&amp;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(self, date, amount, debit_acc, credit_acc, post=True):
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
- {"name": "/", "credit": amount, "account_id": credit_acc.id},
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
- >nbal</code> : balance, debit, credit,
177
- positive balance, negative balance. </li>
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.1.0
3
+ Version: 17.0.1.2.0
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:15b809694d0df33ce04aec8e8ed16625a3442496a11aa63c0da77e303caf1abc
28
+ !! source digest: sha256:a2cbe7d88f7c0d1b932bbfb184b37bc218fd9a523c1e1b0a904448f996590942
29
29
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
30
30
 
31
31
  .. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png
@@ -1,27 +1,27 @@
1
- odoo/addons/mis_builder/README.rst,sha256=s8f6ft6qOH1Y1mWuvtqYXJXkR_zfOBz0HdPYt3aWNik,30016
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=PDhxXHd6wNjvEF9Kz4TvDQMn7vETtBpfPALdFa0Cmdw,1556
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=tnO27zu_QCTgIEJx90e2u83wjSD0q9sirIgglS7ELt0,90543
6
- odoo/addons/mis_builder/i18n/de.po,sha256=zITcHR6Mrg_JTOvqaUP642fF_uJe_9Yh63-JCnXaxGw,64061
7
- odoo/addons/mis_builder/i18n/el.po,sha256=4n4xasPth_VhpcfbGD2noyIQXbvoDnNbifWUN6TYFYo,60849
8
- odoo/addons/mis_builder/i18n/el_GR.po,sha256=9Zl1Z4QQA3IlC63VWpGfmI8lcGH0PdvPLkqakq8GeLs,60852
9
- odoo/addons/mis_builder/i18n/es.po,sha256=C8dQsnQoRC3c1ZkYNFBIehcR8z50i2Rc82-oD9r9pu0,85280
10
- odoo/addons/mis_builder/i18n/fr.po,sha256=2_QU_THoAnLqlzfRjs9901t_tGc4Xp7UkVQrD-q66dU,81422
11
- odoo/addons/mis_builder/i18n/hr.po,sha256=m38tz_PgcicD_dibswZ0Wfey-NzXKiP3iSJghTfZZR0,66139
12
- odoo/addons/mis_builder/i18n/it.po,sha256=7HC4PVgRPyxmgSplxyUJ3A55R6bXr1C076i7fWaJlrI,75018
13
- odoo/addons/mis_builder/i18n/mis_builder.pot,sha256=NJvMMzH_vvIl_pNkQveOvG3Ol8sS7REY5WD0egzi634,61070
14
- odoo/addons/mis_builder/i18n/nl.po,sha256=e8COlMWCR6RYmbVE71PZyH-dc5i5WnBmoVm3JsqwpFM,73524
15
- odoo/addons/mis_builder/i18n/nl_NL.po,sha256=4JeEMcIsdphsrhKKQ5S3On0pYch8ofPRnDPVk-aP5og,73616
16
- odoo/addons/mis_builder/i18n/pt.po,sha256=84r8lMIPshXQKj5g_RwFQOKuuiM2XPwH-KYN1QOYsKQ,61355
17
- odoo/addons/mis_builder/i18n/pt_BR.po,sha256=ef1AtoiJSM4lKDrRukDWaieHRFIut6LghL-SSr2Q-r0,82069
18
- odoo/addons/mis_builder/i18n/sv.po,sha256=z2tx4LZ8qg8jcAtudT5F4wBRrfi_CnMFfwmN9nVHirQ,72887
19
- odoo/addons/mis_builder/i18n/tr.po,sha256=QyJxnmYQnFefKPJ7ZOY5V8LjCEzduSIAooggJsQNE6o,63006
20
- odoo/addons/mis_builder/i18n/zh_CN.po,sha256=ySUlW4Ze3a7j9o5Jmum43f-6wUM_ma88Hyj2I_ZyUso,71008
5
+ odoo/addons/mis_builder/i18n/ca.po,sha256=ezJzS5M-1-IkpTNuQoJ9uBbG4Ecz_XPSb01aEI493_s,90706
6
+ odoo/addons/mis_builder/i18n/de.po,sha256=sAlfdPvLJuUhU0NYel7bOZBfOOPA_f0BrsBeXnwv0aM,64224
7
+ odoo/addons/mis_builder/i18n/el.po,sha256=1OUnguvBICbZ0-loRQB8-E-dprpuXpfz1yTMvI81H_A,61012
8
+ odoo/addons/mis_builder/i18n/el_GR.po,sha256=gyONbTj1aYfomRjtXDmHf6bSwQbhEuAEHnIAOldcuhA,61015
9
+ odoo/addons/mis_builder/i18n/es.po,sha256=P7EOqRc4Ynr2cg26xjFm961ihFIzTli8sz7JJoLOOPs,85443
10
+ odoo/addons/mis_builder/i18n/fr.po,sha256=FVPCXEODjx7VMRJ2L-zSEv2HJcj72PvQU_M8kyPkS7E,81585
11
+ odoo/addons/mis_builder/i18n/hr.po,sha256=tkaSnT4zCvbgmK5C1bMZLfIQZznesgap3GxLda6cS3s,66302
12
+ odoo/addons/mis_builder/i18n/it.po,sha256=TnZfLPpijjUotjr29EY1ZN_v4TSEHpGfSQIjTbm3jnk,75214
13
+ odoo/addons/mis_builder/i18n/mis_builder.pot,sha256=b_NjpXhpt8zzPZasqCqfzQJ7t_c7eVzCJZkDmSeVlPg,61958
14
+ odoo/addons/mis_builder/i18n/nl.po,sha256=FObPX4EtCM7ucjkzxWgrKx1yE8g7m8HU6CYB19VEn5k,73687
15
+ odoo/addons/mis_builder/i18n/nl_NL.po,sha256=QzkjRZVFX7_SooHY8ickJR8ZF4M0VE9UJOlliMkCoYI,73779
16
+ odoo/addons/mis_builder/i18n/pt.po,sha256=isTF_s1qnIaR_AGMxczgLW3g5aVHJcPsx0846dwXTjk,61518
17
+ odoo/addons/mis_builder/i18n/pt_BR.po,sha256=6-NvYrDXCOsWKeAO4LQyRGKSbVJaPiOCIZaR2Gs2kcc,82232
18
+ odoo/addons/mis_builder/i18n/sv.po,sha256=pknXdMAtN-Goy-Wz4YqEj9BytXWBzYX1leBPaQnVnmU,73050
19
+ odoo/addons/mis_builder/i18n/tr.po,sha256=CLy7ibx6lBaZ3dA8AO9oQyRUuRqLo_chqe8chZZi9sU,63169
20
+ odoo/addons/mis_builder/i18n/zh_CN.po,sha256=_3wAAcZeJvg60l88Pe4ScbpcrztUmJjqQtoHR-ndILw,71058
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=aXeeMUSnd8FXHZ158OVvXwUI9SXwwQAIbw1SmuRxv40,22767
24
- odoo/addons/mis_builder/models/aggregate.py,sha256=jLULkhMPh4FBJ9T8Al2Qf9uUwBjhS5t8W2J2tRrYQAA,2860
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=M-sDnoNLdpl1LTJ43TbWP9ak2NbQksDdXqQbMI7jV6I,56611
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=pgm3YO_ER7rkReFshhgp3xOW_LQ12cLOcdXkFFs7gPE,18244
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=po5Zgpt57--B3OrXn-u7FyTbod851osFRZEX02miStg,15280
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.1.0.dist-info/METADATA,sha256=3iORTuT6FouyglqfmklUCAvxS7bQ7OLDecS_BvgLXWg,30730
84
- odoo_addon_mis_builder-17.0.1.1.0.dist-info/WHEEL,sha256=9fEMia4zL7ZuZbnCOrcYogUhmn4XFIVaJ8G4YGI31xc,81
85
- odoo_addon_mis_builder-17.0.1.1.0.dist-info/top_level.txt,sha256=QE6RBQ0QX5f4eFuUcGgU5Kbq1A_qJcDs-e_vpr6pmfU,4
86
- odoo_addon_mis_builder-17.0.1.1.0.dist-info/RECORD,,
83
+ odoo_addon_mis_builder-17.0.1.2.0.dist-info/METADATA,sha256=1sJg_azyMQUwVCC1ZKdnZ3sQrep4KD24f0KQr-qS5Qo,30730
84
+ odoo_addon_mis_builder-17.0.1.2.0.dist-info/WHEEL,sha256=9fEMia4zL7ZuZbnCOrcYogUhmn4XFIVaJ8G4YGI31xc,81
85
+ odoo_addon_mis_builder-17.0.1.2.0.dist-info/top_level.txt,sha256=QE6RBQ0QX5f4eFuUcGgU5Kbq1A_qJcDs-e_vpr6pmfU,4
86
+ odoo_addon_mis_builder-17.0.1.2.0.dist-info/RECORD,,