Explorar o código

Merge remote-tracking branch 'origin/develop' into develop

root hai 4 meses
pai
achega
0135327dc1

+ 1 - 0
.gitignore

@@ -50,3 +50,4 @@ htmlcov/
 *.bak
 *.orig
 
+

+ 3 - 2
models/__init__.py

@@ -1,4 +1,5 @@
+from . import whatsapp_account
 from . import whatsapp_message
+from . import whatsapp_composer
 from . import whatsapp_patch
-from . import whatsapp_account
-from . import whatsapp_composer
+from . import mail_message

+ 130 - 0
models/mail_message.py

@@ -0,0 +1,130 @@
+import logging
+import requests
+from odoo import models
+from odoo.addons.mail.tools.discuss import Store
+from odoo.addons.whatsapp.tools.whatsapp_exception import WhatsAppError
+from odoo.exceptions import UserError
+
+_logger = logging.getLogger(__name__)
+
+class MailMessage(models.Model):
+    _inherit = 'mail.message'
+
+    def _message_reaction(self, content, action, partner, guest, store: Store = None):
+        """Sobrescribir para usar WhatsApp Web API Gateway cuando esté configurado"""
+        # Si es mensaje de WhatsApp, verificar si usa WhatsApp Web
+        if self.message_type == "whatsapp_message" and self.wa_message_ids:
+            wa_msg = self.wa_message_ids[0]
+            
+            # Verificar si la cuenta usa WhatsApp Web
+            if wa_msg.wa_account_id and wa_msg.wa_account_id.whatsapp_web_url:
+                # Usar API Gateway para WhatsApp Web
+                self._send_whatsapp_web_reaction(wa_msg, content, action, partner, guest, store)
+                # Actualizar UI directamente usando el método base de mail (sin pasar por enterprise)
+                # Esto evita que el método del enterprise intente enviar de nuevo
+                return self._update_reaction_ui(content, action, partner, guest, store)
+            else:
+                # Usar método original para WhatsApp Business API (enterprise)
+                # Este llamará a super() al final para actualizar la UI
+                return super()._message_reaction(content, action, partner, guest, store)
+        
+        # Para mensajes que no son de WhatsApp, usar método base
+        return super()._message_reaction(content, action, partner, guest, store)
+    
+    def _update_reaction_ui(self, content, action, partner, guest, store: Store = None):
+        """Actualizar la UI de reacciones sin intentar enviar (para WhatsApp Web)"""
+        self.ensure_one()
+        # Buscar reacción existente
+        domain = [
+            ("message_id", "=", self.id),
+            ("partner_id", "=", partner.id),
+            ("guest_id", "=", guest.id),
+            ("content", "=", content),
+        ]
+        reaction = self.env["mail.message.reaction"].search(domain)
+        # Crear/eliminar reacción según la acción
+        if action == "add" and not reaction:
+            create_values = {
+                "message_id": self.id,
+                "content": content,
+                "partner_id": partner.id,
+                "guest_id": guest.id,
+            }
+            self.env["mail.message.reaction"].create(create_values)
+        if action == "remove" and reaction:
+            reaction.unlink()
+        if store:
+            # Llenar el store para usuarios portal no autenticados
+            self._reaction_group_to_store(store, content)
+        # Enviar el grupo de reacciones al bus para usuarios autenticados
+        self._bus_send_reaction_group(content)
+
+    def _send_whatsapp_web_reaction(self, wa_msg, content, action, partner, guest, store: Store = None):
+        """Enviar reacción usando WhatsApp Web API Gateway"""
+        self.ensure_one()
+        
+        account = wa_msg.wa_account_id
+        url = account.whatsapp_web_url
+        session_name = account.whatsapp_web_login
+        api_key = account.whatsapp_web_api_key
+        
+        if not all([url, session_name, api_key]):
+            raise UserError("WhatsApp Web no está completamente configurado. Faltan URL, Login o API Key.")
+        
+        # Manejar reacciones previas (igual que el método original)
+        if action == "add":
+            previous_reaction = self.env["mail.message.reaction"].search([
+                ("message_id", "=", self.id),
+                ("partner_id", "=", partner.id),
+                ("guest_id", "=", guest.id),
+            ], limit=1)
+            if previous_reaction:
+                previous_reaction_emoji = previous_reaction.content
+                if previous_reaction_emoji == content:
+                    return
+                previous_reaction.unlink()
+                self._bus_send_reaction_group(previous_reaction_emoji)
+        
+        # Obtener el ID del mensaje original
+        message_id = wa_msg.msg_uid
+        if not message_id:
+            raise UserError("No se puede enviar reacción: el mensaje no tiene ID válido.")
+        
+        # Construir URL y payload para la API Gateway
+        base_url = url.rstrip('/')
+        endpoint = 'send-reaction'
+        full_url = f"{base_url}/api/v1/{session_name}/{endpoint}"
+        
+        # Determinar emoji (vacío si es remover)
+        emoji = content if action == "add" else ""
+        
+        payload = {
+            "messageId": message_id,
+            "emoji": emoji
+        }
+        
+        headers = {
+            "Content-Type": "application/json",
+            "X-API-Key": api_key
+        }
+        
+        try:
+            _logger.info("Enviando reacción %s al mensaje %s", emoji or "vacía", message_id)
+            response = requests.post(full_url, json=payload, headers=headers, timeout=30)
+            
+            if response.status_code == 200:
+                _logger.info("Reacción enviada exitosamente a WhatsApp Web")
+                # No retornar aquí, dejar que el método padre actualice la UI
+                return
+            else:
+                error_text = response.text
+                _logger.error("Error al enviar reacción. Código: %s, Respuesta: %s", response.status_code, error_text)
+                raise UserError(f"Error al enviar reacción: {error_text}")
+                
+        except requests.exceptions.RequestException as e:
+            _logger.error("Error de conexión al enviar reacción: %s", str(e))
+            raise UserError(f"Error de conexión al enviar reacción: {str(e)}")
+        except Exception as e:
+            _logger.error("Error inesperado al enviar reacción: %s", str(e))
+            raise UserError(f"Error al enviar reacción: {str(e)}")
+

