whatsapp_composer.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. from odoo import models, fields, api
  2. from odoo.exceptions import ValidationError
  3. import logging
  4. _logger = logging.getLogger(__name__)
  5. class WhatsAppComposer(models.TransientModel):
  6. _inherit = 'whatsapp.composer'
  7. # Campos para soporte básico de grupos (solo por ID string, sin Many2one)
  8. # La funcionalidad completa de grupos con Many2one está en whatsapp_web_groups
  9. recipient_type = fields.Selection([
  10. ('phone', 'Phone Number'),
  11. ('group', 'WhatsApp Group')
  12. ], string='Send To', default='phone', help="Choose recipient type")
  13. # Campo para ID de grupo como string
  14. whatsapp_group_id_char = fields.Char(string='Group ID',
  15. help="WhatsApp Group ID (e.g., 120363158956331133@g.us)")
  16. # Campo para mensaje libre (sin plantilla)
  17. body = fields.Html(string='Message Body', help="Free text message (for WhatsApp Web accounts without template)")
  18. @api.constrains('recipient_type', 'phone', 'whatsapp_group_id_char', 'wa_template_id', 'body')
  19. def _check_recipient_configuration(self):
  20. """Validar configuración de destinatario en composer"""
  21. for record in self:
  22. # Si está en contexto de skip_template_validation, saltar validaciones de plantilla
  23. if self.env.context.get('skip_template_validation'):
  24. # Solo validar configuración básica de destinatario
  25. if record.recipient_type == 'group' and not record.whatsapp_group_id_char:
  26. raise ValidationError("Please enter a Group ID when sending to groups")
  27. elif record.recipient_type == 'phone' and not record.phone:
  28. raise ValidationError("Please provide a phone number when sending to individuals")
  29. return # Saltar el resto de validaciones
  30. # Detectar si hay cuentas de WhatsApp Web disponibles
  31. whatsapp_web_accounts = record.env['whatsapp.account'].search([
  32. ('whatsapp_web_url', '!=', False)
  33. ])
  34. has_whatsapp_web = bool(whatsapp_web_accounts)
  35. if record.recipient_type == 'group' and not record.whatsapp_group_id_char:
  36. raise ValidationError("Please enter a Group ID when sending to groups")
  37. elif record.recipient_type == 'phone' and not record.phone:
  38. raise ValidationError("Please provide a phone number when sending to individuals")
  39. # Validar que haya contenido (plantilla o mensaje libre)
  40. if not record.wa_template_id and not record.body:
  41. if has_whatsapp_web:
  42. raise ValidationError("Please provide either a template or write a free text message")
  43. else:
  44. raise ValidationError("Template is required for WhatsApp Business API")
  45. # Si usa mensaje libre, debe haber WhatsApp Web
  46. if record.body and not record.wa_template_id and not has_whatsapp_web:
  47. raise ValidationError("Free text messages require WhatsApp Web account configuration")
  48. @api.depends('phone', 'batch_mode', 'recipient_type', 'whatsapp_group_id_char')
  49. def _compute_invalid_phone_number_count(self):
  50. """Override SOLO para casos específicos de grupos - NO interferir con funcionalidad nativa"""
  51. for composer in self:
  52. # SOLO intervenir si es un caso muy específico de grupo
  53. if (hasattr(composer, 'recipient_type') and composer.recipient_type == 'group'):
  54. composer.invalid_phone_number_count = 0
  55. continue
  56. # SOLO intervenir si el phone es explícitamente un ID de grupo
  57. if composer.phone and composer.phone.endswith('@g.us'):
  58. composer.invalid_phone_number_count = 0
  59. continue
  60. # TODOS LOS DEMÁS CASOS: usar lógica original sin modificar
  61. super(WhatsAppComposer, composer)._compute_invalid_phone_number_count()
  62. def action_send_whatsapp_template(self):
  63. """Override del método de envío SOLO para casos específicos de WhatsApp Web sin plantilla"""
  64. # SOLO intervenir si es un caso muy específico:
  65. # 1. No hay plantilla
  66. # 2. Hay mensaje libre (body)
  67. # 3. Es tipo grupo O hay WhatsApp Web disponible
  68. whatsapp_web_accounts = self.env['whatsapp.account'].search([
  69. ('whatsapp_web_url', '!=', False)
  70. ])
  71. has_whatsapp_web = bool(whatsapp_web_accounts)
  72. # CONDICIÓN MUY ESPECÍFICA para no interferir con funcionalidad nativa
  73. is_special_case = (
  74. not self.wa_template_id and # Sin plantilla
  75. self.body and # Con mensaje libre
  76. has_whatsapp_web and # Con WhatsApp Web disponible
  77. (self.recipient_type == 'group' or # Es grupo
  78. (hasattr(self, 'phone') and self.phone and self.phone.endswith('@g.us'))) # O es ID de grupo directo
  79. )
  80. if is_special_case:
  81. return self._send_whatsapp_web_message()
  82. # TODOS LOS DEMÁS CASOS: usar método original sin modificar
  83. return super().action_send_whatsapp_template()
  84. def _send_whatsapp_web_message(self):
  85. """Enviar mensaje WhatsApp Web sin plantilla - siguiendo lógica original"""
  86. records = self._get_active_records()
  87. for record in records:
  88. # Determinar destinatario
  89. if self.recipient_type == 'group':
  90. if self.whatsapp_group_id_char:
  91. mobile_number = self.whatsapp_group_id_char
  92. else:
  93. raise ValidationError("Please specify a group ID")
  94. else:
  95. mobile_number = self.phone
  96. if not mobile_number:
  97. raise ValidationError("Please provide a phone number")
  98. # Crear mail.message con adjuntos si existen (siguiendo lógica original)
  99. post_values = {
  100. 'attachment_ids': [self.attachment_id.id] if self.attachment_id else [],
  101. 'body': self.body,
  102. 'message_type': 'whatsapp_message',
  103. 'partner_ids': hasattr(record, '_mail_get_partners') and record._mail_get_partners()[record.id].ids or record._whatsapp_get_responsible().partner_id.ids,
  104. }
  105. if hasattr(records, '_message_log'):
  106. message = record._message_log(**post_values)
  107. else:
  108. message = self.env['mail.message'].create(
  109. dict(post_values, res_id=record.id, model=self.res_model,
  110. subtype_id=self.env['ir.model.data']._xmlid_to_res_id("mail.mt_note"))
  111. )
  112. # Crear mensaje WhatsApp (siguiendo estructura original)
  113. whatsapp_message = self.env['whatsapp.message'].create({
  114. 'mail_message_id': message.id,
  115. 'mobile_number': mobile_number,
  116. 'mobile_number_formatted': mobile_number,
  117. 'recipient_type': self.recipient_type,
  118. 'wa_template_id': False, # Sin plantilla
  119. 'wa_account_id': self._get_whatsapp_web_account().id,
  120. 'state': 'outgoing',
  121. })
  122. # Enviar mensaje usando la lógica original de _send_message
  123. whatsapp_message._send_message()
  124. return {'type': 'ir.actions.act_window_close'}
  125. def _prepare_whatsapp_message_values(self, record):
  126. """Override SOLO para agregar información de grupo - NO interferir con funcionalidad nativa"""
  127. # SIEMPRE usar lógica original primero
  128. values = super()._prepare_whatsapp_message_values(record)
  129. # SOLO agregar información de grupo si es caso específico
  130. if (hasattr(self, 'recipient_type') and self.recipient_type == 'group'):
  131. if self.whatsapp_group_id_char:
  132. values.update({
  133. 'recipient_type': 'group',
  134. 'mobile_number': self.whatsapp_group_id_char,
  135. 'mobile_number_formatted': self.whatsapp_group_id_char,
  136. })
  137. # Siempre agregar recipient_type para compatibilidad
  138. if not values.get('recipient_type'):
  139. values['recipient_type'] = 'phone'
  140. return values
  141. def _get_whatsapp_web_account(self):
  142. """Obtener cuenta de WhatsApp Web disponible"""
  143. # Primero intentar usar la cuenta de la plantilla si existe
  144. if self.wa_template_id and self.wa_template_id.wa_account_id and self.wa_template_id.wa_account_id.whatsapp_web_url:
  145. return self.wa_template_id.wa_account_id
  146. # Si no, buscar cualquier cuenta con WhatsApp Web
  147. account = self.env['whatsapp.account'].search([
  148. ('whatsapp_web_url', '!=', False)
  149. ], limit=1)
  150. if not account:
  151. raise ValidationError("No WhatsApp Web account configured")
  152. return account
  153. def _send_whatsapp_message_without_template(self, body, phone=None, group_id=None):
  154. """Enviar mensaje de WhatsApp sin plantilla (solo para WhatsApp Web)"""
  155. # Solo funciona con WhatsApp Web, no con API oficial
  156. if not (phone or group_id):
  157. raise ValidationError("Debe especificar teléfono o grupo")
  158. # Crear mensaje directamente
  159. message_vals = {
  160. 'body': body,
  161. 'mobile_number': group_id or phone,
  162. 'recipient_type': 'group' if group_id else 'phone',
  163. 'wa_template_id': False, # Sin plantilla
  164. 'state': 'outgoing',
  165. }
  166. # Nota: El campo whatsapp_group_id Many2one está en whatsapp_web_groups
  167. # Crear mail.message
  168. mail_message = self.env['mail.message'].create({
  169. 'body': body,
  170. 'message_type': 'whatsapp_message',
  171. })
  172. message_vals['mail_message_id'] = mail_message.id
  173. # Crear y enviar mensaje WhatsApp
  174. whatsapp_message = self.env['whatsapp.message'].create(message_vals)
  175. whatsapp_message._send_message()
  176. return whatsapp_message