# -*- coding: utf-8 -*-
from odoo import api, fields, models, Command
class HelpdeskWorkflowTemplate(models.Model):
_name = 'helpdesk.workflow.template'
_description = 'Helpdesk Workflow Template'
_order = 'sequence, name'
name = fields.Char(
string='Template Name',
required=True,
translate=True,
help='Name of the workflow template'
)
sequence = fields.Integer(
string='Sequence',
default=10,
help='Order of templates'
)
description = fields.Text(
string='Description',
translate=True,
help='Description of the workflow template'
)
active = fields.Boolean(
string='Active',
default=True,
help='If unchecked, this template will be hidden'
)
stage_template_ids = fields.One2many(
'helpdesk.workflow.template.stage',
'template_id',
string='Stages',
help='Stages included in this workflow template'
)
sla_template_ids = fields.One2many(
'helpdesk.workflow.template.sla',
'template_id',
string='SLA Policies',
help='SLA policies included in this workflow template'
)
field_ids = fields.One2many(
'helpdesk.workflow.template.field',
'workflow_template_id',
string='Form Fields',
copy=True,
help='Form fields for website ticket submission'
)
stage_count = fields.Integer(
string='Stages Count',
compute='_compute_counts',
store=False
)
sla_count = fields.Integer(
string='SLA Policies Count',
compute='_compute_counts',
store=False
)
field_count = fields.Integer(
string='Form Fields Count',
compute='_compute_counts',
store=False
)
team_ids = fields.One2many(
'helpdesk.team',
'workflow_template_id',
string='Teams Using This Template',
readonly=True
)
team_count = fields.Integer(
string='Teams Count',
compute='_compute_counts',
store=False
)
documentation_html = fields.Html(
string='Documentation',
compute='_compute_documentation_html',
store=False,
sanitize=False,
help='Auto-generated documentation of this workflow template'
)
@api.depends('name', 'stage_template_ids', 'sla_template_ids', 'field_ids', 'team_ids')
def _compute_documentation_html(self):
"""Generate dynamic documentation HTML for the workflow template"""
for template in self:
if not template.id:
template.documentation_html = ''
continue
html_parts = []
# Header with summary
html_parts.append(self._build_doc_header(template))
# Stage flow with SLAs
if template.stage_template_ids:
html_parts.append(self._build_doc_stages(template))
# SLA policies table
if template.sla_template_ids:
html_parts.append(self._build_doc_slas(template))
# Form fields grouped by visibility
if template.field_ids:
html_parts.append(self._build_doc_fields(template))
# Teams using this template
if template.team_ids:
html_parts.append(self._build_doc_teams(template))
template.documentation_html = ''.join(html_parts)
def _build_doc_header(self, template):
"""Build documentation header with summary"""
return f'''
'''
def _build_doc_stages(self, template):
"""Build stage flow diagram with integrated SLAs"""
stages = template.stage_template_ids.sorted('sequence')
# Create SLA lookup by stage
sla_by_stage = {}
for sla in template.sla_template_ids:
stage_id = sla.stage_template_id.id
if stage_id not in sla_by_stage:
sla_by_stage[stage_id] = []
sla_by_stage[stage_id].append(sla)
# Build flow diagram
flow_items = []
for i, stage in enumerate(stages):
# Stage box styling
if stage.fold:
bg_color = '#6c757d' # Gray for closed stages
icon = '📦'
elif i == 0:
bg_color = '#17a2b8' # Blue for first stage
icon = '📥'
else:
bg_color = '#28a745' # Green for normal stages
icon = '⚙️'
# SLA info for this stage
sla_html = ''
if stage.id in sla_by_stage:
slas = sla_by_stage[stage.id]
sla_texts = []
for sla in slas[:2]: # Show max 2 SLAs
priority_text = self._get_priority_text(sla.priority)
sla_texts.append(f"⏱️ {sla.time:.0f}h ({priority_text})")
sla_html = '
' + '
'.join(sla_texts) + ''
stage_html = f'''
{icon} {stage.name}
{sla_html}
'''
flow_items.append(stage_html)
flow_html = '→'.join(flow_items)
return f'''
🔄 Flujo de Trabajo
{flow_html}
Las etapas en gris son etapas de cierre (plegadas en Kanban)
'''
def _build_doc_slas(self, template):
"""Build SLA policies table"""
slas = template.sla_template_ids.sorted('sequence')
rows = []
for sla in slas:
priority_badge = self._get_priority_badge(sla.priority)
excluded = ', '.join(sla.exclude_stage_template_ids.mapped('name')) or '-'
rows.append(f'''
| {sla.name} |
{sla.stage_template_id.name or '-'} |
{sla.time:.0f}h |
{priority_badge} |
{excluded} |
''')
return f'''
⏱️ Políticas SLA
| Nombre |
Etapa Objetivo |
Tiempo |
Prioridad |
Pausado en |
{''.join(rows)}
'''
def _build_doc_fields(self, template):
"""Build form fields table grouped by visibility dependency"""
fields = template.field_ids.sorted('sequence')
# Group fields by visibility dependency
groups = {} # {dependency_id: {'name': str, 'condition': str, 'fields': []}}
common_fields = []
for field in fields:
if not field.visibility_dependency:
common_fields.append(field)
else:
dep_id = field.visibility_dependency.id
if dep_id not in groups:
# Get the condition display
condition_display = self._get_visibility_display(field)
groups[dep_id] = {
'name': field.visibility_dependency.field_description or field.visibility_dependency.name,
'condition': condition_display,
'fields': []
}
groups[dep_id]['fields'].append(field)
html_parts = ['', '
📝 Campos del Formulario
']
# Common fields
if common_fields:
html_parts.append(self._build_fields_table('Campos Comunes', 'Siempre visibles', common_fields, 'bg-primary'))
# Grouped fields
for dep_id, group in groups.items():
title = f"Campos para: {group['condition']}"
html_parts.append(self._build_fields_table(title, '', group['fields'], 'bg-warning'))
html_parts.append('')
return ''.join(html_parts)
def _build_fields_table(self, title, subtitle, fields, badge_class):
"""Build a single fields table"""
rows = []
for field in fields:
required_icon = '✅' if field.required else '❌'
model_required = ' 🔒' if field.model_required else ''
label = field.label_custom or (field.field_id.field_description if field.field_id else field.field_name)
help_text = f'{field.help_text[:50]}...' if field.help_text and len(field.help_text) > 50 else (f'{field.help_text}' if field.help_text else '-')
rows.append(f'''
{field.field_name} |
{label} |
{required_icon}{model_required} |
{help_text} |
''')
subtitle_html = f'({subtitle})' if subtitle else ''
return f'''
{title} {subtitle_html}
| Campo |
Etiqueta |
Oblig. |
Descripción |
{''.join(rows)}
'''
def _build_doc_teams(self, template):
"""Build teams using this template"""
teams = template.team_ids
rows = []
for team in teams:
form_icon = '✅' if team.use_website_helpdesk_form else '❌'
rows.append(f'''
| {team.name} |
{form_icon} |
''')
return f'''
👥 Equipos Usando Este Template
| Equipo |
Formulario Web |
{''.join(rows)}
'''
def _get_visibility_display(self, field):
"""Get human readable visibility condition"""
if not field.visibility_dependency:
return 'Siempre visible'
dep_name = field.visibility_dependency.field_description or field.visibility_dependency.name
comparator_map = {
'equal': 'es igual a',
'!equal': 'no es igual a',
'contains': 'contiene',
'!contains': 'no contiene',
'set': 'está definido',
'!set': 'no está definido',
'selected': 'es igual a',
'!selected': 'no es igual a',
}
comp_text = comparator_map.get(field.visibility_comparator, field.visibility_comparator)
# Get condition value display
condition_value = field.visibility_condition
if field.visibility_dependency.ttype == 'many2one' and field.visibility_condition_m2o_id:
try:
related_model = self.env[field.visibility_dependency.relation]
related_record = related_model.browse(field.visibility_condition_m2o_id)
if related_record.exists():
condition_value = related_record.display_name
except:
pass
if field.visibility_comparator in ['set', '!set']:
return f"{dep_name} {comp_text}"
return f"{dep_name} {comp_text} '{condition_value}'"
def _get_priority_text(self, priority):
"""Get priority text"""
priority_map = {
'0': 'Baja',
'1': 'Normal',
'2': 'Alta',
'3': 'Urgente',
}
return priority_map.get(priority, 'Todas')
def _get_priority_badge(self, priority):
"""Get priority badge HTML"""
priority_map = {
'0': ('Baja', 'bg-secondary'),
'1': ('Normal', 'bg-info'),
'2': ('Alta', 'bg-warning text-dark'),
'3': ('Urgente', 'bg-danger'),
}
text, cls = priority_map.get(priority, ('Todas', 'bg-light text-dark'))
return f'{text}'
@api.model
def default_get(self, fields_list):
"""Set default required form fields when creating a new workflow template"""
res = super().default_get(fields_list)
# Only set defaults if field_ids is in fields_list and not already provided
if 'field_ids' in fields_list and not self.env.context.get('default_field_ids'):
# Required fields for the website form builder
required_field_names = ['partner_name', 'partner_email', 'name', 'description']
# Get field records from helpdesk.ticket model
ticket_model = self.env['ir.model'].search([('model', '=', 'helpdesk.ticket')], limit=1)
if ticket_model:
required_fields = self.env['ir.model.fields'].search([
('model_id', '=', ticket_model.id),
('name', 'in', required_field_names),
('website_form_blacklisted', '=', False)
])
field_map = {f.name: f for f in required_fields}
field_ids_commands = []
sequence = 10
# Add partner_name (required, sequence 10)
if 'partner_name' in field_map:
field_ids_commands.append((0, 0, {
'field_id': field_map['partner_name'].id,
'required': True,
'sequence': sequence
}))
sequence += 10
# Add partner_email (required, sequence 20)
if 'partner_email' in field_map:
field_ids_commands.append((0, 0, {
'field_id': field_map['partner_email'].id,
'required': True,
'sequence': sequence
}))
sequence += 10
# Add name (model_required, sequence 30)
if 'name' in field_map:
name_field = field_map['name']
field_ids_commands.append((0, 0, {
'field_id': name_field.id,
'required': True,
'model_required': name_field.required,
'sequence': sequence
}))
sequence += 10
# Add description (required, sequence 40)
if 'description' in field_map:
field_ids_commands.append((0, 0, {
'field_id': field_map['description'].id,
'required': True,
'sequence': sequence
}))
if field_ids_commands:
res['field_ids'] = field_ids_commands
return res
@api.depends('stage_template_ids', 'sla_template_ids', 'field_ids', 'team_ids')
def _compute_counts(self):
for template in self:
template.stage_count = len(template.stage_template_ids)
template.sla_count = len(template.sla_template_ids)
template.field_count = len(template.field_ids)
template.team_count = len(template.team_ids)
def action_view_teams(self):
"""Open teams using this template"""
self.ensure_one()
action = self.env['ir.actions.actions']._for_xml_id('helpdesk.helpdesk_team_action')
action.update({
'domain': [('workflow_template_id', '=', self.id)],
'context': {
'default_workflow_template_id': self.id,
'search_default_workflow_template_id': self.id,
},
})
return action
def copy_data(self, default=None):
"""Override copy to duplicate stages and SLAs"""
defaults = super().copy_data(default=default)
# Note: Stages and SLAs will be copied automatically via ondelete='cascade'
# We just need to update the name
for template, vals in zip(self, defaults):
vals['name'] = self.env._("%s (copy)", template.name)
return defaults