odoo-addon-mis-builder 16.0.5.2.3.1__py3-none-any.whl → 16.0.5.4.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 (37) 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/ca.po +91 -0
  4. odoo/addons/mis_builder/i18n/de.po +91 -0
  5. odoo/addons/mis_builder/i18n/el.po +91 -0
  6. odoo/addons/mis_builder/i18n/el_GR.po +91 -0
  7. odoo/addons/mis_builder/i18n/es.po +91 -0
  8. odoo/addons/mis_builder/i18n/fr.po +91 -0
  9. odoo/addons/mis_builder/i18n/hr.po +91 -0
  10. odoo/addons/mis_builder/i18n/it.po +91 -0
  11. odoo/addons/mis_builder/i18n/mis_builder.pot +97 -0
  12. odoo/addons/mis_builder/i18n/nl.po +91 -0
  13. odoo/addons/mis_builder/i18n/nl_NL.po +91 -0
  14. odoo/addons/mis_builder/i18n/pt.po +91 -0
  15. odoo/addons/mis_builder/i18n/pt_BR.po +91 -0
  16. odoo/addons/mis_builder/i18n/sv.po +91 -0
  17. odoo/addons/mis_builder/i18n/tr.po +91 -0
  18. odoo/addons/mis_builder/models/__init__.py +1 -0
  19. odoo/addons/mis_builder/models/kpimatrix.py +39 -4
  20. odoo/addons/mis_builder/models/mis_report_instance.py +71 -1
  21. odoo/addons/mis_builder/models/mis_report_instance_annotation.py +113 -0
  22. odoo/addons/mis_builder/report/mis_report_instance_qweb.xml +23 -0
  23. odoo/addons/mis_builder/report/mis_report_instance_xlsx.py +11 -1
  24. odoo/addons/mis_builder/security/ir.model.access.csv +2 -0
  25. odoo/addons/mis_builder/security/res_groups.xml +17 -0
  26. odoo/addons/mis_builder/static/description/index.html +1 -1
  27. odoo/addons/mis_builder/static/src/components/mis_report_widget.css +41 -0
  28. odoo/addons/mis_builder/static/src/components/mis_report_widget.esm.js +109 -4
  29. odoo/addons/mis_builder/static/src/components/mis_report_widget.xml +104 -13
  30. odoo/addons/mis_builder/static/src/css/report.css +22 -0
  31. odoo/addons/mis_builder/tests/__init__.py +1 -0
  32. odoo/addons/mis_builder/tests/test_mis_report_instance_annotation.py +154 -0
  33. odoo/addons/mis_builder/views/mis_report_instance.xml +1 -0
  34. {odoo_addon_mis_builder-16.0.5.2.3.1.dist-info → odoo_addon_mis_builder-16.0.5.4.0.dist-info}/METADATA +2 -2
  35. {odoo_addon_mis_builder-16.0.5.2.3.1.dist-info → odoo_addon_mis_builder-16.0.5.4.0.dist-info}/RECORD +37 -34
  36. {odoo_addon_mis_builder-16.0.5.2.3.1.dist-info → odoo_addon_mis_builder-16.0.5.4.0.dist-info}/WHEEL +0 -0
  37. {odoo_addon_mis_builder-16.0.5.2.3.1.dist-info → odoo_addon_mis_builder-16.0.5.4.0.dist-info}/top_level.txt +0 -0
@@ -16,6 +16,14 @@ msgstr ""
16
16
  "Plural-Forms: nplurals=2; plural=n != 1;\n"
17
17
  "X-Generator: Weblate 4.17\n"
18
18
 
19
+ #. module: mis_builder
20
+ #: model:ir.model.fields,help:mis_builder.field_mis_report_instance_annotation__annotation_context
21
+ msgid ""
22
+ "\n"
23
+ " Context used when adding annotation\n"
24
+ " "
25
+ msgstr ""
26
+
19
27
  #. module: mis_builder
20
28
  #. odoo-python
21
29
  #: code:addons/mis_builder/models/mis_report.py:0
@@ -309,6 +317,18 @@ msgstr ""
309
317
  msgid "Analytic Domain"
310
318
  msgstr "Analitik Etki Alanı(sorgu)"
311
319
 
320
+ #. module: mis_builder
321
+ #. odoo-javascript
322
+ #: code:addons/mis_builder/static/src/components/mis_report_widget.xml:0
323
+ #, python-format
324
+ msgid "Annotate"
325
+ msgstr ""
326
+
327
+ #. module: mis_builder
328
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_annotation__annotation_context
329
+ msgid "Annotation Context"
330
+ msgstr ""
331
+
312
332
  #. module: mis_builder