+ 1 - 1
models/whatsapp_account.py

@@ -7,7 +7,7 @@ from odoo import fields, models
 _logger = logging.getLogger(__name__)
 
 class WhatsAppAccount(models.Model):
-    _inherit = ['whatsapp.account']
+    _inherit = 'whatsapp.account'
 
     whatsapp_web_url = fields.Char(string="WhatsApp Web URL", readonly=False, copy=False)
     whatsapp_web_login = fields.Char(string="Login", readonly=False, copy=False)

+ 15 - 45
models/whatsapp_composer.py

@@ -7,33 +7,29 @@ _logger = logging.getLogger(__name__)
 class WhatsAppComposer(models.TransientModel):
     _inherit = 'whatsapp.composer'
 
-    # Campos para soporte de grupos
+    # Campos para soporte básico de grupos (solo por ID string, sin Many2one)
+    # La funcionalidad completa de grupos con Many2one está en whatsapp_web_groups
     recipient_type = fields.Selection([
         ('phone', 'Phone Number'),
         ('group', 'WhatsApp Group')
     ], string='Send To', default='phone', help="Choose recipient type")
     
-    # Campo para ID de grupo como string (más robusto)
+    # Campo para ID de grupo como string
     whatsapp_group_id_char = fields.Char(string='Group ID', 
                                          help="WhatsApp Group ID (e.g., 120363158956331133@g.us)")
     
-    # Campo Many2one opcional si el modelo existe
-    whatsapp_group_id = fields.Many2one('ww.group', string='WhatsApp Group', 
-                                        help="Select WhatsApp group to send message to",
-                                        ondelete='set null')
-    
     # Campo para mensaje libre (sin plantilla)
     body = fields.Html(string='Message Body', help="Free text message (for WhatsApp Web accounts without template)")
     
-    @api.constrains('recipient_type', 'phone', 'whatsapp_group_id', 'wa_template_id', 'body')
+    @api.constrains('recipient_type', 'phone', 'whatsapp_group_id_char', 'wa_template_id', 'body')
     def _check_recipient_configuration(self):
         """Validar configuración de destinatario en composer"""
         for record in self:
             # Si está en contexto de skip_template_validation, saltar validaciones de plantilla
             if self.env.context.get('skip_template_validation'):
                 # Solo validar configuración básica de destinatario
