odoo-addon-mis-builder 16.0.5.2.3__py3-none-any.whl → 16.0.5.3.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.
Files changed (23) hide show
  1. odoo/addons/mis_builder/README.rst +1 -1
  2. odoo/addons/mis_builder/__manifest__.py +2 -1
  3. odoo/addons/mis_builder/i18n/it.po +2 -2
  4. odoo/addons/mis_builder/i18n/mis_builder.pot +92 -0
  5. odoo/addons/mis_builder/models/__init__.py +1 -0
  6. odoo/addons/mis_builder/models/kpimatrix.py +39 -4
  7. odoo/addons/mis_builder/models/mis_report_instance.py +67 -1
  8. odoo/addons/mis_builder/models/mis_report_instance_annotation.py +113 -0
  9. odoo/addons/mis_builder/report/mis_report_instance_qweb.xml +23 -0
  10. odoo/addons/mis_builder/report/mis_report_instance_xlsx.py +11 -1
  11. odoo/addons/mis_builder/security/ir.model.access.csv +2 -0
  12. odoo/addons/mis_builder/security/res_groups.xml +17 -0
  13. odoo/addons/mis_builder/static/description/index.html +1 -1
  14. odoo/addons/mis_builder/static/src/components/mis_report_widget.css +32 -0
  15. odoo/addons/mis_builder/static/src/components/mis_report_widget.esm.js +83 -3
  16. odoo/addons/mis_builder/static/src/components/mis_report_widget.xml +94 -13
  17. odoo/addons/mis_builder/static/src/css/report.css +22 -0
  18. odoo/addons/mis_builder/tests/__init__.py +1 -0
  19. odoo/addons/mis_builder/tests/test_mis_report_instance_annotation.py +154 -0
  20. {odoo_addon_mis_builder-16.0.5.2.3.dist-info → odoo_addon_mis_builder-16.0.5.3.0.dist-info}/METADATA +2 -2
  21. {odoo_addon_mis_builder-16.0.5.2.3.dist-info → odoo_addon_mis_builder-16.0.5.3.0.dist-info}/RECORD +23 -20
  22. {odoo_addon_mis_builder-16.0.5.2.3.dist-info → odoo_addon_mis_builder-16.0.5.3.0.dist-info}/WHEEL +0 -0
  23. {odoo_addon_mis_builder-16.0.5.2.3.dist-info → odoo_addon_mis_builder-16.0.5.3.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:578ec6e1eb6754314b8a51c950070f53d28faebcd67c3d7014c0a53d82cca432
10
+ !! source digest: sha256:ce5435756f965a1651bfcff766931db176c696dbcedb0a99ce8e61f003c9b09d
11
11
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
12
12
 
13
13
  .. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png
@@ -3,7 +3,7 @@
3
3
 
4
4
  {
5
5
  "name": "MIS Builder",
6
- "version": "16.0.5.2.3",
6
+ "version": "16.0.5.3.0",
7
7
  "category": "Reporting",
8
8
  "summary": """
9
9
  Build 'Management Information System' Reports and Dashboards
@@ -17,6 +17,7 @@
17
17
  "date_range", # OCA/server-ux
18
18
  ],
19
19
  "data": [
20
+ "security/res_groups.xml",
20
21
  "wizard/mis_builder_dashboard.xml",
21
22
  "views/mis_report.xml",
22
23
  "views/mis_report_instance.xml",
@@ -6,7 +6,7 @@ msgid ""
6
6
  msgstr ""
7
7
  "Project-Id-Version: Odoo Server 10.0\n"
8
8
  "Report-Msgid-Bugs-To: \n"
9
- "PO-Revision-Date: 2024-12-18 13:06+0000\n"
9
+ "PO-Revision-Date: 2025-01-27 12:06+0000\n"
10
10
  "Last-Translator: mymage <stefano.consolaro@mymage.it>\n"
11
11
  "Language-Team: none\n"
12
12
  "Language: it\n"
@@ -588,7 +588,7 @@ msgstr "Intervallo data"
588
588
  #. module: mis_builder
589
589
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period__date_range_type_id
590
590
  msgid "Date Range Type"
591
- msgstr "Tipo intervallo date"
591
+ msgstr "Tipo intervallo data"
592
592
 
593
593
  #. module: mis_builder
594
594
  #: model:ir.model.fields,field_description:mis_builder.field_prorata_read_group_mixin__date_to
@@ -13,6 +13,14 @@ msgstr ""
13
13
  "Content-Transfer-Encoding: \n"
14
14
  "Plural-Forms: \n"
15
15
 
16
+ #. module: mis_builder
17
+ #: model:ir.model.fields,help:mis_builder.field_mis_report_instance_annotation__annotation_context
18
+ msgid ""
19
+ "\n"
20
+ " Context used when adding annotation\n"
21
+ " "
22
+ msgstr ""
23
+
16
24
  #. module: mis_builder
17
25
  #. odoo-python
18
26
  #: code:addons/mis_builder/models/mis_report.py:0
@@ -292,6 +300,18 @@ msgstr ""
292
300
  msgid "Analytic Domain"
293
301
  msgstr ""
294
302
 
303
+ #. module: mis_builder
304
+ #. odoo-javascript
305
+ #: code:addons/mis_builder/static/src/components/mis_report_widget.xml:0
306
+ #, python-format
307
+ msgid "Annotate"
308
+ msgstr ""
309
+
310
+ #. module: mis_builder
311
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_annotation__annotation_context
312
+ msgid "Annotation Context"
313
+ msgstr ""
314
+
295
315
  #. module: mis_builder
296
316
  #: model_terms:ir.ui.view,arch_db:mis_builder.mis_report_view_kpi_form
297
317
  msgid "Auto expand"
@@ -333,8 +353,11 @@ msgid "Bold"
333
353
  msgstr ""
334
354
 
335
355
  #. module: mis_builder
356
+ #. odoo-javascript
357
+ #: code:addons/mis_builder/static/src/components/mis_report_widget.esm.js:0
336
358
  #: model_terms:ir.ui.view,arch_db:mis_builder.mis_report_instance_add_to_dashboard_form_view
337
359
  #: model_terms:ir.ui.view,arch_db:mis_builder.wizard_mis_report_instance_view_form
360
+ #, python-format
338
361
  msgid "Cancel"
339
362
  msgstr ""
340
363
 
@@ -426,6 +449,7 @@ msgstr ""
426
449
  #: model:ir.model.fields,field_description:mis_builder.field_add_mis_report_instance_dashboard_wizard__create_uid
427
450
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report__create_uid
428
451
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance__create_uid
452
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_annotation__create_uid
429
453
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period__create_uid
430
454
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period_sum__create_uid
431
455
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_kpi__create_uid
@@ -441,6 +465,7 @@ msgstr ""
441
465
  #: model:ir.model.fields,field_description:mis_builder.field_add_mis_report_instance_dashboard_wizard__create_date
442
466
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report__create_date
443
467
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance__create_date
468
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_annotation__create_date
444
469
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period__create_date
445
470
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period_sum__create_date
446
471
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_kpi__create_date
@@ -547,6 +572,7 @@ msgstr ""
547
572
  #: model:ir.model.fields,field_description:mis_builder.field_add_mis_report_instance_dashboard_wizard__display_name
548
573
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report__display_name
549
574
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance__display_name
575
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_annotation__display_name
550
576
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period__display_name
551
577
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period_sum__display_name
552
578
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_kpi__display_name
@@ -785,6 +811,7 @@ msgstr ""
785
811
  #: model:ir.model.fields,field_description:mis_builder.field_add_mis_report_instance_dashboard_wizard__id
786
812
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report__id
787
813
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance__id
814
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_annotation__id
788
815
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period__id
789
816
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period_sum__id
790
817
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_kpi__id
@@ -826,6 +853,13 @@ msgstr ""
826
853
  msgid "Indent level must be greater than or equal to 0"
827
854
  msgstr ""
828
855
 
856
+ #. module: mis_builder
857
+ #. odoo-javascript
858
+ #: code:addons/mis_builder/static/src/components/mis_report_widget.xml:0
859
+ #, python-format
860
+ msgid "Insert note here"
861
+ msgstr ""
862
+
829
863
  #. module: mis_builder
830
864
  #: model:ir.model.fields.selection,name:mis_builder.selection__mis_report_style__font_style__italic
831
865
  msgid "Italic"
@@ -882,6 +916,7 @@ msgid "KPIs of this report and subreports."
882
916
  msgstr ""
883
917
 
884
918
  #. module: mis_builder
919
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_annotation__kpi_id
885
920
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_kpi_expression__kpi_id
886
921
  msgid "Kpi"
887
922
  msgstr ""
@@ -900,6 +935,7 @@ msgstr ""
900
935
  #: model:ir.model.fields,field_description:mis_builder.field_add_mis_report_instance_dashboard_wizard____last_update
901
936
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report____last_update
902
937
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance____last_update
938
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_annotation____last_update
903
939
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period____last_update
904
940
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period_sum____last_update
905
941
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_kpi____last_update
@@ -921,6 +957,7 @@ msgstr ""
921
957
  #: model:ir.model.fields,field_description:mis_builder.field_add_mis_report_instance_dashboard_wizard__write_uid
922
958
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report__write_uid
923
959
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance__write_uid
960
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_annotation__write_uid
924
961
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period__write_uid
925
962
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period_sum__write_uid
926
963
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_kpi__write_uid
@@ -936,6 +973,7 @@ msgstr ""
936
973
  #: model:ir.model.fields,field_description:mis_builder.field_add_mis_report_instance_dashboard_wizard__write_date
937
974
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report__write_date
938
975
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance__write_date
976
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_annotation__write_date
939
977
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period__write_date
940
978
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period_sum__write_date
941
979
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_kpi__write_date
@@ -1042,6 +1080,16 @@ msgstr ""
1042
1080
  msgid "MIS Report Templates"
1043
1081
  msgstr ""
1044
1082
 
1083
+ #. module: mis_builder
1084
+ #: model:res.groups,name:mis_builder.group_edit_annotation
1085
+ msgid "MIS Report: add annotations"
1086
+ msgstr ""
1087
+
1088
+ #. module: mis_builder
1089
+ #: model:res.groups,name:mis_builder.group_read_annotation
1090
+ msgid "MIS Report: view annotations"
1091
+ msgstr ""
1092
+
1045
1093
  #. module: mis_builder
1046
1094
  #: model:ir.ui.menu,name:mis_builder.mis_report_conf_menu
1047
1095
  #: model:ir.ui.menu,name:mis_builder.mis_report_finance_menu
@@ -1080,6 +1128,11 @@ msgstr ""
1080
1128
  msgid "Min"
1081
1129
  msgstr ""
1082
1130
 
1131
+ #. module: mis_builder
1132
+ #: model:ir.model,name:mis_builder.model_mis_report_instance_annotation
1133
+ msgid "Mis Report Instance Annotation"
1134
+ msgstr ""
1135
+
1083
1136
  #. module: mis_builder
1084
1137
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period__mode
1085
1138
  msgid "Mode"
@@ -1167,6 +1220,11 @@ msgstr ""
1167
1220
  msgid "Normal"
1168
1221
  msgstr ""
1169
1222
 
1223
+ #. module: mis_builder
1224
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_annotation__note
1225
+ msgid "Note"
1226
+ msgstr ""
1227
+
1170
1228
  #. module: mis_builder
1171
1229
  #: model_terms:ir.ui.view,arch_db:mis_builder.mis_report_style_view_form
1172
1230
  msgid "Number"
@@ -1210,6 +1268,11 @@ msgstr ""
1210
1268
  msgid "Percentage"
1211
1269
  msgstr ""
1212
1270
 
1271
+ #. module: mis_builder
1272
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_annotation__period_id
1273
+ msgid "Period"
1274
+ msgstr ""
1275
+
1213
1276
  #. module: mis_builder
1214
1277
  #: model:ir.model.constraint,message:mis_builder.constraint_mis_report_instance_period_name_unique
1215
1278
  msgid "Period name should be unique by report"
@@ -1298,6 +1361,13 @@ msgstr ""
1298
1361
  msgid "Relative to report base date"
1299
1362
  msgstr ""
1300
1363
 
1364
+ #. module: mis_builder
1365
+ #. odoo-javascript
1366
+ #: code:addons/mis_builder/static/src/components/mis_report_widget.esm.js:0
1367
+ #, python-format
1368
+ msgid "Remove"
1369
+ msgstr ""
1370
+
1301
1371
  #. module: mis_builder
1302
1372
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance__report_id
1303
1373
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period__report_id
@@ -1334,8 +1404,11 @@ msgid "Rounding inherit"
1334
1404
  msgstr ""
1335
1405
 
1336
1406
  #. module: mis_builder
1407
+ #. odoo-javascript
1408
+ #: code:addons/mis_builder/static/src/components/mis_report_widget.esm.js:0
1337
1409
  #: model_terms:ir.ui.view,arch_db:mis_builder.mis_report_instance_view_form
1338
1410
  #: model_terms:ir.ui.view,arch_db:mis_builder.wizard_mis_report_instance_view_form
1411
+ #, python-format
1339
1412
  msgid "Save"
1340
1413
  msgstr ""
1341
1414
 
@@ -1497,6 +1570,7 @@ msgid "Sub-KPI name ({}) must be a valid python identifier"
1497
1570
  msgstr ""
1498
1571
 
1499
1572
  #. module: mis_builder
1573
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_annotation__subkpi_id
1500
1574
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_kpi_expression__subkpi_id
1501
1575
  msgid "Subkpi"
1502
1576
  msgstr ""
@@ -1646,6 +1720,16 @@ msgstr ""
1646
1720
  msgid "Unsupported operator %s for searching on date"
1647
1721
  msgstr ""
1648
1722
 
1723
+ #. module: mis_builder
1724
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance__user_can_edit_annotation
1725
+ msgid "User Can Edit Annotation"
1726
+ msgstr ""
1727
+
1728
+ #. module: mis_builder
1729
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance__user_can_read_annotation
1730
+ msgid "User Can Read Annotation"
1731
+ msgstr ""
1732
+
1649
1733
  #. module: mis_builder
1650
1734
  #: model:ir.actions.server,name:mis_builder.ir_cron_vacuum_temp_reports_ir_actions_server
1651
1735
  #: model:ir.cron,cron_name:mis_builder.ir_cron_vacuum_temp_reports
@@ -1710,6 +1794,14 @@ msgstr ""
1710
1794
  msgid "You cannot sum period %s with itself."
1711
1795
  msgstr ""
1712
1796
 
1797
+ #. module: mis_builder
1798
+ #. odoo-python
1799
+ #: code:addons/mis_builder/models/mis_report_instance_annotation.py:0
1800
+ #: code:addons/mis_builder/models/mis_report_instance_annotation.py:0
1801
+ #, python-format
1802
+ msgid "You do not have the rights to edit annotations"
1803
+ msgstr ""
1804
+
1713
1805
  #. module: mis_builder
1714
1806
  #. odoo-python
1715
1807
  #: code:addons/mis_builder/models/aep.py:0
@@ -8,3 +8,4 @@ from . import mis_report_style
8
8
  from . import aep
9
9
  from . import mis_kpi_data
10
10
  from . import prorata_read_group_mixin
11
+ from . import mis_report_instance_annotation
@@ -45,10 +45,7 @@ class KpiMatrixRow:
45
45
 
46
46
  @property
47
47
  def row_id(self):
48
- if not self.account_id:
49
- return self.kpi.name
50
- else:
51
- return f"{self.kpi.name}:{self.account_id}"
48
+ self._matrix._make_row_id(self.kpi.id, self.account_id)
52
49
 
53
50
  def iter_cell_tuples(self, cols=None):
54
51
  if cols is None:
@@ -143,6 +140,7 @@ class KpiMatrixCell: # noqa: B903 (immutable data class)
143
140
  self.style_props = style_props
144
141
  self.drilldown_arg = drilldown_arg
145
142
  self.val_type = val_type
143
+ self.cell_id = KpiMatrix._pack_cell_id(self)
146
144
 
147
145
 
148
146
  class KpiMatrix:
@@ -524,12 +522,15 @@ class KpiMatrix:
524
522
  else:
525
523
  val = cell.val
526
524
  col_data = {
525
+ "cell_id": cell.cell_id,
527
526
  "val": val,
528
527
  "val_r": cell.val_rendered,
529
528
  "val_c": cell.val_comment,
530
529
  "style": self._style_model.to_css_style(
531
530
  cell.style_props, no_indent=True
532
531
  ),
532
+ # notes can not be added on 'details by account' lines
533
+ "can_be_annotated": not cell.row.account_id,
533
534
  }
534
535
  if cell.drilldown_arg:
535
536
  col_data["drilldown_arg"] = cell.drilldown_arg
@@ -537,3 +538,37 @@ class KpiMatrix:
537
538
  body.append(row_data)
538
539
 
539
540
  return {"header": header, "body": body}
541
+
542
+ # Logic to convert semantic coordinates (period, kpi, subkpi)
543
+ # to visual coordinates (cell id) and back. The rendering logic musn't know
544
+ # about semantic concepts such as periods and kpis. Having these well identified
545
+ # methods allow us to easily spot where the conversion between the rendering and
546
+ # semantic domain occur.
547
+
548
+ @classmethod
549
+ def _make_row_id(cls, kpi_id: int, account_id: int | None) -> str:
550
+ return f"{kpi_id}:{account_id or ''}"
551
+
552
+ @classmethod
553
+ def _make_cell_id(
554
+ cls, kpi_id: int, account_id: int | None, period_id: int, subkpi_id: int | None
555
+ ) -> str:
556
+ return f"{kpi_id}#{account_id or ''}#{period_id}#{subkpi_id or ''}"
557
+
558
+ @classmethod
559
+ def _pack_cell_id(cls, cell: KpiMatrixCell) -> str:
560
+ return cls._make_cell_id(
561
+ cell.row.kpi.id,
562
+ cell.row.account_id,
563
+ cell.subcol.col.key,
564
+ cell.subcol.subkpi and cell.subcol.subkpi.id,
565
+ )
566
+
567
+ @classmethod
568
+ def _unpack_cell_id(cls, cell_id: str) -> tuple[int, int | None, int, int | None]:
569
+ kpi_id, account_id, col_key, subkpi_id = cell_id.split("#")
570
+ kpi_id = int(kpi_id)
571
+ account_id = int(account_id) if account_id else None
572
+ period_id = int(col_key)
573
+ subkpi_id = int(subkpi_id) if subkpi_id else None
574
+ return kpi_id, account_id, period_id, subkpi_id
@@ -13,6 +13,7 @@ from odoo.exceptions import UserError, ValidationError
13
13
 
14
14
  from .aep import AccountingExpressionProcessor as AEP
15
15
  from .expression_evaluator import ExpressionEvaluator
16
+ from .kpimatrix import KpiMatrix
16
17
 
17
18
  _logger = logging.getLogger(__name__)
18
19
 
@@ -580,6 +581,12 @@ class MisReportInstance(models.Model):
580
581
  string="Filter box search view",
581
582
  help="Search view to customize the filter box in the report widget.",
582
583
  )
584
+ user_can_read_annotation = fields.Boolean(
585
+ compute="_compute_user_can_read_annotation",
586
+ )
587
+ user_can_edit_annotation = fields.Boolean(
588
+ compute="_compute_user_can_edit_annotation",
589
+ )
583
590
 
584
591
  @api.depends("report_id.move_lines_source")
585
592
  def _compute_widget_search_view_id(self):
@@ -877,7 +884,44 @@ class MisReportInstance(models.Model):
877
884
  def compute(self):
878
885
  self.ensure_one()
879
886
  kpi_matrix = self._compute_matrix()
880
- return kpi_matrix.as_dict()
887
+ ret = kpi_matrix.as_dict()
888
+
889
+ ret["notes"] = self.get_notes_by_cell_id()
890
+ return ret
891
+
892
+ def get_notes_by_cell_id(self) -> dict:
893
+ self.ensure_one()
894
+ if not self.user_can_read_annotation:
895
+ return {}
896
+
897
+ annotations = self.env["mis.report.instance.annotation"].search(
898
+ [
899
+ ("period_id", "in", self.period_ids.ids),
900
+ ]
901
+ )
902
+ annotation_context = self._get_annotation_context()
903
+ annotations = annotations.filtered(
904
+ lambda rec: rec.annotation_context == annotation_context
905
+ )
906
+
907
+ annotations_sorted = sorted(
908
+ annotations,
909
+ key=lambda r: (
910
+ r.kpi_id.sequence,
911
+ r.period_id.sequence,
912
+ r.subkpi_id.sequence,
913
+ ),
914
+ )
915
+
916
+ return {
917
+ KpiMatrix._make_cell_id(
918
+ annotation.kpi_id.id,
919
+ False,
920
+ annotation.period_id.id,
921
+ annotation.subkpi_id and annotation.subkpi_id.id,
922
+ ): {"text": annotation.note, "sequence": sequence}
923
+ for sequence, annotation in enumerate(annotations_sorted, 1)
924
+ }
881
925
 
882
926
  @api.model
883
927
  def _get_drilldown_views_and_orders(self):
@@ -940,3 +984,25 @@ class MisReportInstance(models.Model):
940
984
  return f"{kpi.description} - {account.display_name} - {period.display_name}"
941
985
  else:
942
986
  return f"{kpi.description} - {period.display_name}"
987
+
988
+ def _get_annotation_context(self):
989
+ """Return the context used to filter annotation linked to this instance."""
990
+ self.ensure_one()
991
+ annotation_context = {}
992
+ if query_company_ids := self.query_company_ids.ids:
993
+ # sort ids to make the comparaison easier
994
+ annotation_context["query_company_ids"] = sorted(query_company_ids)
995
+
996
+ return annotation_context
997
+
998
+ @api.depends_context("uid")
999
+ def _compute_user_can_read_annotation(self):
1000
+ self.user_can_read_annotation = self.env.user.has_group(
1001
+ "mis_builder.group_read_annotation"
1002
+ )
1003
+
1004
+ @api.depends_context("uid")
1005
+ def _compute_user_can_edit_annotation(self):
1006
+ self.user_can_edit_annotation = self.env.user.has_group(
1007
+ "mis_builder.group_edit_annotation"
1008
+ )
@@ -0,0 +1,113 @@
1
+ # Copyright 2025 ACSONE SA/NV
2
+ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
3
+
4
+
5
+ from odoo import _, api, fields, models
6
+ from odoo.exceptions import AccessError
7
+
8
+ from .kpimatrix import KpiMatrix
9
+
10
+
11
+ class MisReportInstanceAnnotation(models.Model):
12
+ _name = "mis.report.instance.annotation"
13
+ _description = "Mis Report Instance Annotation"
14
+
15
+ period_id = fields.Many2one(
16
+ comodel_name="mis.report.instance.period",
17
+ ondelete="cascade",
18
+ required=True,
19
+ )
20
+ kpi_id = fields.Many2one(
21
+ comodel_name="mis.report.kpi",
22
+ ondelete="cascade",
23
+ required=True,
24
+ )
25
+ subkpi_id = fields.Many2one(
26
+ comodel_name="mis.report.subkpi",
27
+ ondelete="cascade",
28
+ )
29
+ note = fields.Char()
30
+ annotation_context = fields.Json(
31
+ help="""
32
+ Context used when adding annotation
33
+ """
34
+ )
35
+
36
+ def init(self):
37
+ self.env.cr.execute(
38
+ """
39
+ CREATE INDEX IF NOT EXISTS
40
+ mis_report_instance_annotation_period_id_kpi_id_subkpi_id_idx
41
+ ON mis_report_instance_annotation(period_id,kpi_id,subkpi_id);
42
+ """
43
+ )
44
+
45
+ @api.model
46
+ def _get_first_matching_annotation(self, cell_id, instance_id):
47
+ """
48
+ Return first annoation
49
+ matching exactly the period,kpi,subkpi and annotation context
50
+ """
51
+
52
+ kpi_id, _, period_id, subkpi_id = KpiMatrix._unpack_cell_id(cell_id)
53
+
54
+ annotations = self.env["mis.report.instance.annotation"].search(
55
+ [
56
+ ("period_id", "=", period_id),
57
+ ("kpi_id", "=", kpi_id),
58
+ ("subkpi_id", "=", subkpi_id),
59
+ ],
60
+ )
61
+ annotation_context = (
62
+ self.env["mis.report.instance"]
63
+ .browse(instance_id)
64
+ ._get_annotation_context()
65
+ )
66
+ annotation = fields.first(
67
+ annotations.filtered(
68
+ lambda rec: rec.annotation_context == annotation_context
69
+ )
70
+ )
71
+ return annotation
72
+
73
+ @api.model
74
+ def set_annotation(self, cell_id, instance_id, note):
75
+ if (
76
+ not self.env["mis.report.instance"]
77
+ .browse(instance_id)
78
+ .user_can_edit_annotation
79
+ ):
80
+ raise AccessError(_("You do not have the rights to edit annotations"))
81
+
82
+ annotation = self._get_first_matching_annotation(cell_id, instance_id)
83
+
84
+ if annotation:
85
+ annotation.note = note
86
+ else:
87
+ kpi_id, _account_id, period_id, subkpi_id = KpiMatrix._unpack_cell_id(
88
+ cell_id
89
+ )
90
+ self.env["mis.report.instance.annotation"].create(
91
+ {
92
+ "period_id": period_id,
93
+ "kpi_id": kpi_id,
94
+ "subkpi_id": subkpi_id,
95
+ "note": note,
96
+ "annotation_context": self.env["mis.report.instance"]
97
+ .browse(instance_id)
98
+ ._get_annotation_context(),
99
+ }
100
+ )
101
+
102
+ @api.model
103
+ def remove_annotation(self, cell_id, instance_id):
104
+ if (
105
+ not self.env["mis.report.instance"]
106
+ .browse(instance_id)
107
+ .user_can_edit_annotation
108
+ ):
109
+ raise AccessError(_("You do not have the rights to edit annotations"))
110
+
111
+ annotation = self._get_first_matching_annotation(cell_id, instance_id)
112
+ if annotation:
113
+ annotation.unlink()
@@ -17,6 +17,7 @@
17
17
  <t t-foreach="docs" t-as="o">
18
18
  <t t-call="web.internal_layout">
19
19
  <t t-set="matrix" t-value="o._compute_matrix()" />
20
+ <t t-set="notes" t-value="o.get_notes_by_cell_id()" />
20
21
  <t t-set="style_obj" t-value="o.env['mis.report.style']" />
21
22
  <div class="page">
22
23
  <h3>
@@ -97,12 +98,34 @@
97
98
  <t
98
99
  t-out="cell and cell.val_rendered or ''"
99
100
  />
101
+ <span
102
+ class="oe_mis_builder_footnote"
103
+ t-if="cell"
104
+ >
105
+ <t
106
+ t-out="notes.get(cell.cell_id,{}).get('sequence','')"
107
+ />
108
+ </span>
100
109
  </div>
101
110
  </t>
102
111
  </div>
103
112
  </t>
104
113
  </div>
105
114
  </div>
115
+ <!-- Foot notes -->
116
+ <div class="oe_mis_builder_footnote_div">
117
+ <table class="oe_mis_builder_footnote_table">
118
+ <t
119
+ t-foreach="sorted(notes.values(),key=lambda r:r['sequence'])"
120
+ t-as="note"
121
+ >
122
+ <tr>
123
+ <td><t t-out="note['sequence']" />. </td>
124
+ <td><t t-out="note['text']" /></td>
125
+ </tr>
126
+ </t>
127
+ </table>
128
+ </div>
106
129
  </div>
107
130
  </t>
108
131
  </t>
@@ -6,7 +6,7 @@ import numbers
6
6
  from collections import defaultdict
7
7
  from datetime import datetime
8
8
 
9
- from odoo import _, fields, models
9
+ from odoo import _, api, fields, models
10
10
 
11
11
  from ..models.accounting_none import AccountingNone
12
12
  from ..models.data_error import DataError
@@ -26,9 +26,18 @@ class MisBuilderXlsx(models.AbstractModel):
26
26
  _description = "MIS Builder XLSX report"
27
27
  _inherit = "report.report_xlsx.abstract"
28
28
 
29
+ @api.model
30
+ def _mis_builder_add_annotation(self, sheet, cell, row_pos, col_pos, notes):
31
+ """
32
+ Add anotation as a comment on cell in .xls
33
+ """
34
+ if cell and (annotation := notes.get(cell.cell_id, {}).get("text")):
35
+ sheet.write_comment(row_pos, col_pos, annotation)
36
+
29
37
  def generate_xlsx_report(self, workbook, data, objects):
30
38
  # get the computed result of the report
31
39
  matrix = objects._compute_matrix()
40
+ notes = objects.get_notes_by_cell_id()
32
41
  style_obj = self.env["mis.report.style"]
33
42
 
34
43
  # create worksheet
@@ -120,6 +129,7 @@ class MisBuilderXlsx(models.AbstractModel):
120
129
  )
121
130
  for cell in row.iter_cells():
122
131
  col_pos += 1
132
+ self._mis_builder_add_annotation(sheet, cell, row_pos, col_pos, notes)
123
133
  if not cell or cell.val is AccountingNone:
124
134
  # TODO col/subcol format
125
135
  sheet.write(row_pos, col_pos, "", row_format)
@@ -20,3 +20,5 @@ access_mis_report_subreport,access_mis_report_subreport,model_mis_report_subrepo
20
20
  manage_mis_report_style,access_mis_report_style,model_mis_report_style,account.group_account_manager,1,1,1,1
21
21
  access_mis_report_style,access_mis_report_style,model_mis_report_style,base.group_user,1,0,0,0
22
22
  access_add_to_dashboard_wizard,access_add_to_dashboard_wizard,model_add_mis_report_instance_dashboard_wizard,base.group_user,1,1,1,0
23
+ access_read_mis_report_annotation, access_read_mis_report_annotation,model_mis_report_instance_annotation,mis_builder.group_read_annotation,1,0,0,0
24
+ access_edit_mis_report_annotation, access_edit_mis_report_annotation,model_mis_report_instance_annotation,mis_builder.group_edit_annotation,1,1,1,1
@@ -0,0 +1,17 @@
1
+ <?xml version="1.0" encoding="utf-8" ?>
2
+ <odoo>
3
+ <record model="res.groups" id="group_read_annotation">
4
+ <field name="name">MIS Report: view annotations</field>
5
+ </record>
6
+ <record model="res.groups" id="group_edit_annotation">
7
+ <field name="name">MIS Report: add annotations</field>
8
+ <field
9
+ name="implied_ids"
10
+ eval="[Command.link(ref('mis_builder.group_read_annotation'))]"
11
+ />
12
+ <field
13
+ name="users"
14
+ eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"
15
+ />
16
+ </record>
17
+ </odoo>
@@ -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:578ec6e1eb6754314b8a51c950070f53d28faebcd67c3d7014c0a53d82cca432
370
+ !! source digest: sha256:ce5435756f965a1651bfcff766931db176c696dbcedb0a99ce8e61f003c9b09d
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&amp;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.
@@ -65,3 +65,35 @@
65
65
  flex-grow: 1;
66
66
  justify-content: flex-end;
67
67
  }
68
+
69
+ .oe_mis_builder_dropdown {
70
+ overflow: visible !important;
71
+ }
72
+
73
+ .oe_mis_builder_footnote {
74
+ font-size: 80%;
75
+ color: red;
76
+ position: relative;
77
+ bottom: 1ex;
78
+ width: 1em;
79
+ display: inline-block;
80
+ padding-right: 1px;
81
+ }
82
+
83
+ .oe_mis_builder_footnote_table {
84
+ list-style: none;
85
+ white-space: pre-wrap;
86
+ display: inline-block;
87
+
88
+ td {
89
+ vertical-align: top;
90
+ }
91
+ }
92
+
93
+ .oe_mis_builder_footnote_div {
94
+ padding-top: 1em;
95
+ }
96
+
97
+ .oe_mis_builder_menu_disabled {
98
+ color: gainsboro;
99
+ }
@@ -1,13 +1,15 @@
1
1
  /** @odoo-module **/
2
2
 
3
+ import Dialog from "web.Dialog";
3
4
  import {Component, onWillStart, useState, useSubEnv} from "@odoo/owl";
4
- import {useBus, useService} from "@web/core/utils/hooks";
5
5
  import {DatePicker} from "@web/core/datepicker/datepicker";
6
6
  import {FilterMenu} from "@web/search/filter_menu/filter_menu";
7
7
  import {SearchBar} from "@web/search/search_bar/search_bar";
8
8
  import {SearchModel} from "@web/search/search_model";
9
9
  import {parseDate} from "@web/core/l10n/dates";
10
+ import {qweb} from "web.core";
10
11
  import {registry} from "@web/core/registry";
12
+ import {useBus, useService} from "@web/core/utils/hooks";
11
13
 
12
14
  export class MisReportWidget extends Component {
13
15
  setup() {
@@ -18,8 +20,10 @@ export class MisReportWidget extends Component {
18
20
  this.view = useService("view");
19
21
  this.JSON = JSON;
20
22
  this.state = useState({
21
- mis_report_data: {header: [], body: []},
23
+ mis_report_data: {header: [], body: [], notes: {}},
22
24
  pivot_date: null,
25
+ can_edit_annotation: false,
26
+ can_read_annotation: false,
23
27
  });
24
28
  this.searchModel = new SearchModel(this.env, {
25
29
  user: this.user,
@@ -46,6 +50,8 @@ export class MisReportWidget extends Component {
46
50
  "widget_search_view_id",
47
51
  "pivot_date",
48
52
  "widget_show_pivot_date",
53
+ "user_can_read_annotation",
54
+ "user_can_edit_annotation",
49
55
  ],
50
56
  {context: this.context}
51
57
  );
@@ -66,6 +72,8 @@ export class MisReportWidget extends Component {
66
72
 
67
73
  // Compute the report
68
74
  this.refresh();
75
+ this.state.can_edit_annotation = result.user_can_edit_annotation;
76
+ this.state.can_read_annotation = result.user_can_read_annotation;
69
77
  }
70
78
 
71
79
  get showSearchBar() {
@@ -121,7 +129,7 @@ export class MisReportWidget extends Component {
121
129
  }
122
130
 
123
131
  async drilldown(event) {
124
- const drilldown = $(event.target).data("drilldown");
132
+ const drilldown = JSON.parse(event.target.dataset.drilldown);
125
133
  const action = await this.orm.call(
126
134
  "mis.report.instance",
127
135
  "drilldown",
@@ -140,6 +148,15 @@ export class MisReportWidget extends Component {
140
148
  );
141
149
  }
142
150
 
151
+ async refresh_annotation() {
152
+ this.state.mis_report_data.notes = await this.orm.call(
153
+ "mis.report.instance",
154
+ "get_notes_by_cell_id",
155
+ [this._instanceId()],
156
+ {context: this.context}
157
+ );
158
+ }
159
+
143
160
  async printPdf() {
144
161
  const action = await this.orm.call(
145
162
  "mis.report.instance",
@@ -170,6 +187,69 @@ export class MisReportWidget extends Component {
170
187
  this.action.doAction(action);
171
188
  }
172
189
 
190
+ async _remove_annotation(cell_id) {
191
+ await this.orm.call(
192
+ "mis.report.instance.annotation",
193
+ "remove_annotation",
194
+ [cell_id, this._instanceId()],
195
+ {context: this.context}
196
+ );
197
+ this.refresh_annotation();
198
+ }
199
+
200
+ async _save_annotation(cell_id) {
201
+ const text = document.querySelector(".o_mis_builder_annotation_text").value;
202
+ await this.orm.call(
203
+ "mis.report.instance.annotation",
204
+ "set_annotation",
205
+ [cell_id, this._instanceId(), text],
206
+ {context: this.context}
207
+ );
208
+ await this.refresh_annotation();
209
+ }
210
+
211
+ async annotate(event) {
212
+ const cell_id = event.target.dataset.cellId;
213
+ const note = this.state.mis_report_data.notes[cell_id];
214
+ const note_text = (note && note.text) || "";
215
+ var buttons = [
216
+ {
217
+ text: this.env._t("Save"),
218
+ classes: "btn-primary",
219
+ close: true,
220
+ click: this._save_annotation.bind(this, cell_id),
221
+ },
222
+ {
223
+ text: this.env._t("Cancel"),
224
+ close: true,
225
+ },
226
+ ];
227
+ if (typeof note !== "undefined") {
228
+ buttons.push({
229
+ text: this.env._t("Remove"),
230
+ classes: "btn-secondary",
231
+ close: true,
232
+ click: this._remove_annotation.bind(this, cell_id),
233
+ });
234
+ }
235
+
236
+ new Dialog(this, {
237
+ title: "Annotate",
238
+ size: "medium",
239
+ $content: $(
240
+ qweb.render("mis_builder.annotation_dialog", {
241
+ text: note_text,
242
+ })
243
+ ),
244
+ buttons: buttons,
245
+ }).open();
246
+ }
247
+
248
+ async remove_annotation(event) {
249
+ const cell_id = event.target.dataset.cellId;
250
+ this._remove_annotation(cell_id);
251
+ }
252
+
173
253
  onDateTimeChanged(ev) {
174
254
  this.state.pivot_date = ev;
175
255
  this.refresh();
@@ -4,6 +4,7 @@
4
4
  <t t-name="mis_builder.MisReportWidget" owl="1">
5
5
  <div class="oe_mis_builder_content">
6
6
  <t t-if="state.mis_report_data">
7
+ <t t-set="notes" t-value="state.mis_report_data.notes" />
7
8
  <div class="oe_mis_builder_cp">
8
9
  <div class="oe_mis_builder_cp_left">
9
10
  </div>
@@ -85,21 +86,65 @@
85
86
  t-as="cell"
86
87
  t-key="cell_index"
87
88
  t-att="{'style': cell.style, 'title': cell.val_c}"
88
- class="mis_builder_amount"
89
+ class="mis_builder_amount oe_mis_builder_dropdown"
89
90
  >
90
- <t t-if="cell.drilldown_arg">
91
- <a
92
- href="javascript:void(0)"
93
- class="mis_builder_drilldown"
94
- t-on-click="drilldown"
95
- t-att-data-drilldown="JSON.stringify(cell.drilldown_arg)"
96
- >
91
+ <div>
92
+ <t t-if="cell.drilldown_arg">
93
+ <a
94
+ href="javascript:void(0)"
95
+ class="mis_builder_drilldown"
96
+ t-on-click="drilldown"
97
+ t-att-data-drilldown="JSON.stringify(cell.drilldown_arg)"
98
+ >
99
+ <t t-esc="cell.val_r" />
100
+ </a>
101
+ </t>
102
+ <t t-else="">
97
103
  <t t-esc="cell.val_r" />
98
- </a>
99
- </t>
100
- <t t-if="!cell.drilldown_arg">
101
- <t t-esc="cell.val_r" />
102
- </t>
104
+ </t>
105
+ <span class="oe_mis_builder_footnote">
106
+ <div t-if="notes[cell.cell_id]">
107
+ <a
108
+ t-att-id="'note_'+notes[cell.cell_id].sequence"
109
+ t-out="notes[cell.cell_id] and notes[cell.cell_id].sequence"
110
+ t-att="{'title': notes[cell.cell_id].text}"
111
+ href="#footnotes"
112
+ />
113
+ </div>
114
+ </span>
115
+
116
+ <div id="dropdown_menu" class="btn-group">
117
+ <div
118
+ class="dropdown"
119
+ t-if="state.can_edit_annotation and cell.can_be_annotated"
120
+ >
121
+ <div
122
+ data-bs-toggle="dropdown"
123
+ t-attf-class="dropdown-toggle"
124
+ />
125
+
126
+ <div
127
+ class="dropdown-menu o_filter_menu"
128
+ role="menu"
129
+ >
130
+ <a
131
+ href="javascript:void(0)"
132
+ t-on-click="annotate"
133
+ t-att-data-cell-id="cell.cell_id"
134
+ role="menuitem"
135
+ class="dropdown-item js_tag"
136
+ >
137
+ Annotate
138
+ </a>
139
+ </div>
140
+ </div>
141
+ <!-- show menu as disabled -->
142
+ <div
143
+ t-else=""
144
+ class="dropdown-toggle oe_mis_builder_menu_disabled"
145
+ />
146
+ </div>
147
+ </div>
103
148
  </td>
104
149
  </tr>
105
150
  </tbody>
@@ -108,8 +153,44 @@
108
153
  </tfoot>
109
154
  </table>
110
155
  </div>
156
+ <!-- Adding notes -->
157
+ <div class="oe_mis_builder_footnote_div" id="footnotes">
158
+ <table class="oe_mis_builder_footnote_table">
159
+ <t
160
+ t-foreach="state.mis_report_data.notes"
161
+ t-as="cell_id"
162
+ t-key="cell_id"
163
+ >
164
+ <tr>
165
+ <td><a
166
+ t-out="notes[cell_id].sequence"
167
+ t-att-href="'#note_'+notes[cell_id].sequence"
168
+ />. </td>
169
+ <td><t t-out="notes[cell_id].text" /></td>
170
+ <td><i
171
+ href="javascript:void(0)"
172
+ t-on-click="remove_annotation"
173
+ t-att-data-cell-id="cell_id"
174
+ class="btn fa fa-trash-o"
175
+ t-if="state.can_edit_annotation"
176
+ /></td>
177
+ </tr>
178
+ </t>
179
+ </table>
180
+ </div>
111
181
  </t>
112
182
  </div>
113
183
  </t>
114
184
 
185
+ <t t-name="mis_builder.annotation_dialog">
186
+ <form role="form">
187
+ <textarea
188
+ class="o_mis_builder_annotation_text"
189
+ name="note"
190
+ rows='4'
191
+ placeholder="Insert note here"
192
+ ><t t-out="text" t-att-data-textnote="text" /></textarea>
193
+ </form>
194
+ </t>
195
+
115
196
  </templates>
@@ -44,3 +44,25 @@
44
44
  .mis_table .mis_cell.mis_amount {
45
45
  text-align: right;
46
46
  }
47
+ .oe_mis_builder_footnote {
48
+ font-size: 70%;
49
+ color: red;
50
+ position: relative;
51
+ bottom: 1ex;
52
+ width: 1em;
53
+ display: inline-block;
54
+ padding-right: 1px;
55
+ }
56
+ .oe_mis_builder_footnote_div {
57
+ padding-top: 1em;
58
+ }
59
+
60
+ .oe_mis_builder_footnote_table {
61
+ list-style: none;
62
+ white-space: pre-wrap;
63
+ display: inline-block;
64
+
65
+ td {
66
+ vertical-align: top;
67
+ }
68
+ }
@@ -14,3 +14,4 @@ from . import test_render
14
14
  from . import test_simple_array
15
15
  from . import test_target_move
16
16
  from . import test_utc_midnight
17
+ from . import test_mis_report_instance_annotation
@@ -0,0 +1,154 @@
1
+ # Copyright 2025 ACSONE SA/NV
2
+ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
3
+
4
+ from odoo import Command
5
+ from odoo.tests.common import TransactionCase
6
+
7
+
8
+ class TestMisReportInstanceAnnotation(TransactionCase):
9
+ def setUp(self):
10
+ super().setUp()
11
+ self.report = self.env["mis.report"].create(
12
+ dict(
13
+ name="test report",
14
+ subkpi_ids=[
15
+ Command.create(
16
+ dict(
17
+ name="subkpi1_report2",
18
+ description="subkpi 1, report 2",
19
+ sequence=1,
20
+ )
21
+ ),
22
+ Command.create(
23
+ dict(
24
+ name="subkpi2_report2",
25
+ description="subkpi 2, report 2",
26
+ sequence=2,
27
+ ),
28
+ ),
29
+ ],
30
+ )
31
+ )
32
+
33
+ self.kpi = self.env["mis.report.kpi"].create(
34
+ dict(
35
+ report_id=self.report.id,
36
+ description="kpi 1",
37
+ name="k1",
38
+ multi=True,
39
+ expression_ids=[
40
+ Command.create(
41
+ dict(name="bale[200%]", subkpi_id=self.report.subkpi_ids[0].id),
42
+ ),
43
+ Command.create(
44
+ dict(name="balp[200%]", subkpi_id=self.report.subkpi_ids[1].id),
45
+ ),
46
+ ],
47
+ )
48
+ )
49
+
50
+ self.report_instance = self.env["mis.report.instance"].create(
51
+ dict(
52
+ name="test instance",
53
+ report_id=self.report.id,
54
+ company_id=self.env.ref("base.main_company").id,
55
+ period_ids=[
56
+ Command.create(
57
+ dict(
58
+ name="p1",
59
+ mode="fix",
60
+ manual_date_from="2013-01-01",
61
+ manual_date_to="2013-12-31",
62
+ sequence=1,
63
+ ),
64
+ ),
65
+ Command.create(
66
+ dict(
67
+ name="p2",
68
+ mode="fix",
69
+ manual_date_from="2014-01-01",
70
+ manual_date_to="2014-12-31",
71
+ sequence=2,
72
+ ),
73
+ ),
74
+ ],
75
+ )
76
+ )
77
+
78
+ def test_adding_note(self):
79
+ notes = self.report_instance.get_notes_by_cell_id()
80
+
81
+ self.assertEqual({}, notes)
82
+
83
+ # report with 4 cells, 2 periods and 2 subkpis
84
+ matrix = self.report_instance._compute_matrix()
85
+ cell_ids = [c.cell_id for row in matrix.iter_rows() for c in row.iter_cells()]
86
+ self.assertEqual(len(cell_ids), 4)
87
+
88
+ first_cell_id, second_cell_id, third_cell_id, _fourth_cell_id = cell_ids
89
+
90
+ # adding one note
91
+ self.env["mis.report.instance.annotation"].set_annotation(
92
+ first_cell_id, self.report_instance.id, "This is a note"
93
+ )
94
+ notes = self.report_instance.get_notes_by_cell_id()
95
+ self.assertDictEqual(
96
+ {first_cell_id: {"text": "This is a note", "sequence": 1}}, notes
97
+ )
98
+
99
+ # adding another note
100
+ self.env["mis.report.instance.annotation"].set_annotation(
101
+ third_cell_id, self.report_instance.id, "This is another note"
102
+ )
103
+ notes = self.report_instance.get_notes_by_cell_id()
104
+ self.assertDictEqual(
105
+ {
106
+ first_cell_id: {"text": "This is a note", "sequence": 1},
107
+ third_cell_id: {"text": "This is another note", "sequence": 2},
108
+ },
109
+ notes,
110
+ )
111
+
112
+ self.env["mis.report.instance.annotation"].set_annotation(
113
+ second_cell_id, self.report_instance.id, "This is third note"
114
+ )
115
+
116
+ notes = self.report_instance.get_notes_by_cell_id()
117
+ # Last note added should have a sequence of
118
+ # 2 since it is deplayed in the second cell
119
+ self.assertDictEqual(
120
+ {
121
+ first_cell_id: {"text": "This is a note", "sequence": 1},
122
+ second_cell_id: {"text": "This is third note", "sequence": 2},
123
+ third_cell_id: {"text": "This is another note", "sequence": 3},
124
+ },
125
+ notes,
126
+ )
127
+
128
+ def test_remove_note(self):
129
+ notes = self.report_instance.get_notes_by_cell_id()
130
+
131
+ self.assertEqual({}, notes)
132
+
133
+ # report with 4 cells, 2 periods and 2 subkpis
134
+ matrix = self.report_instance._compute_matrix()
135
+ cell_ids = [c.cell_id for row in matrix.iter_rows() for c in row.iter_cells()]
136
+ self.assertEqual(len(cell_ids), 4)
137
+
138
+ first_cell_id = cell_ids[0]
139
+
140
+ # adding one note
141
+ self.env["mis.report.instance.annotation"].set_annotation(
142
+ first_cell_id, self.report_instance.id, "This is a note"
143
+ )
144
+ notes = self.report_instance.get_notes_by_cell_id()
145
+ self.assertDictEqual(
146
+ {first_cell_id: {"text": "This is a note", "sequence": 1}}, notes
147
+ )
148
+
149
+ # remove note
150
+ self.env["mis.report.instance.annotation"].remove_annotation(
151
+ first_cell_id, self.report_instance.id
152
+ )
153
+ notes = self.report_instance.get_notes_by_cell_id()
154
+ self.assertEqual({}, notes)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: odoo-addon-mis_builder
3
- Version: 16.0.5.2.3
3
+ Version: 16.0.5.3.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:578ec6e1eb6754314b8a51c950070f53d28faebcd67c3d7014c0a53d82cca432
28
+ !! source digest: sha256:ce5435756f965a1651bfcff766931db176c696dbcedb0a99ce8e61f003c9b09d
29
29
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
30
30
 
31
31
  .. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png
@@ -1,6 +1,6 @@
1
- odoo/addons/mis_builder/README.rst,sha256=5gwn-IVwClTopE2sZ4-fshrDe35arxdVcjBvogSfmdA,29747
1
+ odoo/addons/mis_builder/README.rst,sha256=vD9IMU_ZgvijmmuWHq7fMwDgkha1csTL9vSlpVq6TDQ,29747
2
2
  odoo/addons/mis_builder/__init__.py,sha256=-8wG-57WxKfMMA5_TFzpubBcfthcsAP5vjTkhXKf7ds,185
3
- odoo/addons/mis_builder/__manifest__.py,sha256=LBnvlvY5dDYx_M74LIyLjSjGMUMsJdmX9US6M4ttnao,1556
3
+ odoo/addons/mis_builder/__manifest__.py,sha256=0yG-VBuTKtWqgltaDCRSGvz-XFozsleRMqigYogDuNo,1591
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=Bs3X0lNAg5u9K4j3FV6AfrtnWax3TahYE2qJE_ZxfMQ,91012
6
6
  odoo/addons/mis_builder/i18n/de.po,sha256=72QoPmri0QrOKlyON0yeHmlkbeiB7iuScqg7WOZZktg,66321
@@ -9,8 +9,8 @@ odoo/addons/mis_builder/i18n/el_GR.po,sha256=9Z0nBH1vT-DHlpBJAXHy_saAGjx4vjeZA1Y
9
9
  odoo/addons/mis_builder/i18n/es.po,sha256=ABKI06IcLNihS_yMcWnahn3ewfFdEml-pgvipb1Lpdo,88131
10
10
  odoo/addons/mis_builder/i18n/fr.po,sha256=4R-V6LFBJ-CJ-oFFC3gA3M-TVU7F59UWDyqvJDkzdac,84367
11
11
  odoo/addons/mis_builder/i18n/hr.po,sha256=0HSRENH2cNYTqLKEFi-5ZXlUPfA7-FlsBwqZs1gAAYk,68399
12
- odoo/addons/mis_builder/i18n/it.po,sha256=LGR0_CVwnaC-TseW1h3Q6ig4GXetoQ208-lT5l72Z-0,78909
13
- odoo/addons/mis_builder/i18n/mis_builder.pot,sha256=YAYf8d9o2-GtT8QzI8Qq-ZBtcP8wjVpj8uxZlkuFqtg,63164
12
+ odoo/addons/mis_builder/i18n/it.po,sha256=2kAHlHSa1Lwz1QmCbbzNumQYpCWhmaWSYmbrYLnSg9M,78909
13
+ odoo/addons/mis_builder/i18n/mis_builder.pot,sha256=YrzJvZAjN8ndgvW0pTw6CCpgVYgaUyhGIluLjodpN8k,66494
14
14
  odoo/addons/mis_builder/i18n/nl.po,sha256=iLVCTcly1KjsdpKbGPcZo_8j-rXKq_5HmT_GbOFkJag,75742
15
15
  odoo/addons/mis_builder/i18n/nl_NL.po,sha256=gkYGrBvveKcYLBVVKcdMPKFh80E-zTvNaRDgISRHNro,75834
16
16
  odoo/addons/mis_builder/i18n/pt.po,sha256=xU0QA-OajdZo21p_lGZSSPoyyFdVO7Tyc_9W7DCmBfo,65110
@@ -19,16 +19,17 @@ odoo/addons/mis_builder/i18n/sv.po,sha256=KvJGM3bbk5fkcQanHIkYUXgInUKAZuIJYH_dxo
19
19
  odoo/addons/mis_builder/i18n/tr.po,sha256=gzpQKxv6kF0jDC3Ejm2voQkXIRQgeVNeCvKJBdRF44Y,65266
20
20
  odoo/addons/mis_builder/migrations/16.0.1.0.1/post-migration.py,sha256=kSvTOKc5R8JGQtOAEGQV2pnwW3HHN9KkKf9KsCJXjaE,851
21
21
  odoo/addons/mis_builder/migrations/16.0.5.0.0/end-migrate.py,sha256=YquWWej_lW3mhkYTSQPqEC_BucmxtLRgwGWU1My7fo8,326
22
- odoo/addons/mis_builder/models/__init__.py,sha256=_h9tbt-H0JjtHL6kI7-wiEnd5u2qkP855DevyjxLBbI,331
22
+ odoo/addons/mis_builder/models/__init__.py,sha256=Ou1WincLWBDDFogq4SYTyrJUDwgBbaTEBpZ_WzYbgFI,376
23
23
  odoo/addons/mis_builder/models/accounting_none.py,sha256=6EE6x4slUbyP2TVnHpilsWu8XqWhGZMt79uBOmNLcVs,4156
24
24
  odoo/addons/mis_builder/models/aep.py,sha256=h2FmNgHRJqL8JK0GyhzS03z5yQ83Zo3vPFrKdPuQT8U,26542
25
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
- odoo/addons/mis_builder/models/kpimatrix.py,sha256=otTvqqvLOINVL1jbudn2PhvWwURDtmsoNN5TzM0hIwI,19812
28
+ odoo/addons/mis_builder/models/kpimatrix.py,sha256=NR3vUKj9Hzi2FVKyAsAC-038FiMN891SxAFo8OLBAAw,21372
29
29
  odoo/addons/mis_builder/models/mis_kpi_data.py,sha256=5lEEXn-j-xBmToJsluitpETQwrtRq5mfcFCv0xaXboM,4185
30
30
  odoo/addons/mis_builder/models/mis_report.py,sha256=Aet1YOlpadf3Cu9XoAnx8GI0kDa5UktI0U_mnj44jZ8,37775
31
- odoo/addons/mis_builder/models/mis_report_instance.py,sha256=zAP7KRsxuLgXSAP2ko-tYpOHMiIw5AKzepgE_bG1Nvc,36716
31
+ odoo/addons/mis_builder/models/mis_report_instance.py,sha256=wXYr6T9BpkBDteyGRMpzfHwtb0HO_RFIJbialQr2dAQ,38940
32
+ odoo/addons/mis_builder/models/mis_report_instance_annotation.py,sha256=NmBTU2sVx39vPmr_3Xv4rH6VwrXKadhWz-WBVYe0Ffc,3496
32
33
  odoo/addons/mis_builder/models/mis_report_style.py,sha256=wN4YVE7oFNXDSoYEIy5oA1qhfTJyJ9LlRHWhaVnQwMw,10668
33
34
  odoo/addons/mis_builder/models/mis_report_subreport.py,sha256=FAGXqbc2XWRAwnRNndBnyemdQE7UtUJ7akTJIn6DM7g,2102
34
35
  odoo/addons/mis_builder/models/mis_safe_eval.py,sha256=F2Xgtmy-gHDvstzq4_yi5BtdWBgyxjpta5fHG2NxRTI,1061
@@ -44,21 +45,22 @@ odoo/addons/mis_builder/readme/USAGE.rst,sha256=6LM-M2rlOhx92yghymhJuteAsmxpURqe
44
45
  odoo/addons/mis_builder/readme/newsfragments/.gitignore,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
46
  odoo/addons/mis_builder/report/__init__.py,sha256=pSBq9B467Ayg7O9UXh39Zt-B6Fpw7YBbOYkcA6q2avs,200
46
47
  odoo/addons/mis_builder/report/mis_report_instance_qweb.py,sha256=gbI3CoZnsqEEupc0UN874sA3JKjApjRmP5OdsQPSDgM,986
47
- odoo/addons/mis_builder/report/mis_report_instance_qweb.xml,sha256=h4YjiCn84TmwXL0IwHQl8Bo2FSowHJU_HzeM-5Aq9Jg,6020
48
- odoo/addons/mis_builder/report/mis_report_instance_xlsx.py,sha256=IFQfdjTVeI9RHPQiPgQ9kaA4scZNRYnX92NCM9KlpUo,6604
48
+ odoo/addons/mis_builder/report/mis_report_instance_qweb.xml,sha256=6uY04JHKy-IkUmVn_Kwc5CjQ5IHL4EN7fx0W7iMEcNQ,7368
49
+ odoo/addons/mis_builder/report/mis_report_instance_xlsx.py,sha256=nNH4H50RNXxlf-6XCocyOyOz96Cqg8qWTrT8vxT4Oq8,7054
49
50
  odoo/addons/mis_builder/report/mis_report_instance_xlsx.xml,sha256=mSf1LuG7o32zg3W5rVBSEWIZwGgORanvmkHEIPAil68,487
50
- odoo/addons/mis_builder/security/ir.model.access.csv,sha256=_b67Ttk2sg7Sr-qv9rgWP6jFW8FRjzcsKSGgf0TcmH4,2464
51
+ odoo/addons/mis_builder/security/ir.model.access.csv,sha256=n7MnRh8Rnwzf7a7OULSwtPwpl7D9XAt2-jzwiJl-gec,2760
51
52
  odoo/addons/mis_builder/security/mis_builder_security.xml,sha256=7ZMedlUf05UKm3bvXgjIxkcm68Jnic4rJdLQGMWNOqM,479
53
+ odoo/addons/mis_builder/security/res_groups.xml,sha256=IqTHCGCnXshI8yN_M79w4HR62TWxOYOnHmkK7lTwoBs,589
52
54
  odoo/addons/mis_builder/static/description/ex_report_preview.png,sha256=NKBRn70E4m7c8mN3ujhaTRWx8B6sXgZJ0LXHcjc7mFI,96172
53
55
  odoo/addons/mis_builder/static/description/ex_report_settings.png,sha256=T7j_3DUUtdZ6XKsAvJ3uKsK_-GtRl0sRPD68ZOa6ebA,103790
54
56
  odoo/addons/mis_builder/static/description/ex_report_template.png,sha256=c8KgzyGD5QiZNCmxaVR4u3h1K0TJN50uXZWoNsieN8A,100278
55
57
  odoo/addons/mis_builder/static/description/icon.png,sha256=0OCahdqDvaS_CHV97ZCbajW0_AcBLVVvja_EJA2mC6s,4770
56
- odoo/addons/mis_builder/static/description/index.html,sha256=2o4j4I0YkJGmjVxvtF64RFov4RdhjcX1aseJyHhJxE4,55964
57
- odoo/addons/mis_builder/static/src/components/mis_report_widget.css,sha256=ew7iozGAv9ue3KPf_8qA_AhHlNGis7pJu8A7tc6CO4Y,1198
58
- odoo/addons/mis_builder/static/src/components/mis_report_widget.esm.js,sha256=UzKMeuffo1BT5VmOi0Um9xtplbvdjcrc-_KOkZrTc_A,5528
59
- odoo/addons/mis_builder/static/src/components/mis_report_widget.xml,sha256=eKbIbOWmsc_qdT_zYuFdqxZIYpf6JEQGTcXE0vcLax4,5679
60
- odoo/addons/mis_builder/static/src/css/report.css,sha256=vntbIm5EMa9sk70a921kkLjgAjgNVsxxEXttaGVKwT8,886
61
- odoo/addons/mis_builder/tests/__init__.py,sha256=ilgojed90T4NL_YXgVf3CACd8fo7nxpxtdGasoG0Arc,531
58
+ odoo/addons/mis_builder/static/description/index.html,sha256=nuCs4Fvh4M7akMHd2kfiKIOyZBlwpwsYhgiYf08MJ3o,55964
59
+ odoo/addons/mis_builder/static/src/components/mis_report_widget.css,sha256=xN89icJkeHNJH-ZRpUeizAsxDjCLIqi4iHJzXraQHEs,1706
60
+ odoo/addons/mis_builder/static/src/components/mis_report_widget.esm.js,sha256=2CWXMO4CsaMkT28xbt9jEW6cPvQPhn5hkPmr8hW9awA,8100
61
+ odoo/addons/mis_builder/static/src/components/mis_report_widget.xml,sha256=p5uQ25jbbDSI99-sO4VV1XT-kGJYpkw2ax5LFySEWpM,10251
62
+ odoo/addons/mis_builder/static/src/css/report.css,sha256=8JMN0iI83VSZb8faOzDhQfsEmPvkmsg050vP5NOGyWs,1271
63
+ odoo/addons/mis_builder/tests/__init__.py,sha256=57qzVitbNHIj4Kxj4fb-1sXlajzP0EMSe3P62meboL8,581
62
64
  odoo/addons/mis_builder/tests/common.py,sha256=7LqnpPyJ8yOi5gA4CwiKZaep6NGY_vIDnK36u8WVC1o,1918
63
65
  odoo/addons/mis_builder/tests/fake_models.py,sha256=DWJsXiIVVXZwXfLQ-9TmUs3eM5KBCXibS2qRXgkFt1c,177
64
66
  odoo/addons/mis_builder/tests/test_accounting_none.py,sha256=satnhbRuI4DdWIOpe-mG7sW9KlamgnyvoVaPQlqZ9FA,239
@@ -67,6 +69,7 @@ odoo/addons/mis_builder/tests/test_aggregate.py,sha256=ZXEVW0hMZYUIJM6ikQpmBH9Xc
67
69
  odoo/addons/mis_builder/tests/test_data_sources.py,sha256=KK8sycuYtaa6o8s2Lzehi3XbYwGVViL-cE3eReOggAk,7792
68
70
  odoo/addons/mis_builder/tests/test_kpi_data.py,sha256=PrgfJIkrw5liJ8InLoXCGXMD-uc7y23J-7R-UYxWIO8,4860
69
71
  odoo/addons/mis_builder/tests/test_mis_report_instance.py,sha256=t3D-0zwr0qTn9ycQKVAGnIyiUzoAf3qEvzYGPhhd8cE,23831
72
+ odoo/addons/mis_builder/tests/test_mis_report_instance_annotation.py,sha256=o9AUOJBe4tj_VNi5nNA_6cn7LEZ0-8Nhb6QJOYMnB3I,5514
70
73
  odoo/addons/mis_builder/tests/test_mis_safe_eval.py,sha256=Qge84h5xt09ibjMFeho2kKJcBXeAnYElfdHWoyc7j08,900
71
74
  odoo/addons/mis_builder/tests/test_multi_company_aep.py,sha256=hKRjqvHzDvjI7PJGjIB62WLPl9rs_vbtRhdKaJYhcWw,8246
72
75
  odoo/addons/mis_builder/tests/test_period_dates.py,sha256=qUa3qhDAAsjhUwom6SszFdT5h1rCUpYs70U3IqYEfyM,6510
@@ -81,7 +84,7 @@ odoo/addons/mis_builder/views/mis_report_style.xml,sha256=HRsXhEZZYbuZg7qJyfhdnE
81
84
  odoo/addons/mis_builder/wizard/__init__.py,sha256=qlc_LcwA6U3Wgv-qC9uacPjZXV3jShrV_RBiuKCBNPA,158
82
85
  odoo/addons/mis_builder/wizard/mis_builder_dashboard.py,sha256=DIas3iie9fb2WzJ2eGcrVc7y64IuRyxSG7tJrcV-53g,3404
83
86
  odoo/addons/mis_builder/wizard/mis_builder_dashboard.xml,sha256=ArG86hdyRWm531nK0hYU-j1N87yXtpDVpSuKTmOB47I,1440
84
- odoo_addon_mis_builder-16.0.5.2.3.dist-info/METADATA,sha256=_Vci0qBMbbIiUmDoIb3upcR_SGlV3JDHp_N8mjgAkKA,30461
85
- odoo_addon_mis_builder-16.0.5.2.3.dist-info/WHEEL,sha256=9fEMia4zL7ZuZbnCOrcYogUhmn4XFIVaJ8G4YGI31xc,81
86
- odoo_addon_mis_builder-16.0.5.2.3.dist-info/top_level.txt,sha256=QE6RBQ0QX5f4eFuUcGgU5Kbq1A_qJcDs-e_vpr6pmfU,4
87
- odoo_addon_mis_builder-16.0.5.2.3.dist-info/RECORD,,
87
+ odoo_addon_mis_builder-16.0.5.3.0.dist-info/METADATA,sha256=hDp-Vlf6JsMFa_aFx-wykQ5_sYyt4fL9PfQFYWHlIUU,30461
88
+ odoo_addon_mis_builder-16.0.5.3.0.dist-info/WHEEL,sha256=9fEMia4zL7ZuZbnCOrcYogUhmn4XFIVaJ8G4YGI31xc,81
89
+ odoo_addon_mis_builder-16.0.5.3.0.dist-info/top_level.txt,sha256=QE6RBQ0QX5f4eFuUcGgU5Kbq1A_qJcDs-e_vpr6pmfU,4
90
+ odoo_addon_mis_builder-16.0.5.3.0.dist-info/RECORD,,