# -*- 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'''

📋 Documentación - {template.name or "Sin nombre"}


{len(template.stage_template_ids)} Etapas {len(template.sla_template_ids)} SLAs {len(template.field_ids)} Campos {len(template.team_ids)} Equipos
''' 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

{''.join(rows)}
Nombre Etapa Objetivo Tiempo Prioridad Pausado en
''' 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}
{''.join(rows)}
Campo Etiqueta Oblig. Descripción
''' 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

{''.join(rows)}
Equipo Formulario Web
''' 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