from odoo import models, fields, api import logging from datetime import datetime _logger = logging.getLogger(__name__) class WWGroup(models.Model): _name = 'ww.group' _description = 'Grupo de WhatsApp Web' name = fields.Char(string='Nombre del Grupo', required=True) whatsapp_web_id = fields.Char(string='ID WhatsApp Web', index=True, help='ID único del grupo en WhatsApp Web') whatsapp_account_id = fields.Many2one('whatsapp.account', string='Cuenta de WhatsApp', required=True) channel_id = fields.Many2one('discuss.channel', string='Canal de Discusión', readonly=True) contact_ids = fields.Many2many( comodel_name='res.partner', relation='ww_group_contact_rel', column1='group_id', column2='contact_id', string='Contactos', readonly=True, ) def _process_messages(self, messages_data): """Process WhatsApp messages and create them in the channel""" self.ensure_one() if not messages_data or not self.channel_id: return True # Get existing message IDs to avoid duplicates existing_ids = set(self.channel_id.message_ids.mapped('message_id')) # Prepare bulk create values message_vals_list = [] for msg_data in messages_data: msg_id = msg_data.get('id', {}).get('_serialized') # Skip if message already exists if msg_id in existing_ids: continue # Get author partner author_whatsapp_id = msg_data.get('author') author = self.env['res.partner'].search([ ('whatsapp_web_id', '=', author_whatsapp_id) ], limit=1) if author_whatsapp_id else False # Get quoted message author if exists quoted_author = False if msg_data.get('hasQuotedMsg') and msg_data.get('quotedParticipant'): quoted_author = self.env['res.partner'].search([ ('whatsapp_web_id', '=', msg_data['quotedParticipant']) ], limit=1) # Convert timestamp to datetime timestamp = datetime.fromtimestamp(msg_data.get('timestamp', 0)) # Prepare message body with author and content author_name = author.name if author else "Desconocido" message_body = f"{msg_data.get('body', '')}" # Add quoted message if exists if msg_data.get('hasQuotedMsg') and msg_data.get('quotedMsg', {}).get('body'): quoted_author_name = quoted_author.name if quoted_author else "Desconocido" message_body += f"\n\n
{quoted_author_name}: {msg_data['quotedMsg']['body']}" message_vals = { 'model': 'discuss.channel', 'res_id': self.channel_id.id, 'message_type': 'comment', 'subtype_id': self.env.ref('mail.mt_comment').id, 'body': message_body, 'date': timestamp, 'author_id': author.id if author else self.env.user.partner_id.id, 'message_id': msg_id, } message_vals_list.append(message_vals) # Bulk create messages if message_vals_list: self.env['mail.message'].create(message_vals_list) return True def _create_discussion_channel(self): """Create a discussion channel for the WhatsApp group""" self.ensure_one() try: # Verificar si ya existe un canal para este grupo if self.channel_id: return self.channel_id # Create channel name with WhatsApp prefix channel_name = f"📱 {self.name}" # Verificar que hay contactos if not self.contact_ids: _logger.warning(f"No hay contactos para crear el canal del grupo {self.name} - saltando creación de canal") # No crear canal pero no fallar, permitir que el grupo exista return False # Obtener los IDs de los contactos de forma segura partner_ids = [] for contact in self.contact_ids: if contact and contact.id: partner_ids.append(contact.id) if not partner_ids: _logger.warning(f"No se encontraron IDs válidos de contactos para el grupo {self.name}") return False # Create the channel using channel_create channel = self.env['discuss.channel'].channel_create( name=channel_name, group_id=self.env.user.groups_id[0].id, # Usar el primer grupo del usuario actual ) # Add members to the channel channel.add_members(partner_ids=partner_ids) # Link the channel to the group self.write({'channel_id': channel.id}) return channel except Exception as e: _logger.error(f"Error al crear el canal para el grupo {self.name}: {str(e)}") return False def _update_discussion_channel(self): """Update the discussion channel members""" self.ensure_one() try: # Si no existe el canal, intentar crearlo if not self.channel_id: return self._create_discussion_channel() # Verificar que el canal aún existe channel = self.env['discuss.channel'].browse(self.channel_id.id) if not channel.exists(): _logger.warning(f"El canal para el grupo {self.name} ya no existe, creando uno nuevo") self.write({'channel_id': False}) return self._create_discussion_channel() # Obtener los IDs de los contactos de forma segura partner_ids = [] for contact in self.contact_ids: if contact and contact.id: partner_ids.append(contact.id) if not partner_ids: _logger.warning(f"No hay contactos válidos para actualizar el canal del grupo {self.name} - saltando actualización") # Si no hay contactos, no actualizar pero no fallar return channel # Update channel members using add_members channel.add_members(partner_ids=partner_ids) return channel except Exception as e: _logger.error(f"Error al actualizar el canal para el grupo {self.name}: {str(e)}") return False @api.model def sync_ww_contacts_groups(self): """ Sincroniza los contactos y grupos de WhatsApp Web. Solo sincroniza contactos que están dentro de grupos y valida que no se dupliquen, verificando los últimos 10 dígitos del campo mobile. """ accounts = self.env['whatsapp.account'].search([]) for account in accounts: try: # Obtener grupos usando el método de la cuenta groups_data = account.get_groups() if not groups_data: continue # Procesar cada grupo for group_data in groups_data: group_id = group_data.get('id').get('_serialized') group_name = group_data.get('name', 'Sin nombre') # Buscar o crear grupo group = self.search([ ('whatsapp_web_id', '=', group_id), ('whatsapp_account_id', '=', account.id) ], limit=1) if not group: group = self.create({ 'name': group_name, 'whatsapp_web_id': group_id, 'whatsapp_account_id': account.id }) # Crear canal solo después de procesar participantes # Se hará más abajo si hay contact_ids else: # Actualizar nombre del grupo si cambió if group.name != group_name: group.write({'name': group_name}) # Actualizar nombre del canal si existe if group.channel_id: group.channel_id.write({'name': f"📱 {group_name}"}) # Procesar participantes del grupo participants = group_data.get('members', []) contact_ids = [] # Log para debug _logger.info(f"Procesando grupo {group_name}: {len(participants)} participantes encontrados") if not participants: _logger.warning(f"Grupo {group_name} no tiene participantes en la respuesta de la API") for participant in participants: whatsapp_web_id = participant.get('id', {}).get('_serialized') mobile = participant.get('number', '') is_admin = participant.get('isAdmin', False) is_super_admin = participant.get('isSuperAdmin', False) # Derive participant name participant_name = participant.get('name') or participant.get('pushname') or mobile # Search for existing contact contact = self.env['res.partner'].search([ ('whatsapp_web_id', '=', whatsapp_web_id) ], limit=1) if not contact and mobile and len(mobile) >= 10: last_10_digits = mobile[-10:] contact = self.env['res.partner'].search([ ('mobile', 'like', '%' + last_10_digits) ], limit=1) partner_vals = { 'name': participant_name, 'mobile': mobile, 'whatsapp_web_id': whatsapp_web_id, } if contact: # Update existing contact contact.write(partner_vals) else: # Create new contact contact = self.env['res.partner'].create(partner_vals) if contact: contact_ids.append(contact.id) # Actualizar contactos del grupo group.write({'contact_ids': [(6, 0, contact_ids)]}) # Update discussion channel members solo si hay contactos if contact_ids: # Si es un grupo nuevo sin canal, crear uno if not group.channel_id: group._create_discussion_channel() else: group._update_discussion_channel() else: _logger.info(f"Grupo {group_name} sincronizado sin contactos - no se creará canal de discusión") # Process messages if available messages = group_data.get('messages', []) if messages: group._process_messages(messages) except Exception as e: _logger.error("Error en la sincronización de grupos para la cuenta %s: %s", account.name, str(e)) continue return True def send_whatsapp_message(self, body, attachment=None, wa_template_id=None): """Enviar mensaje WhatsApp al grupo""" self.ensure_one() if not self.whatsapp_account_id: raise ValueError("Group must have a WhatsApp account configured") # Crear mail.message si hay adjunto mail_message = None if attachment: mail_message = self.env['mail.message'].create({ 'body': body, 'attachment_ids': [(4, attachment.id)] }) # Crear mensaje WhatsApp message_vals = { 'body': body, 'recipient_type': 'group', 'whatsapp_group_id': self.id, 'mobile_number': self.whatsapp_web_id, 'wa_account_id': self.whatsapp_account_id.id, 'state': 'outgoing', } if mail_message: message_vals['mail_message_id'] = mail_message.id if wa_template_id: message_vals['wa_template_id'] = wa_template_id whatsapp_message = self.env['whatsapp.message'].create(message_vals) # Enviar mensaje whatsapp_message._send_message() return whatsapp_message def action_send_whatsapp_message(self): """Acción para abrir el composer de WhatsApp para el grupo""" self.ensure_one() return { 'type': 'ir.actions.act_window', 'name': f'Send Message to {self.name}', 'res_model': 'whatsapp.composer', 'view_mode': 'form', 'target': 'new', 'context': { 'default_recipient_type': 'group', 'default_whatsapp_group_id': self.id, 'default_wa_template_id': False, } }