whatsapp_composer.py 11 KB

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