313
333
  #: model_terms:ir.ui.view,arch_db:mis_builder.mis_report_view_kpi_form
314
334
  msgid "Auto expand"
@@ -349,8 +369,11 @@ msgid "Bold"
349
369
  msgstr "Kalın"
350
370
 
351
371
  #. module: mis_builder
372
+ #. odoo-javascript
373
+ #: code:addons/mis_builder/static/src/components/mis_report_widget.esm.js:0
352
374
  #: model_terms:ir.ui.view,arch_db:mis_builder.mis_report_instance_add_to_dashboard_form_view
353
375
  #: model_terms:ir.ui.view,arch_db:mis_builder.wizard_mis_report_instance_view_form
376
+ #, python-format
354
377
  msgid "Cancel"
355
378
  msgstr "iptal"
356
379
 
@@ -441,6 +464,7 @@ msgstr "Karşılaştırma Modu"
441
464
  #: model:ir.model.fields,field_description:mis_builder.field_add_mis_report_instance_dashboard_wizard__create_uid
442
465
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report__create_uid
443
466
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance__create_uid
467
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_annotation__create_uid
444
468
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period__create_uid
445
469
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period_sum__create_uid
446
470
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_kpi__create_uid
@@ -456,6 +480,7 @@ msgstr "Oluşturan"
456
480
  #: model:ir.model.fields,field_description:mis_builder.field_add_mis_report_instance_dashboard_wizard__create_date
457
481
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report__create_date
458
482
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance__create_date
483
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_annotation__create_date
459
484
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period__create_date
460
485
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period_sum__create_date
461
486
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_kpi__create_date
@@ -566,6 +591,7 @@ msgstr "Sütun Açıklamalarını Görüntüle"
566
591
  #: model:ir.model.fields,field_description:mis_builder.field_add_mis_report_instance_dashboard_wizard__display_name
567
592
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report__display_name
568
593
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance__display_name
594
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_annotation__display_name
569
595
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period__display_name
570
596
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period_sum__display_name
571
597
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_kpi__display_name
@@ -808,6 +834,7 @@ msgstr "Boş Devralmayı Gizle"
808
834
  #: model:ir.model.fields,field_description:mis_builder.field_add_mis_report_instance_dashboard_wizard__id
809
835
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report__id
810
836
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance__id
837
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_annotation__id
811
838
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period__id
812
839
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period_sum__id
813
840
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_kpi__id
@@ -849,6 +876,13 @@ msgstr "Girinti Düzeyi Devralma"
849
876
  msgid "Indent level must be greater than or equal to 0"
850
877
  msgstr ""
851
878
 
879
+ #. module: mis_builder
880
+ #. odoo-javascript
881
+ #: code:addons/mis_builder/static/src/components/mis_report_widget.xml:0
882
+ #, python-format
883
+ msgid "Insert note here"
884
+ msgstr ""
885
+
852
886
  #. module: mis_builder
853
887
  #: model:ir.model.fields.selection,name:mis_builder.selection__mis_report_style__font_style__italic
854
888
  msgid "Italic"
@@ -906,6 +940,7 @@ msgid "KPIs of this report and subreports."
906
940
  msgstr ""
907
941
 
908
942
  #. module: mis_builder
943
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_annotation__kpi_id
909
944
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_kpi_expression__kpi_id
910
945
  msgid "Kpi"
911
946
  msgstr ""
@@ -924,6 +959,7 @@ msgstr "Yatay PDF"
924
959
  #: model:ir.model.fields,field_description:mis_builder.field_add_mis_report_instance_dashboard_wizard____last_update
925
960
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report____last_update
926
961
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance____last_update
962
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_annotation____last_update
927
963
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period____last_update
928
964
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period_sum____last_update
929
965
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_kpi____last_update
@@ -945,6 +981,7 @@ msgstr "Son Oluşturulan Raporlar"
945
981
  #: model:ir.model.fields,field_description:mis_builder.field_add_mis_report_instance_dashboard_wizard__write_uid
946
982
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report__write_uid
947
983
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance__write_uid
984
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_annotation__write_uid
948
985
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period__write_uid
949
986
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period_sum__write_uid
950
987
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_kpi__write_uid
@@ -960,6 +997,7 @@ msgstr ""
960
997
  #: model:ir.model.fields,field_description:mis_builder.field_add_mis_report_instance_dashboard_wizard__write_date
