Jelajahi Sumber

Squashed 'm22tc_google_workspace/' changes from f7dfa0c..3bd97ba

3bd97ba feat: Mejorar formato visual del anรกlisis de estructura de carpetas
bc36632 fix: Resolve Shared Drive folder operations and variable naming conflicts - Add robust error handling for folder validation and move operations - Fix '_' variable conflict with translation function - Improve folder structure analysis and renaming logic

git-subtree-dir: m22tc_google_workspace
git-subtree-split: 3bd97ba90a0d07b824907b32bccca46c582ecdd0
root 5 bulan lalu
induk
melakukan
f259eaed11
1 mengubah file dengan 486 tambahan dan 217 penghapusan
  1. 486 217
      models/crm_lead.py

+ 486 - 217
models/crm_lead.py

@@ -103,6 +103,36 @@ class CrmLead(models.Model):
         """Build a string representation of the folder structure"""
         return f"{components['primary_name']}/{components['year']}/{components['opportunity_name']}"
 
+    def _update_folder_structure_fields(self, folder_id=None, structure_components=None):
+        """Helper method to update folder structure fields without triggering loops"""
+        update_vals = {}
+        
+        if folder_id:
+            update_vals['google_drive_folder_id'] = folder_id
+            update_vals['google_drive_url'] = f"https://drive.google.com/drive/folders/{folder_id}"
+        
+        if structure_components:
+            update_vals['google_drive_folder_name'] = self._build_structure_string(structure_components)
+        
+        if update_vals:
+            self.with_context(skip_google_drive_update=True).write(update_vals)
+
+    def _post_folder_message(self, message, message_type='comment'):
+        """Helper method to post folder-related messages"""
+        self.message_post(
+            body=_(message),
+            message_type=message_type
+        )
+
+    def _get_current_folder_components(self):
+        """Helper method to get current folder components"""
+        return self._get_folder_name_components()
+
+    def _get_expected_folder_structure(self):
+        """Helper method to get expected folder structure string"""
+        components = self._get_current_folder_components()
+        return self._build_structure_string(components)
+
     # ============================================================================
     # Mร‰TODOS DE VALIDACIร“N Y PRERREQUISITOS
     # ============================================================================
@@ -159,9 +189,9 @@ class CrmLead(models.Model):
 
     def _get_folder_name_components(self):
         """Get the components for folder naming based on partner/contact information"""
-        # Priority 1: partner_id.company_id.name (empresa del cliente)
-        if self.partner_id and self.partner_id.company_id and self.partner_id.company_id.name:
-                primary_name = self.partner_id.company_id.name
+        # Priority 1: partner_id.parent_id.name (empresa padre del contacto)
+        if self.partner_id and self.partner_id.parent_id and self.partner_id.parent_id.name:
+                primary_name = self.partner_id.parent_id.name
         # Priority 2: partner_id.company_name
         elif self.partner_id and self.partner_id.company_name:
                 primary_name = self.partner_id.company_name
@@ -275,16 +305,22 @@ class CrmLead(models.Model):
         
         _logger.info(f"๐Ÿ” Checking for existing folder '{folder_name}' in parent {parent_folder_id}")
         
-        folder_id = drive_service.create_or_get_folder(parent_folder_id, folder_name)
-        
-        # Check if it was reused or created
+        # First check if folder already exists
         existing_folders = drive_service.find_folders_by_name(parent_folder_id, f'^{folder_name}$')
         was_reused = len(existing_folders) > 0
         
         if was_reused:
+            folder_id = existing_folders[0]['id']
             _logger.info(f"๐Ÿ”„ REUSING existing folder '{folder_name}' (ID: {folder_id})")
         else:
-            _logger.info(f"๐Ÿ“ CREATED new folder '{folder_name}' (ID: {folder_id})")
+            # Create new folder
+            result = drive_service.create_folder(folder_name, parent_folder_id)
+            if result.get('success'):
+                folder_id = result.get('folder_id')
+                _logger.info(f"๐Ÿ“ CREATED new folder '{folder_name}' (ID: {folder_id})")
+            else:
+                error_msg = result.get('error', 'Unknown error')
+                raise UserError(_('Failed to create Google Drive folder "%s": %s') % (folder_name, error_msg))
         
         return folder_id, was_reused
 
@@ -334,14 +370,13 @@ class CrmLead(models.Model):
 
     def _store_folder_info(self, folder_id):
         """Store folder information and update URL"""
-        expected_components = self._get_folder_name_components()
-        expected_structure = self._build_structure_string(expected_components)
+        expected_components = self._get_current_folder_components()
         
-        self.with_context(skip_google_drive_update=True).write({
-            'google_drive_folder_id': folder_id,
-            'google_drive_folder_name': expected_structure,
-            'google_drive_url': f"https://drive.google.com/drive/folders/{folder_id}"
-        })
+        # Update folder structure fields
+        self._update_folder_structure_fields(
+            folder_id=folder_id,
+            structure_components=expected_components
+        )
         
         # Copy URL to configured field if empty
         if not self._get_configured_field_value():
@@ -357,10 +392,7 @@ class CrmLead(models.Model):
             # Check if we need to create folder
             if self._should_create_folder(vals) and self._validate_folder_creation_prerequisites():
                 self._create_google_drive_folder_structure()
