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