OCA reporting engine fork for dev and update.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

141 lines
4.6 KiB

  1. # Copyright 2020 Creu Blanca
  2. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
  3. from odoo import api, fields, models, _
  4. from odoo.exceptions import ValidationError
  5. import ast
  6. from odoo.tools.safe_eval import safe_eval
  7. import re
  8. class KpiKpi(models.Model):
  9. _name = "kpi.kpi"
  10. _description = "Kpi Kpi"
  11. name = fields.Char(required=True)
  12. active = fields.Boolean(default=True)
  13. cron_id = fields.Many2one("ir.cron", readonly=True, copy=False)
  14. computation_method = fields.Selection(
  15. [("function", "Function"), ("code", "Code")], required=True
  16. )
  17. value = fields.Serialized()
  18. dashboard_item_ids = fields.One2many("kpi.dashboard.item", inverse_name="kpi_id")
  19. model_id = fields.Many2one("ir.model",)
  20. function = fields.Char()
  21. args = fields.Char()
  22. kwargs = fields.Char()
  23. widget = fields.Selection(
  24. [("number", "Number"), ("meter", "Meter"), ("graph", "Graph")],
  25. required=True,
  26. default="number",
  27. )
  28. value_last_update = fields.Datetime(readonly=True)
  29. prefix = fields.Char()
  30. suffix = fields.Char()
  31. action_ids = fields.One2many(
  32. "kpi.kpi.action",
  33. inverse_name='kpi_id',
  34. help="Actions that can be opened from the KPI"
  35. )
  36. code = fields.Text("Code")
  37. def _cron_vals(self):
  38. return {
  39. "name": self.name,
  40. "model_id": self.env.ref("kpi_dashboard.model_kpi_kpi").id,
  41. "interval_number": 1,
  42. "interval_type": "hours",
  43. "state": "code",
  44. "code": "model.browse(%s).compute()" % self.id,
  45. "active": True,
  46. }
  47. def compute(self):
  48. for record in self:
  49. record._compute()
  50. return True
  51. def _compute(self):
  52. self.write(
  53. {
  54. "value": getattr(
  55. self, "_compute_value_%s" % self.computation_method
  56. )()
  57. }
  58. )
  59. notifications = []
  60. for dashboard_item in self.dashboard_item_ids:
  61. channel = "kpi_dashboard_%s" % dashboard_item.dashboard_id.id
  62. notifications.append([channel, dashboard_item._read_dashboard()])
  63. if notifications:
  64. self.env["bus.bus"].sendmany(notifications)
  65. def _compute_value_function(self):
  66. obj = self
  67. if self.model_id:
  68. obj = self.env[self.model_id.model]
  69. args = ast.literal_eval(self.args or "[]")
  70. kwargs = ast.literal_eval(self.kwargs or "{}")
  71. return getattr(obj, self.function)(*args, **kwargs)
  72. def generate_cron(self):
  73. self.ensure_one()
  74. self.cron_id = self.env["ir.cron"].create(self._cron_vals())
  75. @api.multi
  76. def write(self, vals):
  77. if "value" in vals:
  78. vals["value_last_update"] = fields.Datetime.now()
  79. return super().write(vals)
  80. def _get_code_input_dict(self):
  81. return {
  82. "self": self,
  83. "model": self.browse(),
  84. }
  85. def _forbidden_code(self):
  86. return ["commit", "rollback", "getattr", "execute"]
  87. def _compute_value_code(self):
  88. forbidden = self._forbidden_code()
  89. search_terms = "(" + ("|".join(forbidden)) + ")"
  90. if re.search(search_terms, (self.code or "").lower()):
  91. message = ", ".join(forbidden[:-1]) or ""
  92. if len(message) > 0:
  93. message += _(" or ")
  94. message += forbidden[-1]
  95. raise ValidationError(_(
  96. "The code cannot contain the following terms: %s."
  97. ) % message)
  98. results = self._get_code_input_dict()
  99. savepoint = "kpi_formula_%s" % self.id
  100. self.env.cr.execute("savepoint %s" % savepoint)
  101. safe_eval(self.code or "", results, mode="exec", nocopy=True)
  102. self.env.cr.execute("rollback to %s" % savepoint)
  103. return results.get("result", {})
  104. class KpiKpiAction(models.Model):
  105. _name = 'kpi.kpi.action'
  106. _description = 'KPI action'
  107. kpi_id = fields.Many2one('kpi.kpi', required=True, ondelete='cascade')
  108. action = fields.Reference(
  109. selection=[('ir.actions.report', 'ir.actions.report'),
  110. ('ir.actions.act_window', 'ir.actions.act_window'),
  111. ('ir.actions.act_url', 'ir.actions.act_url'),
  112. ('ir.actions.server', 'ir.actions.server'),
  113. ('ir.actions.client', 'ir.actions.client')],
  114. required=True,
  115. )
  116. def read_dashboard(self):
  117. result = []
  118. for r in self:
  119. result.append({
  120. 'id': r.action.id,
  121. 'type': r.action._name,
  122. 'name': r.action.name
  123. })
  124. return result