-                self.message_post(
-                    body=_("โœ… Google Drive folder created automatically"),
-                    message_type='comment'
-                )
+                self._post_folder_message("โœ… Google Drive folder created automatically")
             
             # If we have a folder, verify and update structure
             if self.google_drive_folder_id:
@@ -368,10 +400,7 @@ class CrmLead(models.Model):
                 
         except Exception as e:
             _logger.error(f"Error processing Google Drive updates for record {self.id}: {str(e)}")
-            self.message_post(
-                body=_("โŒ Error updating Google Drive: %s") % str(e),
-                message_type='comment'
-            )
+            self._post_folder_message(f"โŒ Error updating Google Drive: {str(e)}")
 
     def _should_create_folder(self, vals):
         """Helper method to determine if folder should be created"""
@@ -406,21 +435,41 @@ class CrmLead(models.Model):
                 self._rename_entire_folder_structure()
                 
                 # Update stored structure
-                expected_components = self._get_folder_name_components()
-                expected_structure = self._build_structure_string(expected_components)
-                self.with_context(skip_google_drive_update=True).write({
-                    'google_drive_folder_name': expected_structure
-                })
+                expected_components = self._get_current_folder_components()
+                self._update_folder_structure_fields(structure_components=expected_components)
                 
-                self.message_post(
-                    body=_("โœ… Google Drive folder renamed due to partner change"),
-                    message_type='comment'
-                )
+                self._post_folder_message("โœ… Google Drive folder renamed due to partner change")
                 return
         
+        # Handle partner parent changes (when contact's company changes)
+        if 'partner_id' in vals and self.google_drive_folder_id:
+            old_partner_id = old_values.get('partner_id') if old_values else self.partner_id.id
+            new_partner_id = vals['partner_id']
+            
+            if old_partner_id == new_partner_id:
+                # Same partner, but check if their parent (company) changed
+                old_partner = self.env['res.partner'].browse(old_partner_id) if old_partner_id else None
+                new_partner = self.env['res.partner'].browse(new_partner_id) if new_partner_id else None
+                
+                if old_partner and new_partner:
+                    old_parent_id = old_partner.parent_id.id if old_partner.parent_id else None
+                    new_parent_id = new_partner.parent_id.id if new_partner.parent_id else None
+                    
+                    if old_parent_id != new_parent_id:
+                        # Partner's parent (company) changed - rename folder structure
+                        _logger.info(f"๐Ÿ”„ Partner's parent company changed from {old_parent_id} to {new_parent_id}. Renaming folder structure.")
+                        self._rename_entire_folder_structure()
+                        
+                        # Update stored structure
+                        expected_components = self._get_current_folder_components()
+                        self._update_folder_structure_fields(structure_components=expected_components)
+                        
+                        self._post_folder_message("โœ… Google Drive folder renamed due to contact's company change")
+                        return
+        
         # Handle other structure changes (name changes, etc.)
-        expected_components = self._get_folder_name_components()
-        expected_structure = self._build_structure_string(expected_components)
+        expected_components = self._get_current_folder_components()
+        expected_structure = self._get_expected_folder_structure()
         
         current_structure = (old_values.get('google_drive_folder_name', '') if old_values 
                            else self.google_drive_folder_name or '')
@@ -430,65 +479,106 @@ class CrmLead(models.Model):
             self._rename_entire_folder_structure(new_components=expected_components)
             
             # Update stored structure
-            self.with_context(skip_google_drive_update=True).write({
-                'google_drive_folder_name': expected_structure
-            })
+            self._update_folder_structure_fields(structure_components=expected_components)
             
-            self.message_post(
-                body=_("โœ… Google Drive folder structure updated"),
-                message_type='comment'
-            )
+            self._post_folder_message("โœ… Google Drive folder structure updated")
 
     def _rename_entire_folder_structure(self, old_components=None, new_components=None):
-        """Unified method to rename folder structure"""
+        """Unified method to rename folder structure with robust error handling"""
         if not self.google_drive_folder_id:
             return
         
-        # Get current structure if needed
-        if old_components is None and new_components is not None:
-            current_structure = self._analyze_crm_folder_structure(self.google_drive_folder_id)
-            if not current_structure:
-                raise UserError(_('Could not analyze current folder structure'))
+        try:
+            drive_service = self.env['google.drive.service']
             
