# -*- coding: utf-8 -*- """ Migration script to integrate helpdesk.template with helpdesk.workflow.template This script migrates all helpdesk.template records to helpdesk.workflow.template, moving field_ids from template to workflow_template. """ import logging from odoo import api, SUPERUSER_ID _logger = logging.getLogger(__name__) def migrate(cr, version): """Migrate helpdesk.template to helpdesk.workflow.template""" try: env = api.Environment(cr, SUPERUSER_ID, {}) _logger.info("=" * 80) _logger.info("Starting migration: helpdesk.template -> helpdesk.workflow.template") _logger.info("=" * 80) # 0. Verify pre-migration completed (workflow_template_id column exists) cr.execute(""" SELECT column_name FROM information_schema.columns WHERE table_name = 'helpdesk_template_field' AND column_name = 'workflow_template_id' """) if not cr.fetchone(): _logger.error("❌ Pre-migration not completed! workflow_template_id column does not exist.") _logger.error("Module update will fail. Please check pre-migration logs.") raise Exception("Pre-migration required: workflow_template_id column missing") _logger.info("✅ Pre-migration verified: workflow_template_id column exists") # 1. Get all helpdesk.template records templates = env['helpdesk.template'].search([]) _logger.info(f"Found {len(templates)} template(s) to migrate") if not templates: _logger.info("No templates to migrate. Migration complete.") cr.commit() return migrated_count = 0 skipped_count = 0 error_count = 0 # 2. For each template, migrate to workflow template for template in templates: try: _logger.info(f"\nProcessing template: {template.name} (ID: {template.id})") # First, check which teams use this template teams_using_template = env['helpdesk.team'].search([ ('template_id', '=', template.id) ]) _logger.info(f" Found {len(teams_using_template)} team(s) using this template") # Group teams by whether they have workflow_template_id or not teams_with_workflow = teams_using_template.filtered('workflow_template_id') teams_without_workflow = teams_using_template.filtered(lambda t: not t.workflow_template_id) workflow_template = None # Strategy 1: If teams have workflow_template_id, migrate fields to their existing workflow if teams_with_workflow: # Check if all teams use the same workflow_template_id workflow_ids = teams_with_workflow.mapped('workflow_template_id.id') unique_workflows = set(workflow_ids) if len(unique_workflows) == 1: # All teams use the same workflow template - migrate fields there workflow_template = teams_with_workflow[0].workflow_template_id _logger.info(f" All teams use workflow template '{workflow_template.name}' (ID: {workflow_template.id})") _logger.info(f" Migrating fields from template to existing workflow template...") else: # Teams use different workflow templates - use the most common one from collections import Counter workflow_counter = Counter(workflow_ids) most_common_workflow_id = workflow_counter.most_common(1)[0][0] workflow_template = env['helpdesk.workflow.template'].browse(most_common_workflow_id) _logger.info(f" Teams use different workflow templates. Using most common: '{workflow_template.name}' (ID: {workflow_template.id})") _logger.info(f" Migrating fields from template to this workflow template...") # Strategy 2: If no teams have workflow_template_id, create/find workflow template if not workflow_template: # Check if a workflow template with the same name already exists workflow_template = env['helpdesk.workflow.template'].search([ ('name', '=', template.name) ], limit=1) if workflow_template: _logger.info(f" Workflow template '{workflow_template.name}' already exists (ID: {workflow_template.id})") _logger.info(f" Migrating fields from template into existing workflow template...") else: # Create new workflow template workflow_template = env['helpdesk.workflow.template'].create({ 'name': template.name, 'description': template.description or False, 'active': template.active, }) _logger.info(f" Created new workflow template: {workflow_template.name} (ID: {workflow_template.id})") # 3. Migrate field_ids from template to workflow_template field_count = 0 for field in template.field_ids: try: # Check if field already exists in workflow template existing_field = env['helpdesk.template.field'].search([ ('workflow_template_id', '=', workflow_template.id), ('field_id', '=', field.field_id.id) ], limit=1) if existing_field: _logger.warning(f" Field '{field.field_id.name}' already exists in workflow template. Skipping.") continue # Update field to point to workflow_template_id instead of template_id field.write({ 'template_id': False, # Clear legacy reference 'workflow_template_id': workflow_template.id, # Set new reference }) field_count += 1 _logger.info(f" Migrated field: {field.field_id.name}") except Exception as e: _logger.error(f" Error migrating field {field.id}: {e}", exc_info=True) error_count += 1 continue _logger.info(f" Migrated {field_count} field(s) to workflow template") # 4. Update teams that use this template team_count = 0 for team in teams_using_template: try: # If team doesn't have workflow_template_id, assign it if not team.workflow_template_id: team.write({ 'workflow_template_id': workflow_template.id, 'template_id': False, # Clear legacy reference }) team_count += 1 _logger.info(f" Updated team: {team.name} (ID: {team.id}) - assigned workflow template") else: # Team already has workflow_template_id # If we migrated fields to a different workflow, update it if team.workflow_template_id.id != workflow_template.id: _logger.info(f" Team {team.name} had workflow '{team.workflow_template_id.name}', updating to '{workflow_template.name}'") team.write({ 'workflow_template_id': workflow_template.id, 'template_id': False, # Clear legacy reference }) team_count += 1 else: # Same workflow template - just clear template_id team.write({ 'template_id': False, # Clear legacy reference }) _logger.info(f" Team {team.name} already uses workflow '{workflow_template.name}'. Cleared template_id only.") except Exception as e: _logger.error(f" Error updating team {team.id}: {e}", exc_info=True) error_count += 1 continue _logger.info(f" Updated {team_count} team(s)") # 5. Regenerate forms for affected teams (skip in production if too many) teams_affected = env['helpdesk.team'].search([ ('workflow_template_id', '=', workflow_template.id), ('use_website_helpdesk_form', '=', True) ]) # Limit regeneration to avoid timeout in production max_teams_to_regenerate = 50 if len(teams_affected) > max_teams_to_regenerate: _logger.warning(f" Too many teams ({len(teams_affected)}) to regenerate. Skipping automatic regeneration.") _logger.warning(f" Please regenerate forms manually or via cron.") else: for team in teams_affected: if team.website_form_view_id: try: team._regenerate_form_from_template() _logger.info(f" Regenerated form for team: {team.name}") except Exception as e: _logger.warning(f" Could not regenerate form for team {team.id}: {e}") # Don't fail migration if form regeneration fails continue migrated_count += 1 _logger.info(f"✅ Successfully migrated template: {template.name}") except Exception as e: _logger.error(f"❌ Error migrating template {template.id} ({template.name}): {e}", exc_info=True) error_count += 1 skipped_count += 1 continue # 6. Summary _logger.info("\n" + "=" * 80) _logger.info("Migration Summary:") _logger.info(f" Templates processed: {len(templates)}") _logger.info(f" Successfully migrated: {migrated_count}") _logger.info(f" Skipped/Errors: {skipped_count}") _logger.info(f" Total errors: {error_count}") _logger.info("=" * 80) # 7. Verify migration remaining_templates = env['helpdesk.template'].search([]) if remaining_templates: _logger.warning(f"⚠️ {len(remaining_templates)} template(s) still exist. They may have errors or were skipped.") else: _logger.info("✅ All templates migrated successfully") # Check for orphaned fields (fields with template_id but no workflow_template_id) orphaned_fields = env['helpdesk.template.field'].search([ ('template_id', '!=', False), ('workflow_template_id', '=', False) ]) if orphaned_fields: _logger.warning(f"⚠️ Found {len(orphaned_fields)} orphaned field(s) (have template_id but no workflow_template_id)") _logger.warning(f" These fields will need manual migration or will be cleaned up later.") # Commit only if we have successful migrations or no templates to migrate if migrated_count > 0 or len(templates) == 0: cr.commit() _logger.info("✅ Migration completed successfully") else: # If all templates failed, rollback but don't block module update # (data might be corrupted, but module structure is OK) _logger.error("⚠️ All templates failed to migrate. Rolling back data changes.") try: cr.rollback() except Exception: pass _logger.error("Module update will continue, but data migration needs manual intervention.") except Exception as e: _logger.error(f"❌ Critical error in migration: {e}", exc_info=True) try: cr.rollback() except Exception: pass # Don't raise - allow module update to complete even if migration fails # The module structure is OK, only data migration failed _logger.error("Migration failed but module update will continue. Please review logs and fix manually if needed.")