discuss_channel.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. from datetime import datetime, timedelta
  2. from odoo import models, api, fields
  3. from odoo.addons.mail.tools.discuss import Store
  4. from odoo.exceptions import ValidationError
  5. class DiscussChannel(models.Model):
  6. _inherit = "discuss.channel"
  7. is_whatsapp_web = fields.Boolean(compute="_compute_is_whatsapp_web")
  8. def write(self, vals):
  9. """
  10. Override write to debounce 'last_interest_dt' updates.
  11. If the channel was updated less than 10 seconds ago, skip updating last_interest_dt.
  12. This prevents 'concurrent update' errors during high traffic (e.g. active WhatsApp groups).
  13. """
  14. if "last_interest_dt" in vals and len(self) == 1:
  15. # Check if we have a recent update
  16. if self.last_interest_dt:
  17. # Calculate time since last update
  18. # Note: last_interest_dt is usually UTC
  19. time_since_last = datetime.now() - self.last_interest_dt
  20. if time_since_last < timedelta(seconds=10):
  21. # Skip updating this field
  22. del vals["last_interest_dt"]
  23. return super().write(vals)
  24. @api.depends("channel_type", "wa_account_id.whatsapp_web_url")
  25. def _compute_is_whatsapp_web(self):
  26. for record in self:
  27. record.is_whatsapp_web = record.channel_type == "whatsapp" and bool(
  28. record.wa_account_id.whatsapp_web_url
  29. )
  30. def _to_store(self, store: Store):
  31. """
  32. Send is_whatsapp_web to the frontend via Store.
  33. """
  34. super()._to_store(store)
  35. for channel in self:
  36. if channel.is_whatsapp_web:
  37. store.add(channel, {"is_whatsapp_web": True})
  38. def message_post(self, **kwargs):
  39. """
  40. Override message_post to allow sending free text messages in WhatsApp Web channels.
  41. Standard Odoo WhatsApp module might block or restrict messages without templates.
  42. """
  43. # Check if it's a WhatsApp channel with WhatsApp Web configured
  44. if self.channel_type == "whatsapp" and self.wa_account_id.whatsapp_web_url:
  45. # We want to use our custom logic for these channels
  46. # Extract basic message data
  47. body = kwargs.get("body", "")
  48. attachment_ids = kwargs.get("attachment_ids", [])
  49. # If it's a simple text message or has attachments, we handle it.
  50. # Note: We need to ensure we don't break other message_post usages (like system notifications)
  51. # System notifications usually have subtype_xmlid='mail.mt_note' or similar, strict check might be needed.
  52. # Let's check if we should intervene.
  53. # If the user is trying to send a message (comment)
  54. if kwargs.get("message_type") == "comment" or not kwargs.get(
  55. "message_type"
  56. ):
  57. # Check for attachments in kwargs (can be list of IDs or list of tuples)
  58. # We mainly care about passing them to the mail.message
  59. # 1. Create the mail.message manually to bypass potential blocks in super().message_post()
  60. # We need to replicate some logic from mail.thread.message_post
  61. # However, completely skipping super() is risky for notifications/followers.
  62. # Let's try a hybrid approach:
  63. # Create the message using mail.message.create() directly, then run necessary side effects?
  64. # Or invoke mail.thread's message_post directly if possible?
  65. # We can't easily invoke 'grandparent' methods in Odoo new API unless we are careful.
  66. # Simplified approach: mimic whatsapp_composer logic
  67. email_from = kwargs.get("email_from")
  68. if not email_from:
  69. email_from = self.env.user.email_formatted
  70. # Create mail.message
  71. msg_values = {
  72. "body": body,
  73. "model": self._name,
  74. "res_id": self.id,
  75. "message_type": "whatsapp_message", # Use whatsapp_message type so our other logic picks it up? Or 'comment'?
  76. # Standard WA uses 'whatsapp_message'
  77. "email_from": email_from,
  78. "partner_ids": [
  79. (4, p.id) for p in self.channel_partner_ids
  80. ], # Add channel partners?
  81. # 'subtype_id': ...
  82. "attachment_ids": attachment_ids,
  83. }
  84. # Handle author
  85. author_id = kwargs.get("author_id")
  86. if author_id:
  87. msg_values["author_id"] = author_id
  88. else:
  89. msg_values["author_id"] = self.env.user.partner_id.id
  90. # Create the message
  91. message = self.env["mail.message"].create(msg_values)
  92. # Now create the whatsapp.message to trigger sending (via our overridden _send_message or similar)
  93. # Note: whatsapp_message.create() triggers _send_message() if state is outgoing?
  94. # In our whatsapp_composer, we called _send_message() explicitly.
  95. # Determine recipient (Phone or Group)
  96. mobile_number = self.whatsapp_number
  97. recipient_type = "phone"
  98. if mobile_number and mobile_number.endswith("@g.us"):
  99. recipient_type = "group"
  100. wa_msg_values = {
  101. "mail_message_id": message.id,
  102. "wa_account_id": self.wa_account_id.id,
  103. "mobile_number": mobile_number,
  104. "recipient_type": recipient_type,
  105. "wa_template_id": False,
  106. "body": body,
  107. "state": "outgoing",
  108. }
  109. wa_msg = self.env["whatsapp.message"].create(wa_msg_values)
  110. # Send it
  111. wa_msg._send_message()
  112. # Ensure the message is linked to the channel (standard mail.message behavior should handle res_id/model)
  113. # But Discuss expects the message to be in the channel.
  114. return message
  115. # Default behavior for other channels or if conditions not met
  116. return super(DiscussChannel, self).message_post(**kwargs)