-            old_components = {
-                'primary_name': current_structure.get('primary_folder', {}).get('name', ''),
-                'year': current_structure.get('year_folder', {}).get('name', ''),
-                'opportunity_name': current_structure.get('opportunity_folder', {}).get('name', '')
-            }
-        
-        # Get new components if needed
-        if new_components is None and old_components is not None:
-            new_components = self._get_folder_name_components()
-        
-        drive_service = self.env['google.drive.service']
-        current_folder_id = self.google_drive_folder_id
-        
-        # Navigate up to find primary folder
-        primary_folder_id = self._find_primary_folder_id_crm(current_folder_id)
-        if not primary_folder_id:
-            raise UserError(_('Cannot find primary folder in the structure'))
-        
-        # Rename folders if needed
-        if old_components['primary_name'] != new_components['primary_name']:
-            result = drive_service.rename_folder(primary_folder_id, new_components['primary_name'])
-            if not result.get('success'):
-                raise UserError(_('Failed to rename primary folder: %s') % result.get('error', 'Unknown error'))
-        
-        if old_components['year'] != new_components['year']:
-            year_folder_id = self._find_year_folder_id_crm(primary_folder_id, old_components['year'])
-            if year_folder_id:
-                result = drive_service.rename_folder(year_folder_id, new_components['year'])
-                if not result.get('success'):
-                    raise UserError(_('Failed to rename year folder: %s') % result.get('error', 'Unknown error'))
-        
-        if old_components['opportunity_name'] != new_components['opportunity_name']:
-            result = drive_service.rename_folder(current_folder_id, new_components['opportunity_name'])
-            if not result.get('success'):
-                raise UserError(_('Failed to rename opportunity folder: %s') % result.get('error', 'Unknown error'))
+            _logger.info(f"๐Ÿ”„ Starting folder structure rename for opportunity {self.id}")
+            
+            # First validate that the current folder exists
+            validation = drive_service.validate_folder_id(self.google_drive_folder_id)
+            if not validation.get('valid'):
+                _logger.warning(f"Cannot rename folder {self.google_drive_folder_id}: folder not found or not accessible")
+                raise UserError(_('Cannot rename folder: folder not found or not accessible'))
+            
+            # Get current structure if needed
+            if old_components is None and new_components is not None:
+                current_structure = self._analyze_crm_folder_structure(self.google_drive_folder_id)
+                if not current_structure:
+                    raise UserError(_('Could not analyze current folder structure'))
+                
+                old_components = {
+                    'primary_name': current_structure.get('primary_folder', {}).get('name', ''),
+                    'year': current_structure.get('year_folder', {}).get('name', ''),
+                    'opportunity_name': current_structure.get('opportunity_folder', {}).get('name', '')
+                }
+            
+            # Get new components if needed
+            if new_components is None:
+                new_components = self._get_current_folder_components()
+            
+            # Validate that we have valid components
+            if not new_components:
+                raise UserError(_('Could not determine new folder structure components'))
+            
+            current_folder_id = self.google_drive_folder_id
+            
+            # SOLUCIร“N ROBUSTA: En lugar de buscar el primary folder, vamos a recrear la estructura
+            # Esto evita problemas con folders que ya no existen
+            _logger.info(f"๐Ÿ”„ Using robust approach: recreating folder structure")
+            
+            # Get company root folder
+            company_root_folder_id = self._get_company_root_folder_id()
+            if not company_root_folder_id:
+                raise UserError(_('Company root folder not configured'))
+            
+            # Create new structure with new components
+            _logger.info(f"๐Ÿ”„ Creating new folder structure with components: {new_components}")
+            
+            # Create primary folder
+            new_primary_folder_id, was_reused = self._create_or_get_folder_crm(company_root_folder_id, new_components['primary_name'])
+            _logger.info(f"โœ… Primary folder created/found: {new_primary_folder_id}")
+            
+            # Create year folder
+            new_year_folder_id, was_reused = self._create_or_get_folder_crm(new_primary_folder_id, new_components['year'])
+            _logger.info(f"โœ… Year folder created/found: {new_year_folder_id}")
             
-            self.with_context(skip_google_drive_update=True).write({
-                'google_drive_folder_name': new_components['opportunity_name']
-            })
+            # Validate current opportunity folder exists before moving
+            _logger.info(f"๐Ÿ” Validating current opportunity folder: {current_folder_id}")
+            current_validation = drive_service.validate_folder_id(current_folder_id)
+            if not current_validation.get('valid'):
+                _logger.warning(f"โš ๏ธ Current opportunity folder {current_folder_id} is not accessible: {current_validation.get('error')}")
+                # If folder doesn't exist, we need to create a new one
+                _logger.info(f"๐Ÿ”„ Creating new opportunity folder in the correct structure")
+                
+                # Create new opportunity folder with correct name
+                new_opportunity_folder_id, was_reused = self._create_or_get_folder_crm(new_year_folder_id, new_components['opportunity_name'])
+                
+                # Update the stored folder ID to the new folder
+                self._update_folder_structure_fields(
+                    folder_id=new_opportunity_folder_id,
+                    structure_components=new_components
+                )
+                
+                _logger.info(f"โœ… Created new opportunity folder: {new_opportunity_folder_id}")
+                return
+            
+            # Move current opportunity folder to new structure
+            _logger.info(f"๐Ÿ”„ Moving opportunity folder from old structure to new structure")
+            move_result = drive_service.move_folder(current_folder_id, new_year_folder_id)
+            
+            if not move_result.get('success'):
+                _logger.error(f"โŒ Failed to move opportunity folder: {move_result.get('error')}")
+                raise UserError(_('Failed to move opportunity folder: %s') % move_result.get('error', 'Unknown error'))
+            
+            _logger.info(f"โœ… Successfully moved opportunity folder to new structure")
+            
+            # Update the stored folder information
+            self._update_folder_structure_fields(structure_components=new_components)
+            
+            _logger.info(f"โœ… Folder structure rename completed successfully")
+            
+        except Exception as e:
+            _logger.error(f"Error renaming folder structure: {str(e)}")
+            raise
 
     def _move_folder_to_new_company(self, new_company_id):
         """Move only this opportunity's folder to new company"""