-                if record.recipient_type == 'group' and not record.whatsapp_group_id and not record.whatsapp_group_id_char:
-                    raise ValidationError("Please select a WhatsApp group or enter a Group ID when sending to groups")
+                if record.recipient_type == 'group' and not record.whatsapp_group_id_char:
+                    raise ValidationError("Please enter a Group ID when sending to groups")
                 elif record.recipient_type == 'phone' and not record.phone:
                     raise ValidationError("Please provide a phone number when sending to individuals")
                 return  # Saltar el resto de validaciones
@@ -44,8 +40,8 @@ class WhatsAppComposer(models.TransientModel):
             ])
             has_whatsapp_web = bool(whatsapp_web_accounts)
             
-            if record.recipient_type == 'group' and not record.whatsapp_group_id and not record.whatsapp_group_id_char:
-                raise ValidationError("Please select a WhatsApp group or enter a Group ID when sending to groups")
+            if record.recipient_type == 'group' and not record.whatsapp_group_id_char:
+                raise ValidationError("Please enter a Group ID when sending to groups")
             elif record.recipient_type == 'phone' and not record.phone:
                 raise ValidationError("Please provide a phone number when sending to individuals")
             
@@ -60,7 +56,7 @@ class WhatsAppComposer(models.TransientModel):
             if record.body and not record.wa_template_id and not has_whatsapp_web:
                 raise ValidationError("Free text messages require WhatsApp Web account configuration")
     
-    @api.depends('phone', 'batch_mode', 'recipient_type', 'whatsapp_group_id', 'whatsapp_group_id_char')
+    @api.depends('phone', 'batch_mode', 'recipient_type', 'whatsapp_group_id_char')
     def _compute_invalid_phone_number_count(self):
         """Override SOLO para casos específicos de grupos - NO interferir con funcionalidad nativa"""
         for composer in self:
@@ -77,14 +73,6 @@ class WhatsAppComposer(models.TransientModel):
             # TODOS LOS DEMÁS CASOS: usar lógica original sin modificar
             super(WhatsAppComposer, composer)._compute_invalid_phone_number_count()
     
-    @api.onchange('recipient_type')
-    def _onchange_recipient_type(self):
-        """Limpiar campos al cambiar tipo de destinatario"""
-        if self.recipient_type == 'group':
-            self.phone = False
-        else:
-            self.whatsapp_group_id = False
-    
     def action_send_whatsapp_template(self):
         """Override del método de envío SOLO para casos específicos de WhatsApp Web sin plantilla"""
         
@@ -119,12 +107,10 @@ class WhatsAppComposer(models.TransientModel):
         for record in records:
             # Determinar destinatario
             if self.recipient_type == 'group':
-                if self.whatsapp_group_id:
-                    mobile_number = self.whatsapp_group_id.whatsapp_web_id
-                elif self.whatsapp_group_id_char:
+                if self.whatsapp_group_id_char:
                     mobile_number = self.whatsapp_group_id_char
                 else:
-                    raise ValidationError("Please specify a group")
+                    raise ValidationError("Please specify a group ID")
             else:
                 mobile_number = self.phone
                 if not mobile_number:
@@ -170,26 +156,11 @@ class WhatsAppComposer(models.TransientModel):
         
         # SOLO agregar información de grupo si es caso específico
         if (hasattr(self, 'recipient_type') and self.recipient_type == 'group'):
-            group_id_to_use = None
-            if self.whatsapp_group_id:
-                try:
-                    group_id_to_use = self.whatsapp_group_id.whatsapp_web_id
-                    values.update({
-                        'whatsapp_group_id': self.whatsapp_group_id.id,
-                    })
-                except:
-                    # Si falla, usar campo manual
-                    pass
-                    
-            if not group_id_to_use and self.whatsapp_group_id_char:
-                group_id_to_use = self.whatsapp_group_id_char
-                
-            # Solo actualizar si hay un grupo válido
-            if group_id_to_use:
+            if self.whatsapp_group_id_char:
                 values.update({
                     'recipient_type': 'group',
-                    'mobile_number': group_id_to_use,
-                    'mobile_number_formatted': group_id_to_use,
+                    'mobile_number': self.whatsapp_group_id_char,
+                    'mobile_number_formatted': self.whatsapp_group_id_char,
                 })
         
         # Siempre agregar recipient_type para compatibilidad
