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.

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