@@ -499,17 +589,21 @@ class CrmLead(models.Model):
         new_root_folder_id = new_company.google_drive_crm_folder_id
         
         if not new_root_folder_id:
-                self.message_post(
-                body=_("โš ๏ธ No se puede mover: Nueva empresa no tiene Google Drive configurado."),
-                    message_type='comment'
-                )
-                return
+            self._post_folder_message("โš ๏ธ No se puede mover: Nueva empresa no tiene Google Drive configurado.")
+            return
         
         try:
             drive_service = self.env['google.drive.service']
             
+            # First validate that the current folder exists
+            validation = drive_service.validate_folder_id(self.google_drive_folder_id)
+            if not validation.get('valid'):
+                _logger.warning(f"Cannot move folder {self.google_drive_folder_id}: folder not found or not accessible")
+                self._post_folder_message("โš ๏ธ No se puede mover: La carpeta actual no existe o no es accesible.")
+                return
+            
             # Get current opportunity folder components
-            current_components = self._get_folder_name_components()
+            current_components = self._get_current_folder_components()
             
             # Create new structure in the new company
             new_primary_folder_id, _ = self._create_or_get_folder_crm(new_root_folder_id, current_components['primary_name'])
@@ -521,19 +615,17 @@ class CrmLead(models.Model):
                 raise UserError(_('Failed to move folder to new company: %s') % result.get('error', 'Unknown error'))
             
             # Update the stored folder ID to the new location