961
998
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report__write_date
962
999
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance__write_date
1000
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_annotation__write_date
963
1001
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period__write_date
964
1002
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period_sum__write_date
965
1003
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_kpi__write_date
@@ -1066,6 +1104,16 @@ msgstr "Mali Rapor Şablonu"
1066
1104
  msgid "MIS Report Templates"
1067
1105
  msgstr "Mali Rapor Şablonları"
1068
1106
 
1107
+ #. module: mis_builder
1108
+ #: model:res.groups,name:mis_builder.group_edit_annotation
1109
+ msgid "MIS Report: add annotations"
1110
+ msgstr ""
1111
+
1112
+ #. module: mis_builder
1113
+ #: model:res.groups,name:mis_builder.group_read_annotation
1114
+ msgid "MIS Report: view annotations"
1115
+ msgstr ""
1116
+
1069
1117
  #. module: mis_builder
1070
1118
  #: model:ir.ui.menu,name:mis_builder.mis_report_conf_menu
1071
1119
  #: model:ir.ui.menu,name:mis_builder.mis_report_finance_menu
@@ -1104,6 +1152,11 @@ msgstr ""
1104
1152
  msgid "Min"
1105
1153
  msgstr ""
1106
1154
 
1155
+ #. module: mis_builder
1156
+ #: model:ir.model,name:mis_builder.model_mis_report_instance_annotation
1157
+ msgid "Mis Report Instance Annotation"
1158
+ msgstr ""
1159
+
1107
1160
  #. module: mis_builder
1108
1161
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period__mode
1109
1162
  msgid "Mode"
@@ -1190,6 +1243,11 @@ msgstr ""
1190
1243
  msgid "Normal"
1191
1244
  msgstr ""
1192
1245
 
1246
+ #. module: mis_builder
1247
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_annotation__note
1248
+ msgid "Note"
1249
+ msgstr ""
1250
+
1193
1251
  #. module: mis_builder
1194
1252
  #: model_terms:ir.ui.view,arch_db:mis_builder.mis_report_style_view_form
1195
1253
  msgid "Number"
@@ -1232,6 +1290,11 @@ msgstr ""
1232
1290
  msgid "Percentage"
1233
1291
  msgstr ""
1234
1292
 
1293
+ #. module: mis_builder
1294
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_annotation__period_id
1295
+ msgid "Period"
1296
+ msgstr ""
1297
+
1235
1298
  #. module: mis_builder
1236
1299
  #: model:ir.model.constraint,message:mis_builder.constraint_mis_report_instance_period_name_unique
1237
1300
  msgid "Period name should be unique by report"
@@ -1319,6 +1382,13 @@ msgstr ""
1319
1382
  msgid "Relative to report base date"
1320
1383
  msgstr ""
1321
1384
 
1385
+ #. module: mis_builder
1386
+ #. odoo-javascript
1387
+ #: code:addons/mis_builder/static/src/components/mis_report_widget.esm.js:0
1388
+ #, python-format
1389
+ msgid "Remove"
1390
+ msgstr ""
1391
+
1322
1392
  #. module: mis_builder
1323
1393
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance__report_id
1324
1394
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_period__report_id
@@ -1355,8 +1425,11 @@ msgid "Rounding inherit"
1355
1425
  msgstr "Yuvarlama Devralma"
1356
1426
 
1357
1427
  #. module: mis_builder
1428
+ #. odoo-javascript
1429
+ #: code:addons/mis_builder/static/src/components/mis_report_widget.esm.js:0
1358
1430
  #: model_terms:ir.ui.view,arch_db:mis_builder.mis_report_instance_view_form
1359
1431
  #: model_terms:ir.ui.view,arch_db:mis_builder.wizard_mis_report_instance_view_form
1432
+ #, python-format
1360
1433
  msgid "Save"
1361
1434
  msgstr ""
1362
1435
 
@@ -1518,6 +1591,7 @@ msgid "Sub-KPI name ({}) must be a valid python identifier"
1518
1591
  msgstr ""
1519
1592
 
1520
1593
  #. module: mis_builder
1594
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance_annotation__subkpi_id
1521
1595
  #: model:ir.model.fields,field_description:mis_builder.field_mis_report_kpi_expression__subkpi_id
1522
1596
  msgid "Subkpi"
1523
1597
  msgstr ""
@@ -1669,6 +1743,16 @@ msgstr ""
1669
1743
  msgid "Unsupported operator %s for searching on date"
1670
1744
  msgstr ""
1671
1745
 
