whatsapp_composer.py 11 KB

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