Bläddra i källkod

Update whatsapp_web and whatsapp_web_groups modules

odoo 1 månad sedan
förälder
incheckning
d7900d0e4b
1 ändrade filer med 299 tillägg och 216 borttagningar
  1. 299 216
      models/ww_group.py

+ 299 - 216
models/ww_group.py

@@ -4,89 +4,104 @@ from datetime import datetime
 
 _logger = logging.getLogger(__name__)
 
+
 class WWGroup(models.Model):
-    _name = 'ww.group'
-    _description = 'Grupo de WhatsApp Web'
+    _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)
+    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',
+        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'))
-        
+        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')
-            
+            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
+            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)
+            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))
+            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"
+            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<blockquote><strong>{quoted_author_name}:</strong> {msg_data['quotedMsg']['body']}</blockquote>"
 
             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,
+                "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)
+            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:
@@ -94,73 +109,224 @@ class WWGroup(models.Model):
 
             # 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 self.contact_ids:
+                for contact in self.contact_ids:
+                    if contact and contact.id:
+                        partner_ids.append(contact.id)
+
+            # Create the channel using standard create method for maximum robustness
+            _logger.info(f"WWGroup: Creating channel via create() for {self.name}")
+
+            # Default values for a public channel in WhatsApp section
+            # We use channel_type='whatsapp' so it appears in the correct UI section
+            channel_vals = {
+                "name": channel_name,
+                "channel_type": "whatsapp",
+                "whatsapp_number": self.whatsapp_web_id,  # @g.us ID
+                "wa_account_id": self.whatsapp_account_id.id,
+                "group_public_id": None,
+            }
 
-            if not partner_ids:
-                _logger.warning(f"No se encontraron IDs válidos de contactos para el grupo {self.name}")
-                return False
+            channel = self.env["discuss.channel"].create(channel_vals)
+            _logger.info(f"WWGroup: WhatsApp Channel created with ID {channel.id}")
+
+            # Add members to the channel if any
+            # AUTOMATICALLY ADD system user / account owner so they can see the channel
+            if self.whatsapp_account_id and self.whatsapp_account_id.create_uid:
+                owner_partner = self.whatsapp_account_id.create_uid.partner_id
+                if owner_partner and owner_partner.id not in partner_ids:
+                    partner_ids.append(owner_partner.id)
+
+            if partner_ids:
+                _logger.info(
+                    f"WWGroup: Adding {len(partner_ids)} members to channel {channel.id}"
+                )
+                channel.add_members(partner_ids=partner_ids)
 
-            # 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})
+            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)}")
+            _logger.error(
+                f"Error al crear el canal para el grupo {self.name}: {str(e)}",
+                exc_info=True,
+            )
             return False
 
-    def _update_discussion_channel(self):
-        """Update the discussion channel members"""
-        self.ensure_one()
-        
+        except Exception as e:
+            _logger.error(
+                f"Error al crear el canal para el grupo {self.name}: {str(e)}"
+            )
+            return False
+
+    def process_webhook_group(self, account, group_metadata, sender_partner=False):
+        """
+        Process group metadata from webhook to Create/Update group and handle members gracefully.
+        Args:
+            account: whatsapp.account record
+            group_metadata: dict {'id':..., 'name':...}
+            sender_partner: res.partner record (the sender of the message)
+        Returns:
+            ww.group record or False
+        """
         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)
+            group_id = group_metadata.get("id")
+            group_name = group_metadata.get("name", "Sin nombre")
 
-            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
+            if not group_id:
+                return False
+
+            # 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,
+                    }
+                )
+            else:
+                # Actualizar nombre del grupo si cambió y no es el default
+                if group.name != group_name and group_name != "Sin nombre":
+                    group.write({"name": group_name})
+                    # Actualizar nombre del canal si existe
+                    if group.channel_id:
+                        group.channel_id.write({"name": f"📱 {group_name}"})
+
+            # Organic Member Growth: Add sender to group if not present
+            if sender_partner:
+                if sender_partner.id not in group.contact_ids.ids:
+                    _logger.info(
+                        f"Adding sender {sender_partner.name} to group {group.name}"
+                    )
+                    group.write({"contact_ids": [(4, sender_partner.id)]})
+
+            # Ensure channel exists (Lazy creation)
+            if not group.channel_id:
+                group._create_discussion_channel()
+
+            # Organic Member Growth: Add sender to channel if not present
+            if group.channel_id and sender_partner:
+                # Check membership (this check might be redundant as add_members handles duplication, but safe)
+                group.channel_id.add_members(partner_ids=[sender_partner.id])
+
+            return group
 
-            # 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)}")
+            _logger.error(f"Error processing webhook group {group_metadata}: {e}")
+            return False
+
+    def process_group_data(self, account, group_data):
+        """
+        Process a single group data (from API SYNC): create/update group, sync participants.
+        Returns the group object or False.
+        """
+        try:
+            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,
+                    }
+                )
+            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"
+            )
+
+            for participant in participants:
+                whatsapp_web_id = participant.get("id", {}).get("_serialized")
+                mobile = participant.get("number", "")
+
+                # 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
+            if not group.channel_id:
+                group._create_discussion_channel()
+            else:
+                group._update_discussion_channel()
+
+            # Process messages if available
+            messages = group_data.get("messages", [])
+            if messages:
+                group._process_messages(messages)
+
+            return group
+
+        except Exception as e:
+            _logger.error(
+                f"Error procesando datos del grupo {group_data.get('name')}: {str(e)}"
+            )
             return False
 
     @api.model
@@ -170,162 +336,79 @@ class WWGroup(models.Model):
         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([])
-        
+        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)
+                    self.process_group_data(account, group_data)
 
             except Exception as e:
-                _logger.error("Error en la sincronización de grupos para la cuenta %s: %s", account.name, str(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)]
-            })
-        
+            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',
+            "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
-            
+            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)
-        
+            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,
-            }
-        } 
+            "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,
+            },
+        }