@@ -229,8 +200,7 @@ class WhatsAppComposer(models.TransientModel):
             'state': 'outgoing',
         }
         
-        if group_id and hasattr(self, 'whatsapp_group_id') and self.whatsapp_group_id:
-            message_vals['whatsapp_group_id'] = self.whatsapp_group_id.id
+        # Nota: El campo whatsapp_group_id Many2one está en whatsapp_web_groups
             
         # Crear mail.message
         mail_message = self.env['mail.message'].create({

+ 12 - 34
models/whatsapp_message.py

@@ -16,22 +16,20 @@ _logger = logging.getLogger(__name__)
 class WhatsAppMessage(models.Model):
     _inherit = 'whatsapp.message'
 
-    # Campos para soporte de grupos
+    # Campos para soporte básico de grupos (solo por ID string, sin Many2one)
+    # La funcionalidad completa de grupos con Many2one está en whatsapp_web_groups
     recipient_type = fields.Selection([
         ('phone', 'Phone Number'),
         ('group', 'WhatsApp Group')
     ], string='Recipient Type', default='phone', help="Type of recipient: phone number or WhatsApp group")
     
-    whatsapp_group_id = fields.Many2one('ww.group', string='WhatsApp Group', 
-                                        help="WhatsApp group to send message to (if recipient_type is group)",
-                                        ondelete='set null')
-    
-    @api.depends('recipient_type', 'mobile_number', 'whatsapp_group_id')
+    @api.depends('recipient_type', 'mobile_number')
     def _compute_final_recipient(self):
         """Compute the final recipient based on type"""
         for record in self:
-            if record.recipient_type == 'group' and record.whatsapp_group_id:
-                record.final_recipient = record.whatsapp_group_id.whatsapp_web_id
+            # Si es grupo y mobile_number termina en @g.us, usarlo directamente
+            if record.recipient_type == 'group' and record.mobile_number and record.mobile_number.endswith('@g.us'):
+                record.final_recipient = record.mobile_number
             else:
                 record.final_recipient = record.mobile_number
     
@@ -52,25 +50,17 @@ class WhatsAppMessage(models.Model):
                 # TODOS LOS DEMÁS CASOS: usar lógica original sin modificar
                 super(WhatsAppMessage, message)._compute_mobile_number_formatted()
     
-    @api.constrains('recipient_type', 'mobile_number', 'whatsapp_group_id')
+    @api.constrains('recipient_type', 'mobile_number')
     def _check_recipient_configuration(self):
         """Validar configuración de destinatario"""
         for record in self:
             if record.recipient_type == 'group':
-                if not record.whatsapp_group_id and not (record.mobile_number and record.mobile_number.endswith('@g.us')):
-                    raise ValidationError("Para mensajes a grupos, debe seleccionar un grupo o proporcionar un ID de grupo válido (@g.us)")
+                if not (record.mobile_number and record.mobile_number.endswith('@g.us')):
+                    raise ValidationError("Para mensajes a grupos, debe proporcionar un ID de grupo válido (@g.us)")
             elif record.recipient_type == 'phone':
                 if not record.mobile_number or record.mobile_number.endswith('@g.us'):
                     raise ValidationError("Para mensajes a teléfonos, debe proporcionar un número telefónico válido")
     
-    @api.onchange('recipient_type')
-    def _onchange_recipient_type(self):
-        """Limpiar campos al cambiar tipo de destinatario"""
-        if self.recipient_type == 'group':
-            self.mobile_number = False
-        else:
-            self.whatsapp_group_id = False
-    
     def _whatsapp_phone_format(self, fpath=None, number=None, raise_on_format_error=False):
         """Override SOLO para casos específicos de grupos - NO interferir con funcionalidad nativa"""
         self.ensure_one()
@@ -80,10 +70,8 @@ class WhatsAppMessage(models.Model):
         if (hasattr(self, 'recipient_type') and self.recipient_type == 'group' and 
             self.wa_account_id and self.wa_account_id.whatsapp_web_url):
             
-            if self.whatsapp_group_id:
-                return self.whatsapp_group_id.whatsapp_web_id
             # Si el número es un ID de grupo (termina en @g.us), retornarlo sin validar
-            elif number and number.endswith('@g.us'):
+            if number and number.endswith('@g.us'):
                 return number
             elif self.mobile_number and self.mobile_number.endswith('@g.us'):
                 return self.mobile_number
@@ -95,15 +83,7 @@ class WhatsAppMessage(models.Model):
         """Método mejorado para obtener destino final (grupo o teléfono)"""
         self.ensure_one()
         
-        # 1. Si es tipo grupo y hay grupo seleccionado
-        if self.recipient_type == 'group' and self.whatsapp_group_id:
-            try:
-                return self.whatsapp_group_id.whatsapp_web_id
-            except Exception:
-                # Si el modelo ww.group no existe, usar mobile_number
-                pass
-        
-        # 2. Si el mobile_number es un ID de grupo
+        # Si el mobile_number es un ID de grupo (termina en @g.us)
         if self.mobile_number and self.mobile_number.endswith('@g.us'):
             return self.mobile_number
             
@@ -196,9 +176,7 @@ class WhatsAppMessage(models.Model):
             else:
                 # Formatear número según el tipo de destinatario
                 if whatsapp_message.recipient_type == 'group':
-                    if whatsapp_message.whatsapp_group_id:
-                        number = whatsapp_message.whatsapp_group_id.whatsapp_web_id
-                    elif whatsapp_message.mobile_number and whatsapp_message.mobile_number.endswith('@g.us'):
+                    if whatsapp_message.mobile_number and whatsapp_message.mobile_number.endswith('@g.us'):
                         number = whatsapp_message.mobile_number
                     else:
                         _logger.error("Mensaje configurado como grupo pero sin destinatario válido")

+ 3 - 6
views/whatsapp_composer_views.xml

@@ -26,15 +26,12 @@
                 </xpath>
                 
                 <!-- Agregar campos de grupo después del teléfono -->
+                <!-- Nota: El campo whatsapp_group_id Many2one está en whatsapp_web_groups -->
                 <xpath expr="//field[@name='phone']" position="after">
-                    <field name="whatsapp_group_id" 
-                           invisible="recipient_type != 'group'"
-                           string="WhatsApp Group"
-                           placeholder="Select a WhatsApp group..."/>
                     <field name="whatsapp_group_id_char" 
                            invisible="recipient_type != 'group'"
-                           placeholder="Or enter Group ID manually (e.g., 120363158956331133@g.us)"
-                           string="Group ID (Manual)"/>
+                           placeholder="Enter Group ID manually (e.g., 120363158956331133@g.us)"
+                           string="Group ID"/>
                 </xpath>
                 
                 <!-- Agregar campo de mensaje libre para WhatsApp Web -->

+ 1 - 4
views/whatsapp_message_views.xml

@@ -8,11 +8,9 @@
             <field name="inherit_id" ref="whatsapp.whatsapp_message_view_form"/>
             <field name="arch" type="xml">
                 <!-- Agregar campos de grupo después del campo mobile_number -->
+                <!-- Nota: El campo whatsapp_group_id Many2one está en whatsapp_web_groups -->
                 <xpath expr="//field[@name='mobile_number']" position="after">
                     <field name="recipient_type" widget="radio" options="{'horizontal': true}"/>
-                    <field name="whatsapp_group_id" 
-                           invisible="recipient_type != 'group'"
-                           required="recipient_type == 'group'"/>
                     <field name="final_recipient" readonly="1" 
                            invisible="not final_recipient"/>
                 </xpath>
@@ -34,7 +32,6 @@
                 <!-- Agregar columna de tipo de destinatario -->
                 <xpath expr="//field[@name='mobile_number']" position="after">
                     <field name="recipient_type"/>
-                    <field name="whatsapp_group_id" optional="hide"/>
                 </xpath>
             </field>
         </record>