440 lines
17 KiB
Python
440 lines
17 KiB
Python
# -*- coding: utf-8 -*-
|
|
from odoo import fields, models, api, _
|
|
from odoo.exceptions import ValidationError
|
|
|
|
|
|
class BaseDashboard(models.Model):
|
|
_name = 'base.dashbord'
|
|
_description = 'Dashboard Builder'
|
|
_order = 'sequence'
|
|
|
|
sequence = fields.Integer()
|
|
|
|
name = fields.Char(
|
|
string='Card name',
|
|
translate=True
|
|
)
|
|
|
|
model_name = fields.Char(string='Model Name')
|
|
|
|
model_id = fields.Many2one(
|
|
string='Model',
|
|
comodel_name='ir.model'
|
|
)
|
|
|
|
line_ids = fields.One2many(
|
|
string='Lines',
|
|
comodel_name='base.dashbord.line',
|
|
inverse_name='board_id'
|
|
)
|
|
|
|
icon_type = fields.Selection(
|
|
[('image', 'Image'), ('icon', 'Icon Library')],
|
|
string='Icon Type',
|
|
default='image',
|
|
required=True
|
|
)
|
|
|
|
icon_name = fields.Char(
|
|
string='Icon Class',
|
|
help="FontAwesome class (e.g., 'fa-plane', 'fa-users'). See https://fontawesome.com/v4/icons/"
|
|
)
|
|
|
|
icon_preview_html = fields.Html(
|
|
compute='_compute_icon_preview',
|
|
string="Icon/Image Preview"
|
|
)
|
|
|
|
card_image = fields.Binary(string='Card Image')
|
|
|
|
is_self_service = fields.Boolean(string='Self Service?')
|
|
is_financial_impact = fields.Boolean(string='Without Financial Impact?')
|
|
|
|
form_view_id = fields.Many2one('ir.ui.view', string='Form View')
|
|
list_view_id = fields.Many2one('ir.ui.view', string='List View')
|
|
action_id = fields.Many2one('ir.actions.act_window', string='Action')
|
|
field_id = fields.Many2one('ir.model.fields', string='Fields')
|
|
|
|
is_button = fields.Boolean(string='Is Button')
|
|
is_stage = fields.Boolean(string='Is Stage', compute='_compute_field', store=True)
|
|
is_double = fields.Boolean(string='Is Double', compute='_compute_field', store=True)
|
|
is_state = fields.Boolean(string='Is State', compute='_compute_field', store=True)
|
|
|
|
action_domain = fields.Char(
|
|
string='Action Domain',
|
|
compute='_compute_action_domain',
|
|
store=True
|
|
)
|
|
action_context = fields.Char(
|
|
string='Action Context',
|
|
compute='_compute_action_domain',
|
|
store=True
|
|
)
|
|
|
|
relation = fields.Char(string='Relation')
|
|
search_field = fields.Char(
|
|
string='Search Field',
|
|
required=True,
|
|
default='employee_id.user_id'
|
|
)
|
|
|
|
@api.depends('icon_type', 'icon_name', 'card_image')
|
|
def _compute_icon_preview(self):
|
|
for record in self:
|
|
if record.icon_type == 'icon' and record.icon_name:
|
|
record.icon_preview_html = f'<div class="dashboard-icon-preview-box icon-mode"><i class="fa {record.icon_name}"></i></div>'
|
|
elif record.icon_type == 'image' and record.card_image:
|
|
try:
|
|
img_rec = record.with_context(bin_size=False)
|
|
image_data = img_rec.card_image.decode('utf-8') if isinstance(img_rec.card_image, bytes) else img_rec.card_image
|
|
record.icon_preview_html = f'<div class="dashboard-icon-preview-box image-mode"><img src="data:image/png;base64,{image_data}"/></div>'
|
|
except Exception:
|
|
record.icon_preview_html = '<div class="dashboard-icon-preview-box error-mode"><i class="fa fa-exclamation-triangle"></i></div>'
|
|
else:
|
|
record.icon_preview_html = '<div class="dashboard-icon-preview-box default-mode"><i class="fa fa-th-large"></i></div>'
|
|
|
|
def unlink_nodes(self):
|
|
for rec in self:
|
|
rec.is_button = False
|
|
nodes = self.env['node.state'].sudo().search([
|
|
('model_id', '=', rec.model_id.id),
|
|
('is_workflow', '=', False)
|
|
])
|
|
nodes.sudo().unlink()
|
|
|
|
def unlink(self):
|
|
for rec in self:
|
|
rec.unlink_nodes()
|
|
return super(BaseDashboard, self).unlink()
|
|
|
|
@api.constrains('action_id')
|
|
def _check_action_id(self):
|
|
for record in self:
|
|
is_record = self.sudo().search_count([('action_id', '=', record.action_id.id)])
|
|
if is_record > 1:
|
|
raise ValidationError(_('There is already a record for this action.'))
|
|
|
|
@api.depends('action_id')
|
|
def _compute_action_domain(self):
|
|
for record in self:
|
|
record.action_domain = record.action_id.domain if record.action_id else False
|
|
record.action_context = record.action_id.context if record.action_id else False
|
|
|
|
@api.onchange('model_id')
|
|
def _get_stage_value(self):
|
|
for rec in self:
|
|
if rec.model_id:
|
|
rec.model_name = rec.model_id.model
|
|
|
|
@api.depends('model_name')
|
|
def _compute_field(self):
|
|
for rec in self:
|
|
rec.is_stage = False
|
|
rec.is_double = False
|
|
rec.is_state = False
|
|
|
|
if rec.model_id and rec.model_name and rec.model_name in self.env:
|
|
model = self.env[rec.model_name]
|
|
# hr.holidays has special case (can have both state and stage)
|
|
if rec.model_name in ('hr.holidays', 'hr.leave'):
|
|
rec.is_double = True
|
|
elif 'state' in model._fields:
|
|
rec.is_state = True
|
|
elif 'stage_id' in model._fields:
|
|
rec.is_stage = True
|
|
|
|
@api.depends('name', 'model_id')
|
|
def _compute_display_name(self):
|
|
for record in self:
|
|
if record.name:
|
|
record.display_name = record.name
|
|
elif record.model_id:
|
|
record.display_name = record.model_id.name
|
|
else:
|
|
record.display_name = _('Dashboard Card')
|
|
|
|
def _get_stage(self, rel):
|
|
"""Get stages from relation model and create them in intermediate table"""
|
|
for rec in self:
|
|
current_model = self.env['stage.stage'].sudo().search([('model_id', '=', rec.model_id.id)])
|
|
stage_ids = self.env[rel].sudo().search([])
|
|
|
|
if not current_model:
|
|
for stage in stage_ids:
|
|
value = stage.with_context(lang=self.env.user.lang).name
|
|
self.env['stage.stage'].sudo().create({
|
|
'model_id': rec.model_id.id,
|
|
'form_view_id': rec.form_view_id.id,
|
|
'list_view_id': rec.list_view_id.id,
|
|
'stage_id': stage.id,
|
|
'name': stage.name,
|
|
'value': value
|
|
})
|
|
else:
|
|
self.update_selection()
|
|
|
|
def update_selection(self):
|
|
"""Update states/stages when dynamic workflow changes"""
|
|
odoo_dynamic_workflow = self.env['ir.module.module'].sudo().search([
|
|
('name', '=', 'odoo_dynamic_workflow')
|
|
])
|
|
|
|
for rec in self:
|
|
if odoo_dynamic_workflow and odoo_dynamic_workflow.state == 'installed':
|
|
if rec.model_name and rec.model_name in self.env:
|
|
model = self.env[rec.model_name]
|
|
|
|
work_folow_active = self.env['odoo.workflow'].sudo().search([
|
|
('model_id', '=', rec.model_id.id),
|
|
('active', '=', True)
|
|
])
|
|
|
|
state = self.env['node.state'].sudo().search([('is_workflow', '=', True)])
|
|
work_folow_name = work_folow_active.node_ids.filtered(
|
|
lambda r: r.code_node == False and r.active == True
|
|
).mapped("node_name")
|
|
state_name = state.mapped('state')
|
|
|
|
if not rec.is_stage and rec.model_name not in ('hr.holidays', 'hr.leave'):
|
|
for line in work_folow_active.node_ids:
|
|
if not line.code_node and line.active:
|
|
if not self.env['node.state'].sudo().search([('state', '=', line.node_name)]):
|
|
self.env['node.state'].create({
|
|
'model_id': rec.model_id.id,
|
|
'form_view_id': rec.form_view_id.id,
|
|
'list_view_id': rec.list_view_id.id,
|
|
'action_id': rec.action_id.id,
|
|
'state': line.node_name,
|
|
'name': line.name,
|
|
'is_workflow': True
|
|
})
|
|
|
|
diffs = list(set(state_name) - set(work_folow_name))
|
|
self.env['node.state'].sudo().search([('state', 'in', diffs)]).unlink()
|
|
|
|
# Handle stage updates
|
|
if rec.is_stage:
|
|
rel = self.env['ir.model.fields'].sudo().search([
|
|
('model_id', '=', rec.model_id.id),
|
|
('name', '=', 'stage_id')
|
|
])
|
|
current_model = self.env['stage.stage'].sudo().search([('model_id', '=', rec.model_id.id)]).ids
|
|
|
|
if rel:
|
|
rel_ids = self.env[rel.relation].sudo().search([])
|
|
for r in rel_ids:
|
|
if r.id not in current_model:
|
|
self.env['stage.stage'].create({
|
|
'model_id': rec.model_id.id,
|
|
'form_view_id': rec.form_view_id.id,
|
|
'list_view_id': rec.list_view_id.id,
|
|
'stage_id': r.id,
|
|
'name': r.name
|
|
})
|
|
|
|
def compute_selection(self):
|
|
"""Compute states or stages depending on chosen model"""
|
|
for rec in self:
|
|
rec.is_button = True
|
|
|
|
if not rec.model_name or rec.model_name not in self.env:
|
|
raise ValidationError(_('Please select a valid model first.'))
|
|
|
|
model = self.env[rec.model_name]
|
|
current_model = self.env['node.state'].sudo().search([('model_id', '=', rec.model_id.id)])
|
|
|
|
# Handle hr.holidays/hr.leave (can have both states and stages)
|
|
if rec.model_name in ('hr.holidays', 'hr.leave'):
|
|
rec.is_double = True
|
|
if not current_model:
|
|
if 'state' in model._fields:
|
|
nodes = model._fields.get('state')._description_selection(self.env)
|
|
for node in nodes:
|
|
self.env['node.state'].create({
|
|
'model_id': rec.model_id.id,
|
|
'form_view_id': rec.form_view_id.id,
|
|
'list_view_id': rec.list_view_id.id,
|
|
'action_id': rec.action_id.id,
|
|
'state': node[0],
|
|
'name': node[1]
|
|
})
|
|
|
|
rel = self.env['ir.model.fields'].sudo().search([
|
|
('model_id', '=', rec.model_id.id),
|
|
('name', '=', 'stage_id')
|
|
])
|
|
if rel:
|
|
rel_ids = self.env[rel.relation].sudo().search([])
|
|
for r in rel_ids:
|
|
if hasattr(r, 'state') and r.state == 'approved':
|
|
self.env['node.state'].create({
|
|
'model_id': rec.model_id.id,
|
|
'form_view_id': rec.form_view_id.id,
|
|
'list_view_id': rec.list_view_id.id,
|
|
'action_id': rec.action_id.id,
|
|
'stage_id': r.id,
|
|
'name': r.name,
|
|
'is_holiday_workflow': True
|
|
})
|
|
else:
|
|
self.update_selection()
|
|
|
|
# Handle state-based models
|
|
elif 'state' in model._fields:
|
|
if not current_model:
|
|
nodes = model._fields.get('state')._description_selection(self.env)
|
|
for node in nodes:
|
|
self.env['node.state'].create({
|
|
'model_id': rec.model_id.id,
|
|
'form_view_id': rec.form_view_id.id,
|
|
'list_view_id': rec.list_view_id.id,
|
|
'action_id': rec.action_id.id,
|
|
'state': node[0],
|
|
'name': node[1]
|
|
})
|
|
else:
|
|
self.update_selection()
|
|
|
|
# Handle stage-based models
|
|
elif 'stage_id' in model._fields:
|
|
rel = self.env['ir.model.fields'].sudo().search([
|
|
('model_id', '=', rec.model_id.id),
|
|
('name', '=', 'stage_id')
|
|
])
|
|
if rel:
|
|
self._get_stage(rel.relation)
|
|
|
|
else:
|
|
raise ValidationError(_('This model has no states nor stages.'))
|
|
|
|
|
|
class BaseDashboardLine(models.Model):
|
|
_name = 'base.dashbord.line'
|
|
_description = 'Dashboard Builder Line'
|
|
|
|
name = fields.Char(string='Name')
|
|
|
|
group_ids = fields.Many2many(
|
|
string='Groups',
|
|
comodel_name='res.groups'
|
|
)
|
|
|
|
board_id = fields.Many2one(
|
|
string='Dashboard',
|
|
comodel_name='base.dashbord',
|
|
ondelete='cascade'
|
|
)
|
|
|
|
state_id = fields.Many2one(
|
|
string='State',
|
|
comodel_name='node.state'
|
|
)
|
|
|
|
stage_id = fields.Many2one(
|
|
string='Stage',
|
|
comodel_name='stage.stage'
|
|
)
|
|
|
|
model_id = fields.Many2one(
|
|
string='Model',
|
|
comodel_name='ir.model'
|
|
)
|
|
|
|
model_name = fields.Char(string='Model Name')
|
|
sequence = fields.Integer(string='Sequence')
|
|
|
|
@api.onchange('state_id')
|
|
def onchange_state_id(self):
|
|
if self.state_id:
|
|
state_ids = [stat.state_id.id for stat in self.board_id.line_ids]
|
|
if state_ids.count(self.state_id.id) > 2:
|
|
raise ValidationError(_('This state is already selected.'))
|
|
|
|
@api.onchange('stage_id')
|
|
def onchange_stage_id(self):
|
|
if self.stage_id:
|
|
stage_ids = [stat.stage_id.id for stat in self.board_id.line_ids]
|
|
if stage_ids.count(self.stage_id.id) > 2:
|
|
raise ValidationError(_('This stage is already selected.'))
|
|
|
|
|
|
class NodeState(models.Model):
|
|
_name = 'node.state'
|
|
_description = 'Dashboard Node State'
|
|
|
|
name = fields.Char(string='Name', translate=True)
|
|
state = fields.Char(string='State', translate=True)
|
|
stage_id = fields.Char(string='Stage', readonly=True)
|
|
|
|
is_workflow = fields.Boolean(string='Is Workflow', readonly=True)
|
|
is_holiday_workflow = fields.Boolean(string='Is Holiday Workflow', readonly=True)
|
|
|
|
model_id = fields.Many2one(
|
|
string='Model',
|
|
comodel_name='ir.model',
|
|
readonly=True
|
|
)
|
|
|
|
form_view_id = fields.Many2one(
|
|
'ir.ui.view',
|
|
string='Form View',
|
|
readonly=True
|
|
)
|
|
list_view_id = fields.Many2one(
|
|
'ir.ui.view',
|
|
string='List View',
|
|
readonly=True
|
|
)
|
|
action_id = fields.Many2one(
|
|
'ir.actions.act_window',
|
|
string='Action',
|
|
readonly=True
|
|
)
|
|
|
|
@api.depends('name', 'state')
|
|
def _compute_display_name(self):
|
|
for record in self:
|
|
if self.env.user.lang == 'en_US':
|
|
record.display_name = record.state or record.name or ''
|
|
else:
|
|
record.display_name = record.name or record.state or ''
|
|
|
|
|
|
class StageStage(models.Model):
|
|
_name = 'stage.stage'
|
|
_description = 'Dashboard Stage'
|
|
|
|
name = fields.Char(string='Name', translate=True, readonly=True)
|
|
stage_id = fields.Char(string='Stage', readonly=True)
|
|
value = fields.Char(string='Value', readonly=True)
|
|
|
|
model_id = fields.Many2one(
|
|
string='Model',
|
|
comodel_name='ir.model',
|
|
readonly=True
|
|
)
|
|
|
|
form_view_id = fields.Many2one(
|
|
'ir.ui.view',
|
|
string='Form View',
|
|
readonly=True
|
|
)
|
|
list_view_id = fields.Many2one(
|
|
'ir.ui.view',
|
|
string='List View',
|
|
readonly=True
|
|
)
|
|
action_id = fields.Many2one(
|
|
'ir.actions.act_window',
|
|
string='Action',
|
|
readonly=True
|
|
)
|
|
|
|
@api.depends('name', 'value')
|
|
def _compute_display_name(self):
|
|
for record in self:
|
|
if self.env.user.lang == 'en_US':
|
|
record.display_name = record.name or ''
|
|
else:
|
|
record.display_name = record.value or record.name or ''
|