254 lines
11 KiB
Python
254 lines
11 KiB
Python
import logging
|
||
import re
|
||
|
||
from odoo import models
|
||
|
||
_logger = logging.getLogger(__name__)
|
||
|
||
|
||
class MailThread(models.AbstractModel):
|
||
_inherit = 'mail.thread'
|
||
|
||
def _get_allowed_message_params(self):
|
||
"""Extend allowed params so channel_tag_ids passes through the controller."""
|
||
return super()._get_allowed_message_params() | {'channel_tag_ids'}
|
||
|
||
def _get_message_create_valid_field_names(self):
|
||
"""Add channel_tag_ids to the whitelist for _message_create."""
|
||
return super()._get_message_create_valid_field_names() | {'channel_tag_ids'}
|
||
|
||
def assign_mailbox(self, mailbox_id):
|
||
"""Assign all messages of this thread to the given tier.mailbox.
|
||
|
||
Called from the chatter UI when user clicks "Входящие лиды" / "Входящие тикеты".
|
||
Removes existing tags for this mailbox first to avoid duplicates.
|
||
"""
|
||
self.ensure_one()
|
||
mailbox = self.env['tier.mailbox'].browse(mailbox_id)
|
||
if not mailbox.exists():
|
||
return False
|
||
|
||
# Get all messages of this thread
|
||
thread_messages = self.env['mail.message'].search([
|
||
('model', '=', self._name),
|
||
('res_id', '=', self.id),
|
||
])
|
||
|
||
for message in thread_messages:
|
||
# Remove existing tag for this mailbox if any
|
||
existing_tag = message.mailbox_tag_ids.filtered(
|
||
lambda tag: tag.mailbox_id.id == mailbox_id
|
||
)
|
||
if existing_tag:
|
||
existing_tag.unlink()
|
||
# Create new tag
|
||
self.env['tier.mailbox.tag'].create({
|
||
'mailbox_id': mailbox_id,
|
||
'message_id': message.id,
|
||
})
|
||
|
||
print(
|
||
f"[tier_communications] assign_mailbox: "
|
||
f"thread {self._name}#{self.id} → mailbox '{mailbox.name}' "
|
||
f"({len(thread_messages)} messages tagged)"
|
||
)
|
||
return True
|
||
|
||
# =========================================================================
|
||
# ОТПРАВКА ИСХОДЯЩИХ СООБЩЕНИЙ
|
||
# =========================================================================
|
||
|
||
def message_post(self, *, body='', **kwargs):
|
||
"""Override: создаём сообщение в Odoo. Внешняя отправка через канал
|
||
выполняется отдельным RPC-вызовом из JS (/tier_communications/send_via_channel).
|
||
"""
|
||
message = super().message_post(body=body, **kwargs)
|
||
if not message:
|
||
return message
|
||
|
||
if not message.channel_tag_ids:
|
||
print(
|
||
f"[tier_communications] message_post: msg_id={message.id} — "
|
||
f"канал не выбран, сообщение только в Odoo"
|
||
)
|
||
|
||
return message
|
||
|
||
# =========================================================================
|
||
# ! ОТПРАВКА СООБЩЕНИЯ НА EMAIL
|
||
# =========================================================================
|
||
|
||
def _tier_send_email(self, message):
|
||
"""Отправить сообщение на email партнёра треда через стандартный mail.mail."""
|
||
recipient_email = None
|
||
|
||
# 1. Thread has partner_id (business models: sale.order, crm.lead, etc.)
|
||
if self._name not in ('mail.thread', 'discuss.channel') and hasattr(self, 'partner_id'):
|
||
partner = self.partner_id
|
||
if partner and partner.email:
|
||
recipient_email = partner.email
|
||
|
||
# 2. Message has explicit partner_ids
|
||
if not recipient_email and message.partner_ids:
|
||
for partner in message.partner_ids:
|
||
partner_email = partner.email if hasattr(partner, 'email') else None
|
||
if partner_email and isinstance(partner_email, str) and '@' in partner_email:
|
||
recipient_email = partner_email
|
||
break
|
||
|
||
# 3. Last incoming message in this thread — use its email_from
|
||
if not recipient_email and message.model and message.res_id:
|
||
last_incoming = self.env['mail.message'].search([
|
||
('model', '=', message.model),
|
||
('res_id', '=', message.res_id),
|
||
('message_type', 'in', ['email', 'comment']),
|
||
('email_from', '!=', False),
|
||
('author_id', '!=', self.env.user.partner_id.id),
|
||
], order='id desc', limit=1)
|
||
if last_incoming and last_incoming.email_from:
|
||
email_match = re.search(r'<([^>]+)>', last_incoming.email_from)
|
||
recipient_email = email_match.group(1) if email_match else last_incoming.email_from
|
||
|
||
# 4. For discuss.channel — find the other member's email
|
||
if not recipient_email and self._name == 'discuss.channel':
|
||
other_members = self.channel_member_ids.filtered(
|
||
lambda m: m.partner_id and m.partner_id != self.env.user.partner_id
|
||
)
|
||
for member in other_members:
|
||
if member.partner_id.email:
|
||
recipient_email = member.partner_id.email
|
||
break
|
||
|
||
if not recipient_email:
|
||
print(
|
||
f"[tier_communications] ! ОТПРАВКА СООБЩЕНИЯ НА EMAIL: "
|
||
f"msg_id={message.id} — У пользователя нет email адреса получателя"
|
||
)
|
||
return
|
||
|
||
print(
|
||
f"[tier_communications] ! ОТПРАВКА СООБЩЕНИЯ НА EMAIL: "
|
||
f"msg_id={message.id}, email_to={recipient_email}"
|
||
)
|
||
|
||
try:
|
||
mail_values = {
|
||
'subject': message.subject or 'Сообщение из Odoo',
|
||
'body_html': message.body or '',
|
||
'email_to': recipient_email,
|
||
'email_from': self.env.company.email or self.env.user.email or '',
|
||
'auto_delete': True,
|
||
}
|
||
outgoing_mail = self.env['mail.mail'].sudo().create(mail_values)
|
||
outgoing_mail.send()
|
||
print(
|
||
f"[tier_communications] ! ОТПРАВКА СООБЩЕНИЯ НА EMAIL: "
|
||
f"msg_id={message.id} — SUCCESS, mail.mail id={outgoing_mail.id}"
|
||
)
|
||
except Exception as send_error:
|
||
print(
|
||
f"[tier_communications] ! ОТПРАВКА СООБЩЕНИЯ НА EMAIL: "
|
||
f"msg_id={message.id} — ОШИБКА: {send_error}"
|
||
)
|
||
_logger.exception(
|
||
'tier_communications: ошибка отправки email, msg_id=%s', message.id
|
||
)
|
||
|
||
# =========================================================================
|
||
# ! ОТПРАВКА СООБЩЕНИЯ НА MATRIX
|
||
# =========================================================================
|
||
|
||
def _tier_send_matrix(self, message):
|
||
"""Отправить сообщение в Matrix комнату текущего пользователя."""
|
||
current_user = self.env.user
|
||
|
||
# Matrix fields are defined in tier_communications_matrix module
|
||
# If not installed — fields don't exist
|
||
if not hasattr(current_user, 'tier_matrix_homeserver'):
|
||
print(
|
||
f"[tier_communications] ! ОТПРАВКА СООБЩЕНИЯ НА MATRIX: "
|
||
f"msg_id={message.id} — модуль tier_communications_matrix не установлен"
|
||
)
|
||
return
|
||
|
||
homeserver_url = current_user.tier_matrix_homeserver
|
||
access_token = current_user.tier_matrix_access_token
|
||
room_id = current_user.tier_matrix_room_id
|
||
|
||
if not homeserver_url:
|
||
print(
|
||
f"[tier_communications] ! ОТПРАВКА СООБЩЕНИЯ НА MATRIX: "
|
||
f"msg_id={message.id} — У пользователя нет homeserver URL (tier_matrix_homeserver)"
|
||
)
|
||
return
|
||
if not access_token:
|
||
print(
|
||
f"[tier_communications] ! ОТПРАВКА СООБЩЕНИЯ НА MATRIX: "
|
||
f"msg_id={message.id} — У пользователя нет access token (tier_matrix_access_token)"
|
||
)
|
||
return
|
||
if not room_id:
|
||
print(
|
||
f"[tier_communications] ! ОТПРАВКА СООБЩЕНИЯ НА MATRIX: "
|
||
f"msg_id={message.id} — У пользователя нет room ID (tier_matrix_room_id)"
|
||
)
|
||
return
|
||
|
||
print(
|
||
f"[tier_communications] ! ОТПРАВКА СООБЩЕНИЯ НА MATRIX: "
|
||
f"msg_id={message.id}, room={room_id}, homeserver={homeserver_url}"
|
||
)
|
||
|
||
if 'tier.channel.matrix.helper' in self.env:
|
||
try:
|
||
self.env['tier.channel.matrix.helper'].channel_send_message(
|
||
message, user=current_user
|
||
)
|
||
except Exception as send_error:
|
||
print(
|
||
f"[tier_communications] ! ОТПРАВКА СООБЩЕНИЯ НА MATRIX: "
|
||
f"msg_id={message.id} — ОШИБКА: {send_error}"
|
||
)
|
||
_logger.exception(
|
||
'tier_communications: ошибка отправки matrix, msg_id=%s', message.id
|
||
)
|
||
else:
|
||
print(
|
||
f"[tier_communications] ! ОТПРАВКА СООБЩЕНИЯ НА MATRIX: "
|
||
f"msg_id={message.id} — модуль tier_communications_matrix не установлен"
|
||
)
|
||
|
||
# =========================================================================
|
||
# ПРИЁМ ВХОДЯЩИХ СООБЩЕНИЙ (fetchmail / стандартный механизм Odoo)
|
||
# =========================================================================
|
||
|
||
def message_process(self, model, message, custom_values=None, save_original=False,
|
||
strip_attachments=False, thread_id=None):
|
||
"""Override: после стандартной обработки входящего письма — помечаем тегом email."""
|
||
result = super().message_process(
|
||
model, message,
|
||
custom_values=custom_values,
|
||
save_original=save_original,
|
||
strip_attachments=strip_attachments,
|
||
thread_id=thread_id,
|
||
)
|
||
if result:
|
||
incoming_message = self.env['mail.message'].search(
|
||
[('model', '=', model), ('res_id', '=', result)],
|
||
order='id desc',
|
||
limit=1,
|
||
)
|
||
if incoming_message:
|
||
email_tag = self.env['tier.channel.tag'].search(
|
||
[('code', '=', 'email')], limit=1
|
||
)
|
||
if email_tag and not incoming_message.channel_tag_ids:
|
||
incoming_message.channel_tag_ids = [(4, email_tag.id)]
|
||
print(
|
||
f"[tier_communications] message_process: "
|
||
f"входящее письмо msg_id={incoming_message.id} помечено тегом 'email'"
|
||
)
|
||
from .tier_router import TierRouter
|
||
TierRouter(self.env).route_incoming(incoming_message)
|
||
return result
|