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.

225 lines
7.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. class KpiDashboard(models.Model):
  6. _name = "kpi.dashboard"
  7. _description = "Dashboard"
  8. name = fields.Char(required=True)
  9. active = fields.Boolean(default=True,)
  10. item_ids = fields.One2many(
  11. "kpi.dashboard.item", inverse_name="dashboard_id", copy=True,
  12. )
  13. number_of_columns = fields.Integer(default=5, required=True)
  14. compute_on_fly_refresh = fields.Integer(
  15. default=0,
  16. help="Seconds to refresh on fly elements"
  17. )
  18. width = fields.Integer(compute="_compute_width")
  19. margin_y = fields.Integer(default=10, required=True)
  20. margin_x = fields.Integer(default=10, required=True)
  21. widget_dimension_x = fields.Integer(default=250, required=True)
  22. widget_dimension_y = fields.Integer(default=250, required=True)
  23. background_color = fields.Char(required=True, default="#f9f9f9")
  24. group_ids = fields.Many2many("res.groups",)
  25. menu_id = fields.Many2one("ir.ui.menu", copy=False)
  26. def write(self, vals):
  27. res = super().write(vals)
  28. if "group_ids" in vals:
  29. for rec in self:
  30. if rec.menu_id:
  31. rec.menu_id.write(
  32. {"groups_id": [(6, 0, rec.group_ids.ids)]}
  33. )
  34. return res
  35. @api.depends("widget_dimension_x", "margin_x", "number_of_columns")
  36. def _compute_width(self):
  37. for rec in self:
  38. rec.width = (
  39. rec.margin_x * (rec.number_of_columns + 1)
  40. + rec.widget_dimension_x * rec.number_of_columns
  41. )
  42. def read_dashboard_on_fly(self):
  43. self.ensure_one()
  44. result = []
  45. for item in self.item_ids:
  46. if not item.kpi_id.compute_on_fly:
  47. continue
  48. result.append(item._read_dashboard())
  49. return result
  50. def read_dashboard(self):
  51. self.ensure_one()
  52. result = {
  53. "name": self.name,
  54. "width": self.width,
  55. "item_ids": self.item_ids.read_dashboard(),
  56. "max_cols": self.number_of_columns,
  57. "margin_x": self.margin_x,
  58. "margin_y": self.margin_y,
  59. "compute_on_fly_refresh": self.compute_on_fly_refresh,
  60. "widget_dimension_x": self.widget_dimension_x,
  61. "widget_dimension_y": self.widget_dimension_y,
  62. "background_color": self.background_color,
  63. }
  64. if self.menu_id:
  65. result["action_id"] = self.menu_id.action.id
  66. return result
  67. def _generate_menu_vals(self, menu, action):
  68. return {
  69. "parent_id": menu.id or False,
  70. "name": self.name,
  71. "action": "%s,%s" % (action._name, action.id),
  72. "groups_id": [(6, 0, self.group_ids.ids)],
  73. }
  74. def _generate_action_vals(self, menu):
  75. return {
  76. "name": self.name,
  77. "res_model": self._name,
  78. "view_mode": "dashboard",
  79. "res_id": self.id,
  80. }
  81. def _generate_menu(self, menu):
  82. action = self.env["ir.actions.act_window"].create(
  83. self._generate_action_vals(menu)
  84. )
  85. self.menu_id = self.env["ir.ui.menu"].create(
  86. self._generate_menu_vals(menu, action)
  87. )
  88. class KpiDashboardItem(models.Model):
  89. _name = "kpi.dashboard.item"
  90. _description = "Dashboard Items"
  91. _order = "row asc, column asc"
  92. name = fields.Char(required=True)
  93. kpi_id = fields.Many2one("kpi.kpi")
  94. dashboard_id = fields.Many2one(
  95. "kpi.dashboard", required=True, ondelete="cascade"
  96. )
  97. column = fields.Integer(required=True, default=1)
  98. row = fields.Integer(required=True, default=1)
  99. end_row = fields.Integer(store=True, compute='_compute_end_row')
  100. end_column = fields.Integer(store=True, compute='_compute_end_column')
  101. size_x = fields.Integer(required=True, default=1)
  102. size_y = fields.Integer(required=True, default=1)
  103. color = fields.Char()
  104. font_color = fields.Char()
  105. modify_context = fields.Boolean()
  106. modify_context_expression = fields.Char()
  107. @api.depends('row', 'size_y')
  108. def _compute_end_row(self):
  109. for r in self:
  110. r.end_row = r.row + r.size_y - 1
  111. @api.depends('column', 'size_x')
  112. def _compute_end_column(self):
  113. for r in self:
  114. r.end_column = r.column + r.size_x - 1
  115. @api.constrains('size_y')
  116. def _check_size_y(self):
  117. for rec in self:
  118. if rec.size_y > 10:
  119. raise ValidationError(_(
  120. 'Size Y of the widget cannot be bigger than 10'))
  121. def _check_size_domain(self):
  122. return [
  123. ('dashboard_id', '=', self.dashboard_id.id),
  124. ('id', '!=', self.id),
  125. ('row', '<=', self.end_row),
  126. ('end_row', '>=', self.row),
  127. ('column', '<=', self.end_column),
  128. ('end_column', '>=', self.column),
  129. ]
  130. @api.constrains('end_row', 'end_column', 'row', 'column')
  131. def _check_size(self):
  132. for r in self:
  133. if self.search(r._check_size_domain(), limit=1):
  134. raise ValidationError(_(
  135. 'Widgets cannot be crossed by other widgets'
  136. ))
  137. if r.end_column > r.dashboard_id.number_of_columns:
  138. raise ValidationError(_(
  139. 'Widget %s is bigger than expected'
  140. ) % r.display_name)
  141. @api.onchange("kpi_id")
  142. def _onchange_kpi(self):
  143. for rec in self:
  144. if not rec.name and rec.kpi_id:
  145. rec.name = rec.kpi_id.name
  146. def _read_dashboard(self):
  147. vals = {
  148. "id": self.id,
  149. "name": self.name,
  150. "col": self.column,
  151. "row": self.row,
  152. "sizex": self.size_x,
  153. "sizey": self.size_y,
  154. "color": self.color,
  155. "font_color": self.font_color or "000000",
  156. "modify_context": self.modify_context,
  157. }
  158. if self.modify_context:
  159. vals['modify_context_expression'] = self.modify_context_expression
  160. if self.kpi_id:
  161. vals.update(
  162. {
  163. "widget": self.kpi_id.widget,
  164. "kpi_id": self.kpi_id.id,
  165. "suffix": self.kpi_id.suffix or "",
  166. "prefix": self.kpi_id.prefix or "",
  167. "compute_on_fly": self.kpi_id.compute_on_fly,
  168. }
  169. )
  170. if self.kpi_id.compute_on_fly:
  171. vals.update({
  172. "value": self.kpi_id._compute_value(),
  173. "value_last_update": fields.Datetime.now(),
  174. })
  175. else:
  176. vals.update({
  177. "value": self.kpi_id.value,
  178. "value_last_update": self.kpi_id.value_last_update,
  179. })
  180. if self.kpi_id.action_ids:
  181. vals["actions"] = self.kpi_id.action_ids.read_dashboard()
  182. else:
  183. vals["widget"] = "base_text"
  184. return vals
  185. def read_dashboard(self):
  186. result = []
  187. for kpi in self:
  188. result.append(kpi._read_dashboard())
  189. return result
  190. def technical_config(self):
  191. self.ensure_one()
  192. return {
  193. 'name': self.display_name,
  194. 'res_model': self._name,
  195. 'res_id': self.id,
  196. 'type': 'ir.actions.act_window',
  197. 'view_mode': 'form',
  198. 'target': 'new',
  199. 'view_id': self.env.ref(
  200. 'kpi_dashboard.kpi_dashboard_item_config_form_view').id,
  201. }