ww_group.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. from odoo import models, fields, api
  2. import logging
  3. from datetime import datetime
  4. _logger = logging.getLogger(__name__)
  5. class WWGroup(models.Model):
  6. _name = 'ww.group'
  7. _description = 'Grupo de WhatsApp Web'
  8. name = fields.Char(string='Nombre del Grupo', required=True)
  9. whatsapp_web_id = fields.Char(string='ID WhatsApp Web', index=True, help='ID único del grupo en WhatsApp Web')
  10. whatsapp_account_id = fields.Many2one('whatsapp.account', string='Cuenta de WhatsApp', required=True)
  11. channel_id = fields.Many2one('discuss.channel', string='Canal de Discusión', readonly=True)
  12. contact_ids = fields.Many2many(
  13. comodel_name='res.partner',
  14. relation='ww_group_contact_rel',
  15. column1='group_id',
  16. column2='contact_id',
  17. string='Contactos',
  18. readonly=True,
  19. )
  20. def _process_messages(self, messages_data):
  21. """Process WhatsApp messages and create them in the channel"""
  22. self.ensure_one()
  23. if not messages_data or not self.channel_id:
  24. return True
  25. # Get existing message IDs to avoid duplicates
  26. existing_ids = set(self.channel_id.message_ids.mapped('message_id'))
  27. # Prepare bulk create values
  28. message_vals_list = []
  29. for msg_data in messages_data:
  30. msg_id = msg_data.get('id', {}).get('_serialized')
  31. # Skip if message already exists
  32. if msg_id in existing_ids:
  33. continue
  34. # Get author partner
  35. author_whatsapp_id = msg_data.get('author')
  36. author = self.env['res.partner'].search([
  37. ('whatsapp_web_id', '=', author_whatsapp_id)
  38. ], limit=1) if author_whatsapp_id else False
  39. # Get quoted message author if exists
  40. quoted_author = False
  41. if msg_data.get('hasQuotedMsg') and msg_data.get('quotedParticipant'):
  42. quoted_author = self.env['res.partner'].search([
  43. ('whatsapp_web_id', '=', msg_data['quotedParticipant'])
  44. ], limit=1)
  45. # Convert timestamp to datetime
  46. timestamp = datetime.fromtimestamp(msg_data.get('timestamp', 0))
  47. # Prepare message body with author and content
  48. author_name = author.name if author else "Desconocido"
  49. message_body = f"{msg_data.get('body', '')}"
  50. # Add quoted message if exists
  51. if msg_data.get('hasQuotedMsg') and msg_data.get('quotedMsg', {}).get('body'):
  52. quoted_author_name = quoted_author.name if quoted_author else "Desconocido"
  53. message_body += f"\n\n<blockquote><strong>{quoted_author_name}:</strong> {msg_data['quotedMsg']['body']}</blockquote>"
  54. message_vals = {
  55. 'model': 'discuss.channel',
  56. 'res_id': self.channel_id.id,
  57. 'message_type': 'comment',
  58. 'subtype_id': self.env.ref('mail.mt_comment').id,
  59. 'body': message_body,
  60. 'date': timestamp,
  61. 'author_id': author.id if author else self.env.user.partner_id.id,
  62. 'message_id': msg_id,
  63. }
  64. message_vals_list.append(message_vals)
  65. # Bulk create messages
  66. if message_vals_list:
  67. self.env['mail.message'].create(message_vals_list)
  68. return True
  69. def _create_discussion_channel(self):
  70. """Create a discussion channel for the WhatsApp group"""
  71. self.ensure_one()
  72. try:
  73. # Verificar si ya existe un canal para este grupo
  74. if self.channel_id:
  75. return self.channel_id
  76. # Create channel name with WhatsApp prefix
  77. channel_name = f"📱 {self.name}"
  78. # Verificar que hay contactos
  79. if not self.contact_ids:
  80. _logger.warning(f"No hay contactos para crear el canal del grupo {self.name} - saltando creación de canal")
  81. # No crear canal pero no fallar, permitir que el grupo exista
  82. return False
  83. # Obtener los IDs de los contactos de forma segura
  84. partner_ids = []
  85. for contact in self.contact_ids:
  86. if contact and contact.id:
  87. partner_ids.append(contact.id)
  88. if not partner_ids:
  89. _logger.warning(f"No se encontraron IDs válidos de contactos para el grupo {self.name}")
  90. return False
  91. # Create the channel using channel_create
  92. channel = self.env['discuss.channel'].channel_create(
  93. name=channel_name,
  94. group_id=self.env.user.groups_id[0].id, # Usar el primer grupo del usuario actual
  95. )
  96. # Add members to the channel
  97. channel.add_members(partner_ids=partner_ids)
  98. # Link the channel to the group
  99. self.write({'channel_id': channel.id})
  100. return channel
  101. except Exception as e:
  102. _logger.error(f"Error al crear el canal para el grupo {self.name}: {str(e)}")
  103. return False
  104. def _update_discussion_channel(self):
  105. """Update the discussion channel members"""
  106. self.ensure_one()
  107. try:
  108. # Si no existe el canal, intentar crearlo
  109. if not self.channel_id:
  110. return self._create_discussion_channel()
  111. # Verificar que el canal aún existe
  112. channel = self.env['discuss.channel'].browse(self.channel_id.id)
  113. if not channel.exists():
  114. _logger.warning(f"El canal para el grupo {self.name} ya no existe, creando uno nuevo")
  115. self.write({'channel_id': False})
  116. return self._create_discussion_channel()
  117. # Obtener los IDs de los contactos de forma segura
  118. partner_ids = []
  119. for contact in self.contact_ids:
  120. if contact and contact.id:
  121. partner_ids.append(contact.id)
  122. if not partner_ids:
  123. _logger.warning(f"No hay contactos válidos para actualizar el canal del grupo {self.name} - saltando actualización")
  124. # Si no hay contactos, no actualizar pero no fallar
  125. return channel
  126. # Update channel members using add_members
  127. channel.add_members(partner_ids=partner_ids)
  128. return channel
  129. except Exception as e:
  130. _logger.error(f"Error al actualizar el canal para el grupo {self.name}: {str(e)}")
  131. return False
  132. @api.model
  133. def sync_ww_contacts_groups(self):
  134. """
  135. Sincroniza los contactos y grupos de WhatsApp Web.
  136. Solo sincroniza contactos que están dentro de grupos y valida que no se dupliquen,
  137. verificando los últimos 10 dígitos del campo mobile.
  138. """
  139. accounts = self.env['whatsapp.account'].search([])
  140. for account in accounts:
  141. try:
  142. # Obtener grupos usando el método de la cuenta
  143. groups_data = account.get_groups()
  144. if not groups_data:
  145. continue
  146. # Procesar cada grupo
  147. for group_data in groups_data:
  148. group_id = group_data.get('id').get('_serialized')
  149. group_name = group_data.get('name', 'Sin nombre')
  150. # Buscar o crear grupo
  151. group = self.search([
  152. ('whatsapp_web_id', '=', group_id),
  153. ('whatsapp_account_id', '=', account.id)
  154. ], limit=1)
  155. if not group:
  156. group = self.create({
  157. 'name': group_name,
  158. 'whatsapp_web_id': group_id,
  159. 'whatsapp_account_id': account.id
  160. })
  161. # Crear canal solo después de procesar participantes
  162. # Se hará más abajo si hay contact_ids
  163. else:
  164. # Actualizar nombre del grupo si cambió
  165. if group.name != group_name:
  166. group.write({'name': group_name})
  167. # Actualizar nombre del canal si existe
  168. if group.channel_id:
  169. group.channel_id.write({'name': f"📱 {group_name}"})
  170. # Procesar participantes del grupo
  171. participants = group_data.get('members', [])
  172. contact_ids = []
  173. # Log para debug
  174. _logger.info(f"Procesando grupo {group_name}: {len(participants)} participantes encontrados")
  175. if not participants:
  176. _logger.warning(f"Grupo {group_name} no tiene participantes en la respuesta de la API")
  177. for participant in participants:
  178. whatsapp_web_id = participant.get('id', {}).get('_serialized')
  179. mobile = participant.get('number', '')
  180. is_admin = participant.get('isAdmin', False)
  181. is_super_admin = participant.get('isSuperAdmin', False)
  182. # Derive participant name
  183. participant_name = participant.get('name') or participant.get('pushname') or mobile
  184. # Search for existing contact
  185. contact = self.env['res.partner'].search([
  186. ('whatsapp_web_id', '=', whatsapp_web_id)
  187. ], limit=1)
  188. if not contact and mobile and len(mobile) >= 10:
  189. last_10_digits = mobile[-10:]
  190. contact = self.env['res.partner'].search([
  191. ('mobile', 'like', '%' + last_10_digits)
  192. ], limit=1)
  193. partner_vals = {
  194. 'name': participant_name,
  195. 'mobile': mobile,
  196. 'whatsapp_web_id': whatsapp_web_id,
  197. }
  198. if contact:
  199. # Update existing contact
  200. contact.write(partner_vals)
  201. else:
  202. # Create new contact
  203. contact = self.env['res.partner'].create(partner_vals)
  204. if contact:
  205. contact_ids.append(contact.id)
  206. # Actualizar contactos del grupo
  207. group.write({'contact_ids': [(6, 0, contact_ids)]})
  208. # Update discussion channel members solo si hay contactos
  209. if contact_ids:
  210. # Si es un grupo nuevo sin canal, crear uno
  211. if not group.channel_id:
  212. group._create_discussion_channel()
  213. else:
  214. group._update_discussion_channel()
  215. else:
  216. _logger.info(f"Grupo {group_name} sincronizado sin contactos - no se creará canal de discusión")
  217. # Process messages if available
  218. messages = group_data.get('messages', [])
  219. if messages:
  220. group._process_messages(messages)
  221. except Exception as e:
  222. _logger.error("Error en la sincronización de grupos para la cuenta %s: %s", account.name, str(e))
  223. continue
  224. return True
  225. def send_whatsapp_message(self, body, attachment=None, wa_template_id=None):
  226. """Enviar mensaje WhatsApp al grupo"""
  227. self.ensure_one()
  228. if not self.whatsapp_account_id:
  229. raise ValueError("Group must have a WhatsApp account configured")
  230. # Crear mail.message si hay adjunto
  231. mail_message = None
  232. if attachment:
  233. mail_message = self.env['mail.message'].create({
  234. 'body': body,
  235. 'attachment_ids': [(4, attachment.id)]
  236. })
  237. # Crear mensaje WhatsApp
  238. message_vals = {
  239. 'body': body,
  240. 'recipient_type': 'group',
  241. 'whatsapp_group_id': self.id,
  242. 'mobile_number': self.whatsapp_web_id,
  243. 'wa_account_id': self.whatsapp_account_id.id,
  244. 'state': 'outgoing',
  245. }
  246. if mail_message:
  247. message_vals['mail_message_id'] = mail_message.id
  248. if wa_template_id:
  249. message_vals['wa_template_id'] = wa_template_id
  250. whatsapp_message = self.env['whatsapp.message'].create(message_vals)
  251. # Enviar mensaje
  252. whatsapp_message._send_message()
  253. return whatsapp_message
  254. def action_send_whatsapp_message(self):
  255. """Acción para abrir el composer de WhatsApp para el grupo"""
  256. self.ensure_one()
  257. return {
  258. 'type': 'ir.actions.act_window',
  259. 'name': f'Send Message to {self.name}',
  260. 'res_model': 'whatsapp.composer',
  261. 'view_mode': 'form',
  262. 'target': 'new',
  263. 'context': {
  264. 'default_recipient_type': 'group',
  265. 'default_whatsapp_group_id': self.id,
  266. 'default_wa_template_id': False,
  267. }
  268. }