1746
+ #. module: mis_builder
1747
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance__user_can_edit_annotation
1748
+ msgid "User Can Edit Annotation"
1749
+ msgstr ""
1750
+
1751
+ #. module: mis_builder
1752
+ #: model:ir.model.fields,field_description:mis_builder.field_mis_report_instance__user_can_read_annotation
1753
+ msgid "User Can Read Annotation"
1754
+ msgstr ""
1755
+
1672
1756
  #. module: mis_builder
1673
1757
  #: model:ir.actions.server,name:mis_builder.ir_cron_vacuum_temp_reports_ir_actions_server
1674
1758
  #: model:ir.cron,cron_name:mis_builder.ir_cron_vacuum_temp_reports
@@ -1733,6 +1817,13 @@ msgstr ""
1733
1817
  msgid "You cannot sum period %s with itself."
1734
1818
  msgstr ""
1735
1819
 
1820
+ #. module: mis_builder
1821
+ #. odoo-python
1822
+ #: code:addons/mis_builder/models/mis_report_instance_annotation.py:0
1823
+ #, python-format
1824
+ msgid "You do not have the rights to edit annotations"
1825
+ msgstr ""
1826
+
1736
1827
  #. module: mis_builder
1737
1828
  #. odoo-python
1738
1829
  #: 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,16 @@ 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
+ )
590
+
591
+ wide_display_by_default = fields.Boolean(
592
+ string="Open report in wide mode by default",
593
+ )
583
594
 
584
595
  @api.depends("report_id.move_lines_source")
585
596
  def _compute_widget_search_view_id(self):
@@ -877,7 +888,44 @@ class MisReportInstance(models.Model):
877
888
  def compute(self):
878
889
  self.ensure_one()
879
890
  kpi_matrix = self._compute_matrix()
880
- return kpi_matrix.as_dict()
891
+ ret = kpi_matrix.as_dict()
892
+
893
+ ret["notes"] = self.get_notes_by_cell_id()
894
+ return ret
895
+
896
+ def get_notes_by_cell_id(self) -> dict:
897
+ self.ensure_one()
898
+ if not self.user_can_read_annotation:
899
+ return {}
900
+
901
+ annotations = self.env["mis.report.instance.annotation"].search(
902
+ [
903
+ ("period_id", "in", self.period_ids.ids),
904
+ ]
905
+ )
906
+ annotation_context = self._get_annotation_context()
907
+ annotations = annotations.filtered(
908
+ lambda rec: rec.annotation_context == annotation_context
909
+ )
910
+
911
+ annotations_sorted = sorted(
912
+ annotations,
913
+ key=lambda r: (
914
+ r.kpi_id.sequence,
915
+ r.period_id.sequence,
916
+ r.subkpi_id.sequence,
917
+ ),
918
+ )
919
+
920
+ return {
921
+ KpiMatrix._make_cell_id(
922
+ annotation.kpi_id.id,
923
+ False,
924
+ annotation.period_id.id,
925
+ annotation.subkpi_id and annotation.subkpi_id.id,
926
+ ): {"text": annotation.note, "sequence": sequence}
927
+ for sequence, annotation in enumerate(annotations_sorted, 1)
928
+ }
881
929
 
882
930
  @api.model
883
931
  def _get_drilldown_views_and_orders(self):
@@ -940,3 +988,25 @@ class MisReportInstance(models.Model):
940
988
  return f"{kpi.description} - {account.display_name} - {period.display_name}"
941
989
  else:
942
990
  return f"{kpi.description} - {period.display_name}"
991
+
992
+ def _get_annotation_context(self):
993
+ """Return the context used to filter annotation linked to this instance."""
994
+ self.ensure_one()
995
+ annotation_context = {}
996
+ if query_company_ids := self.query_company_ids.ids:
997
+ # sort ids to make the comparaison easier
998
+ annotation_context["query_company_ids"] = sorted(query_company_ids)
999
+
1000
+ return annotation_context
1001
+
1002
+ @api.depends_context("uid")
1003
+ def _compute_user_can_read_annotation(self):
1004
+ self.user_can_read_annotation = self.env.user.has_group(
1005
+ "mis_builder.group_read_annotation"
1006
+ )
1007
+
1008
+ @api.depends_context("uid")
1009
+ def _compute_user_can_edit_annotation(self):
1010
+ self.user_can_edit_annotation = self.env.user.has_group(
1011
+ "mis_builder.group_edit_annotation"
1012
+ )
@@ -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:eaf310c997ae9746cf01edf102baa7c519355121536c5a695b45470f6ab27dee
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.