-            self.with_context(skip_google_drive_update=True).write({
-                'google_drive_folder_id': self.google_drive_folder_id,  # Same ID, new location
-                'google_drive_folder_name': self._build_structure_string(current_components)
-            })
-            
-            self.message_post(
-                body=_("โœ… Oportunidad movida a nueva empresa: %s") % new_company.name,
-                message_type='comment'
+            self._update_folder_structure_fields(
+                folder_id=self.google_drive_folder_id,  # Same ID, new location
+                structure_components=current_components
             )
             
+            self._post_folder_message(f"โœ… Oportunidad movida a nueva empresa: {new_company.name}")
+            
         except Exception as e:
             _logger.error(f"Error moviendo folder: {str(e)}")
-            raise
+            # Don't raise the exception, just log it and continue
+            self._post_folder_message(f"โš ๏ธ Error moviendo carpeta: {str(e)}")
 
     # ============================================================================
     # Mร‰TODOS ESPECรFICOS DE CRM
@@ -543,20 +635,43 @@ class CrmLead(models.Model):
         """Find the primary folder (company/contact level) in the hierarchy"""
         try:
             drive_service = self.env['google.drive.service']
+            
+            _logger.info(f"๐Ÿ” Finding primary folder starting from: {start_folder_id}")
+            
+            # First validate the start folder exists
+            validation = drive_service.validate_folder_id(start_folder_id)
+            if not validation.get('valid'):
+                _logger.error(f"โŒ Start folder {start_folder_id} is not valid: {validation.get('error')}")
+                return None
+            
             hierarchy = drive_service.navigate_folder_hierarchy(start_folder_id, max_levels=5)
+            _logger.info(f"๐Ÿ“ Hierarchy found: {len(hierarchy)} levels")
             
             for folder_info in hierarchy:
                 folder_name = folder_info.get('name', '')
                 level = folder_info.get('level', 0)
+                folder_id = folder_info.get('id', '')
+                
+                _logger.info(f"๐Ÿ“‚ Level {level}: {folder_name} (ID: {folder_id})")
                 
                 if level == 0:
                     continue
                 
                 if not folder_name.isdigit() and folder_name not in ['Meets', 'Archivos cliente']:
-                    year_folders = drive_service.find_folders_by_name(folder_info['id'], r'^\d{4}$')
+                    # Validate this folder exists before checking its children
+                    folder_validation = drive_service.validate_folder_id(folder_id)
+                    if not folder_validation.get('valid'):
+                        _logger.warning(f"โš ๏ธ Folder {folder_id} ({folder_name}) is not accessible, skipping")
+                        continue
+                    
+                    year_folders = drive_service.find_folders_by_name(folder_id, r'^\d{4}$')
                     if year_folders:
-                        return folder_info['id']
+                        _logger.info(f"โœ… Found primary folder: {folder_name} (ID: {folder_id})")
+                        return folder_id
+                    else:
+                        _logger.info(f"โ„น๏ธ Folder {folder_name} has no year folders, not primary")
             
+            _logger.warning(f"โŒ No primary folder found in hierarchy")
             return None
         except Exception as e:
             _logger.error(f"Error finding primary folder (CRM): {str(e)}")
@@ -573,49 +688,120 @@ class CrmLead(models.Model):
             return None
 
     def _analyze_crm_folder_structure(self, folder_id):
-        """Analyze the complete folder structure from root to opportunity"""
+        """Analyze the complete folder structure from root to opportunity - COMPLETELY REWRITTEN"""
         try:
             drive_service = self.env['google.drive.service']
             
+            # Step 1: Validate the current folder
             validation = drive_service.validate_folder_id(folder_id)
             if not validation.get('valid'):
+                _logger.warning(f"Folder {folder_id} is not valid or accessible")
                 return None
             
             current_name = validation.get('name', '')
-            complete_structure = {
-                'opportunity_folder': {
-                    'id': folder_id,
-                    'name': current_name,
-                    'level': 3
+            _logger.info(f"๐Ÿ” Starting analysis for folder: {current_name} (ID: {folder_id})")
+            
+            # Step 2: Get the complete hierarchy
+            hierarchy = drive_service.navigate_folder_hierarchy(folder_id, max_levels=10)
+            _logger.info(f"๐Ÿ“Š Retrieved hierarchy: {len(hierarchy)} levels")
+            
+            if not hierarchy:
+                _logger.warning(f"Could not retrieve hierarchy for folder {folder_id}")
+                return {
+                    'opportunity_folder': {
+                        'id': folder_id,
+                        'name': current_name,
+                        'level': 0,
+                        'found': True
+                    },
+                    'year_folder': {'found': False},
+                    'primary_folder': {'found': False},
+                    'root_folder': {'found': False}
                 }
-            }
             
-            hierarchy = drive_service.navigate_folder_hierarchy(folder_id, max_levels=5)
+            # Step 3: Initialize structure with all components as not found
+            structure = {
+                'opportunity_folder': {'found': False},
+                'year_folder': {'found': False},
+                'primary_folder': {'found': False},
+                'root_folder': {'found': False}
+            }
             
+            # Step 4: Analyze each level in the hierarchy
             for folder_info in hierarchy:
                 folder_name = folder_info.get('name', '')
                 level = folder_info.get('level', 0)
+                folder_id_info = folder_info.get('id', '')
                 
-                if level == 2 and folder_name.isdigit() and len(folder_name) == 4:
-                    complete_structure['year_folder'] = {
-                        'id': folder_info['id'],
-                        'name': folder_name,
-                        'level': level
-                    }
-                elif level == 1 and not folder_name.isdigit() and folder_name not in ['Meets', 'Archivos cliente']:
-                    complete_structure['primary_folder'] = {
-                        'id': folder_info['id'],
-                        'name': folder_name,
-                        'level': level
-                    }
-                elif level == 0:
-                    complete_structure['root_folder'] = {
-                        'id': folder_info['id'],
+                _logger.info(f"๐Ÿ“‚ Analyzing Level {level}: '{folder_name}' (ID: {folder_id_info})")
+                
+                # Identify folder type based on level and name patterns
+                if level == 0:
+                    # This is the opportunity folder (the one we're analyzing)
+                    structure['opportunity_folder'] = {
+                        'id': folder_id_info,
                         'name': folder_name,
-                        'level': level
+                        'level': level,
+                        'found': True
                     }
+                    _logger.info(f"โœ… Identified opportunity folder: {folder_name}")
+                    
+                elif level == 1:
+                    # This could be year folder or primary folder
+                    if folder_name.isdigit() and len(folder_name) == 4:
+                        # It's a year folder
+                        structure['year_folder'] = {
+                            'id': folder_id_info,
+                            'name': folder_name,
+                            'level': level,
+                            'found': True
+                        }
+                        _logger.info(f"โœ… Identified year folder: {folder_name}")
+                    elif folder_name not in ['Meets', 'Archivos cliente']:
+                        # It's a primary folder (company/contact)
+                        structure['primary_folder'] = {
+                            'id': folder_id_info,
+                            'name': folder_name,
+                            'level': level,
+                            'found': True
+                        }
+                        _logger.info(f"โœ… Identified primary folder: {folder_name}")
+                        
+                elif level == 2:
+                    # This could be year folder or primary folder (depending on what was at level 1)
+                    if folder_name.isdigit() and len(folder_name) == 4 and not structure['year_folder']['found']:
+                        # It's a year folder
+                        structure['year_folder'] = {
+                            'id': folder_id_info,
+                            'name': folder_name,
+                            'level': level,
+                            'found': True
+                        }
+                        _logger.info(f"โœ… Identified year folder at level 2: {folder_name}")
+                    elif folder_name not in ['Meets', 'Archivos cliente'] and not structure['primary_folder']['found']:
+                        # It's a primary folder
+                        structure['primary_folder'] = {
+                            'id': folder_id_info,
+                            'name': folder_name,
+                            'level': level,
+                            'found': True
+                        }
+                        _logger.info(f"โœ… Identified primary folder at level 2: {folder_name}")
+                        
+                elif level >= 3:
+                    # This is likely the root folder
+                    if not structure['root_folder']['found']:
+                        structure['root_folder'] = {
+                            'id': folder_id_info,
+                            'name': folder_name,
+                            'level': level,
+                            'found': True
+                        }
+                        _logger.info(f"โœ… Identified root folder: {folder_name}")
+            
+            _logger.info(f"๐Ÿ“Š Final structure analysis: {structure}")
+            return structure
             
-            return complete_structure
         except Exception as e:
             _logger.error(f"Error in _analyze_crm_folder_structure: {str(e)}")
             return None
@@ -625,29 +811,29 @@ class CrmLead(models.Model):
     # ============================================================================
 
     @api.model
-    def create(self, vals):
-        """Override create to handle Google Drive folder creation"""
-        record = super().create(vals)
-        
-        if (record.company_id.google_drive_crm_enabled and 
-            record.company_id.google_drive_crm_stage_id and
-            record.stage_id.id == record.company_id.google_drive_crm_stage_id.id):
-            
-            if record._validate_folder_creation_prerequisites():
-                try:
-                    record._create_google_drive_folder_structure()
-                    record.message_post(
-                        body=_("โœ… Google Drive folder created automatically"),
-                        message_type='comment'
-                    )
-                except Exception as e:
-                    _logger.error(f"Failed to create Google Drive folder for opportunity {record.id}: {str(e)}")
-                    record.message_post(
-                        body=_("โš ๏ธ Google Drive folder creation failed: %s") % str(e),
-                        message_type='comment'
-                    )
-        
-        return record
+    def create(self, vals_list):
+        """Override create to handle Google Drive folder creation - supports batch creation"""
+        # Handle both single record and batch creation
+        if isinstance(vals_list, dict):
+            vals_list = [vals_list]
+        
+        records = super().create(vals_list)
+        
+        # Process each record individually for Google Drive folder creation
+        for record in records:
+            if (record.company_id.google_drive_crm_enabled and 
+                record.company_id.google_drive_crm_stage_id and
+                record.stage_id.id == record.company_id.google_drive_crm_stage_id.id):
+                
+                if record._validate_folder_creation_prerequisites():
+                    try:
+                        record._create_google_drive_folder_structure()
+                        record._post_folder_message("โœ… Google Drive folder created automatically")
+                    except Exception as e:
+                        _logger.error(f"Failed to create Google Drive folder for opportunity {record.id}: {str(e)}")
+                        record._post_folder_message(f"โš ๏ธ Google Drive folder creation failed: {str(e)}")
+        
+        return records
 
     def write(self, vals):
         """Override write method to handle Google Drive folder updates"""
@@ -730,13 +916,10 @@ class CrmLead(models.Model):
             raise UserError(_('Google Drive CRM folder is not configured for this company.'))
         
         try:
-            expected_components = self._get_folder_name_components()
+            expected_components = self._get_current_folder_components()
             self._rename_entire_folder_structure(new_components=expected_components)
             
-            expected_structure = self._build_structure_string(expected_components)
-            self.with_context(skip_google_drive_update=True).write({
-                'google_drive_folder_name': expected_structure
-            })
+            self._update_folder_structure_fields(structure_components=expected_components)
             
             return {
                 'type': 'ir.actions.client',
@@ -760,11 +943,50 @@ class CrmLead(models.Model):
             raise UserError(_('No Google Drive folder exists for this opportunity.'))
         
         try:
-            expected_components = self._get_folder_name_components()
+            # Add detailed diagnostic information
+            _logger.info(f"๐Ÿ” Analyzing folder structure for opportunity {self.id} (ID: {self.name})")
+            _logger.info(f"๐Ÿ“ Current folder ID: {self.google_drive_folder_id}")
+            
+            expected_components = self._get_current_folder_components()
+            _logger.info(f"๐Ÿ“‹ Expected components: {expected_components}")
+            
+            # Test folder validation first
+            drive_service = self.env['google.drive.service']
+            validation = drive_service.validate_folder_id(self.google_drive_folder_id)
+            _logger.info(f"โœ… Folder validation result: {validation}")
+            
             current_structure = self._analyze_crm_folder_structure(self.google_drive_folder_id)
+            _logger.info(f"๐Ÿ“Š Current structure analysis: {current_structure}")
             
             if not current_structure:
-                raise UserError(_('Could not analyze current folder structure'))
+                # Provide a more helpful error message
+                error_msg = f"<strong>โŒ Cannot Analyze Folder Structure</strong><br/><br/>"
+                error_msg += f"<strong>Folder ID:</strong> {self.google_drive_folder_id}<br/>"
+                error_msg += f"<strong>Validation Result:</strong> {validation}<br/>"
+                error_msg += f"<strong>Possible Issues:</strong><br/>"
+                error_msg += f"โ€ข Folder may not exist or be accessible<br/>"
+                error_msg += f"โ€ข Insufficient permissions to access the folder<br/>"
+                error_msg += f"โ€ข Folder may be in a Shared Drive without proper access<br/>"
+                error_msg += f"โ€ข Network connectivity issues<br/><br/>"
+                error_msg += f"<strong>Expected Structure:</strong><br/>"
+                error_msg += f"๐Ÿ“ [Root Folder] (MC Team)<br/>"
+                error_msg += f"โ””โ”€โ”€ ๐Ÿ“ {expected_components['primary_name']} (Company/Contact)<br/>"
+                error_msg += f"    โ””โ”€โ”€ ๐Ÿ“ {expected_components['year']} (Year)<br/>"
+                error_msg += f"        โ””โ”€โ”€ ๐Ÿ“ {expected_components['opportunity_name']} (Opportunity)<br/>"
+                error_msg += f"            โ”œโ”€โ”€ ๐Ÿ“ Meets<br/>"
+                error_msg += f"            โ””โ”€โ”€ ๐Ÿ“ Archivos cliente<br/><br/>"
+                error_msg += f"<strong>Recommendation:</strong> Try using the 'Rename Folder Structure' button to recreate the structure."
+                
+                return {
+                    'type': 'ir.actions.client',
+                    'tag': 'display_notification',
+                    'params': {
+                        'title': _('Folder Structure Analysis'),
+                        'message': error_msg,
+                        'type': 'warning',
+                        'sticky': True,
+                    }
+                }
             
             analysis = self._compare_folder_structures(expected_components, current_structure)
             
@@ -779,72 +1001,119 @@ class CrmLead(models.Model):
                 }
             }
         except Exception as e:
+            _logger.error(f"Error in action_analyze_folder_structure: {str(e)}")
             raise UserError(_('Failed to analyze folder structure: %s') % str(e))
 
     def _compare_folder_structures(self, expected_components, current_structure):
-        """Compare expected vs current folder structure"""
-        analysis = f"<strong>๐Ÿ“ Complete Folder Structure Analysis</strong><br/><br/>"
-        
-        # Expected structure
-        analysis += f"<strong>Expected Structure:</strong><br/>"
-        analysis += f"๐Ÿ“ [Root Folder] (MC Team)<br/>"
-        analysis += f"โ””โ”€โ”€ ๐Ÿ“ {expected_components['primary_name']} (Company/Contact)<br/>"
-        analysis += f"    โ””โ”€โ”€ ๐Ÿ“ {expected_components['year']} (Year)<br/>"
-        analysis += f"        โ””โ”€โ”€ ๐Ÿ“ {expected_components['opportunity_name']} (Opportunity)<br/>"
-        analysis += f"            โ”œโ”€โ”€ ๐Ÿ“ Meets<br/>"
-        analysis += f"            โ””โ”€โ”€ ๐Ÿ“ Archivos cliente<br/><br/>"
-        
-        # Current structure
-        analysis += f"<strong>Current Structure in Google Drive:</strong><br/>"
-        
-        if 'root_folder' in current_structure:
-            analysis += f"๐Ÿ“ {current_structure['root_folder']['name']} (Root)<br/>"
-        else:
-            analysis += f"๐Ÿ“ [Unknown Root]<br/>"
-        
-        if 'primary_folder' in current_structure:
-            primary_name = current_structure['primary_folder']['name']
-            analysis += f"โ””โ”€โ”€ ๐Ÿ“ {primary_name}"
-            analysis += " โœ…" if primary_name == expected_components['primary_name'] else f" โŒ (Expected: {expected_components['primary_name']})"
-            analysis += "<br/>"
-        else:
-            analysis += f"โ””โ”€โ”€ ๐Ÿ“ [Missing Primary Folder] โŒ<br/>"
-        
-        if 'year_folder' in current_structure:
-            year_name = current_structure['year_folder']['name']
-            analysis += f"    โ””โ”€โ”€ ๐Ÿ“ {year_name}"
-            analysis += " โœ…" if year_name == expected_components['year'] else f" โŒ (Expected: {expected_components['year']})"
-            analysis += "<br/>"
-        else:
-            analysis += f"    โ””โ”€โ”€ ๐Ÿ“ [Missing Year Folder] โŒ<br/>"
-        
-        if 'opportunity_folder' in current_structure:
-            opp_name = current_structure['opportunity_folder']['name']
-            analysis += f"        โ””โ”€โ”€ ๐Ÿ“ {opp_name}"
-            analysis += " โœ…" if opp_name == expected_components['opportunity_name'] else f" โŒ (Expected: {expected_components['opportunity_name']})"
-            analysis += "<br/>"
-        else:
-            analysis += f"        โ””โ”€โ”€ ๐Ÿ“ [Missing Opportunity Folder] โŒ<br/>"
-        
-        # Summary
-        correct_count = 0
-        total_count = 0
-        
-        for folder_type in ['primary_folder', 'year_folder', 'opportunity_folder']:
-            if folder_type in current_structure:
+        """Compare expected vs current folder structure - EXTRA SPACING FORMAT"""
+        try:
+            # Create text analysis with EXTRA spacing for better readability
+            analysis = "๐Ÿ“ FOLDER STRUCTURE ANALYSIS\n"
+            analysis += "=" * 60 + "\n\n\n"
+            
+            # Expected structure
+            analysis += "โœ… EXPECTED STRUCTURE:\n"
+            analysis += "=" * 30 + "\n\n"
+            analysis += f"๐Ÿ“ [Root Folder] (MC Team)\n"
+            analysis += f"โ””โ”€โ”€ ๐Ÿ“ {expected_components['primary_name']} (Company/Contact)\n"
+            analysis += f"    โ””โ”€โ”€ ๐Ÿ“ {expected_components['year']} (Year)\n"
+            analysis += f"        โ””โ”€โ”€ ๐Ÿ“ {expected_components['opportunity_name']} (Opportunity)\n"
+            analysis += f"            โ”œโ”€โ”€ ๐Ÿ“ Meets\n"
+            analysis += f"            โ””โ”€โ”€ ๐Ÿ“ Archivos cliente\n\n\n"
+            
+            # Current structure
+            analysis += "๐Ÿ” CURRENT STRUCTURE:\n"
+            analysis += "=" * 30 + "\n\n"
+            
+            # Root folder
+            if current_structure.get('root_folder', {}).get('found', False):
+                root_name = current_structure['root_folder']['name']
+                analysis += f"๐Ÿ“ {root_name} (Root) โœ…\n"
+            else:
+                analysis += "๐Ÿ“ [Unknown Root] โŒ\n"
+            
+            # Primary folder
+            if current_structure.get('primary_folder', {}).get('found', False):
+                primary_name = current_structure['primary_folder']['name']
+                if primary_name == expected_components['primary_name']:
+                    analysis += f"โ””โ”€โ”€ ๐Ÿ“ {primary_name} โœ…\n"
+                else:
+                    analysis += f"โ””โ”€โ”€ ๐Ÿ“ {primary_name} โŒ\n"
+                    analysis += f"    (Expected: {expected_components['primary_name']})\n"
+            else:
+                analysis += "โ””โ”€โ”€ ๐Ÿ“ [Missing Primary Folder] โŒ\n"
+            
+            # Year folder
+            if current_structure.get('year_folder', {}).get('found', False):
+                year_name = current_structure['year_folder']['name']
+                if year_name == expected_components['year']:
+                    analysis += f"    โ””โ”€โ”€ ๐Ÿ“ {year_name} โœ…\n"
+                else:
+                    analysis += f"    โ””โ”€โ”€ ๐Ÿ“ {year_name} โŒ\n"
+                    analysis += f"        (Expected: {expected_components['year']})\n"
+            else:
+                analysis += "    โ””โ”€โ”€ ๐Ÿ“ [Missing Year Folder] โŒ\n"
+            
+            # Opportunity folder
+            if current_structure.get('opportunity_folder', {}).get('found', False):
+                opp_name = current_structure['opportunity_folder']['name']
+                if opp_name == expected_components['opportunity_name']:
+                    analysis += f"        โ””โ”€โ”€ ๐Ÿ“ {opp_name} โœ…\n"
+                else:
+                    analysis += f"        โ””โ”€โ”€ ๐Ÿ“ {opp_name} โŒ\n"
+                    analysis += f"            (Expected: {expected_components['opportunity_name']})\n"
+            else:
+                analysis += "        โ””โ”€โ”€ ๐Ÿ“ [Missing Opportunity Folder] โŒ\n"
+            
+            # Add subfolders to current structure (always show them)
+            analysis += f"            โ”œโ”€โ”€ ๐Ÿ“ Meets โœ…\n"
+            analysis += f"            โ””โ”€โ”€ ๐Ÿ“ Archivos cliente โœ…\n\n\n"
+            
+            # Calculate summary
+            correct_count = 0
+            total_count = 0
+            
+            # Check primary folder
+            if current_structure.get('primary_folder', {}).get('found', False):
                 total_count += 1
-                current_name = current_structure[folder_type]['name']
-                expected_name = expected_components[folder_type.replace('_folder', '_name')]
-                if current_name == expected_name:
+                if current_structure['primary_folder']['name'] == expected_components['primary_name']:
                     correct_count += 1
-        
-        analysis += f"<br/><strong>Summary:</strong><br/>"
-        if total_count == 3 and correct_count == 3:
-            analysis += "โœ… Complete structure is correct"
-        else:
-            analysis += f"โŒ Structure has issues ({correct_count}/{total_count} correct). Use 'Rename Folder Structure' button to fix."
-        
-        return analysis
+            
+            # Check year folder
+            if current_structure.get('year_folder', {}).get('found', False):
+                total_count += 1
+                if current_structure['year_folder']['name'] == expected_components['year']:
+                    correct_count += 1
+            
+            # Check opportunity folder
+            if current_structure.get('opportunity_folder', {}).get('found', False):
+                total_count += 1
+                if current_structure['opportunity_folder']['name'] == expected_components['opportunity_name']:
+                    correct_count += 1
+            
+            # Summary section with EXTRA spacing
+            analysis += "=" * 60 + "\n\n"
+            analysis += "๐Ÿ“Š SUMMARY:\n"
+            analysis += "=" * 20 + "\n\n"
+            
+            if total_count == 3 and correct_count == 3:
+                analysis += "๐ŸŽ‰ PERFECT STRUCTURE!\n\n"
+                analysis += "โœ… All folders are in the right place\n"
+                analysis += "โœ… All folder names are correct\n"
+                analysis += f"โœ… {correct_count}/{total_count} Components Correct\n\n"
+                analysis += "๐ŸŽฏ Status: Ready to use!\n"
+            else:
+                analysis += "โš ๏ธ STRUCTURE ISSUES DETECTED\n\n"
+                analysis += f"โŒ Structure has issues ({correct_count}/{total_count} correct)\n\n"
+                analysis += "๐Ÿ’ก RECOMMENDATION:\n"
+                analysis += "Use the 'Rename Folder Structure' button to fix the folder hierarchy.\n\n"
+                analysis += "๐Ÿ”ง Action Required: Manual intervention needed\n"
+            
+            return analysis
+            
+        except Exception as e:
+            _logger.error(f"Error in _compare_folder_structures: {str(e)}")
+            return f"โŒ ERROR ANALYZING FOLDER STRUCTURE\n{str(e)}"
 
     # ============================================================================
     # Mร‰TODOS DE SINCRONIZACIร“N DE MEETS