1# -*- coding: utf-8 -*-
2# Part of Odoo. See LICENSE file for full copyright and licensing details.
3
4from dateutil.relativedelta import relativedelta
5
6from odoo import api, fields, models
7from odoo.exceptions import AccessError
8from odoo.tools.translate import _
9
10
11class MailNotification(models.Model):
12    _name = 'mail.notification'
13    _table = 'mail_message_res_partner_needaction_rel'
14    _rec_name = 'res_partner_id'
15    _log_access = False
16    _description = 'Message Notifications'
17
18    # origin
19    mail_message_id = fields.Many2one('mail.message', 'Message', index=True, ondelete='cascade', required=True)
20    mail_id = fields.Many2one('mail.mail', 'Mail', index=True, help='Optional mail_mail ID. Used mainly to optimize searches.')
21    # recipient
22    res_partner_id = fields.Many2one('res.partner', 'Recipient', index=True, ondelete='cascade')
23    # status
24    notification_type = fields.Selection([
25        ('inbox', 'Inbox'), ('email', 'Email')
26        ], string='Notification Type', default='inbox', index=True, required=True)
27    notification_status = fields.Selection([
28        ('ready', 'Ready to Send'),
29        ('sent', 'Sent'),
30        ('bounce', 'Bounced'),
31        ('exception', 'Exception'),
32        ('canceled', 'Canceled')
33        ], string='Status', default='ready', index=True)
34    is_read = fields.Boolean('Is Read', index=True)
35    read_date = fields.Datetime('Read Date', copy=False)
36    failure_type = fields.Selection(selection=[
37        ("SMTP", "Connection failed (outgoing mail server problem)"),
38        ("RECIPIENT", "Invalid email address"),
39        ("BOUNCE", "Email address rejected by destination"),
40        ("UNKNOWN", "Unknown error"),
41        ], string='Failure type')
42    failure_reason = fields.Text('Failure reason', copy=False)
43
44    _sql_constraints = [
45        # email notification;: partner is required
46        ('notification_partner_required',
47         "CHECK(notification_type NOT IN ('email', 'inbox') OR res_partner_id IS NOT NULL)",
48         'Customer is required for inbox / email notification'),
49    ]
50
51    def init(self):
52        self._cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s',
53                         ('mail_notification_res_partner_id_is_read_notification_status_mail_message_id',))
54        if not self._cr.fetchone():
55            self._cr.execute("""
56                CREATE INDEX mail_notification_res_partner_id_is_read_notification_status_mail_message_id
57                          ON mail_message_res_partner_needaction_rel (res_partner_id, is_read, notification_status, mail_message_id)
58            """)
59
60    @api.model_create_multi
61    def create(self, vals_list):
62        messages = self.env['mail.message'].browse(vals['mail_message_id'] for vals in vals_list)
63        messages.check_access_rights('read')
64        messages.check_access_rule('read')
65        for vals in vals_list:
66            if vals.get('is_read'):
67                vals['read_date'] = fields.Datetime.now()
68        return super(MailNotification, self).create(vals_list)
69
70    def write(self, vals):
71        if ('mail_message_id' in vals or 'res_partner_id' in vals) and not self.env.is_admin():
72            raise AccessError(_("Can not update the message or recipient of a notification."))
73        if vals.get('is_read'):
74            vals['read_date'] = fields.Datetime.now()
75        return super(MailNotification, self).write(vals)
76
77    def format_failure_reason(self):
78        self.ensure_one()
79        if self.failure_type != 'UNKNOWN':
80            return dict(type(self).failure_type.selection).get(self.failure_type, _('No Error'))
81        else:
82            return _("Unknown error") + ": %s" % (self.failure_reason or '')
83
84    @api.model
85    def _gc_notifications(self, max_age_days=180):
86        domain = [
87            ('is_read', '=', True),
88            ('read_date', '<', fields.Datetime.now() - relativedelta(days=max_age_days)),
89            ('res_partner_id.partner_share', '=', False),
90            ('notification_status', 'in', ('sent', 'canceled'))
91        ]
92        return self.search(domain).unlink()
93
94    def _filtered_for_web_client(self):
95        """Returns only the notifications to show on the web client."""
96        return self.filtered(lambda n:
97            n.notification_type != 'inbox' and
98            (n.notification_status in ['bounce', 'exception', 'canceled'] or n.res_partner_id.partner_share)
99        )
100
101    def _notification_format(self):
102        """Returns the current notifications in the format expected by the web
103        client."""
104        return [{
105            'id': notif.id,
106            'notification_type': notif.notification_type,
107            'notification_status': notif.notification_status,
108            'failure_type': notif.failure_type,
109            'res_partner_id': [notif.res_partner_id.id, notif.res_partner_id.display_name] if notif.res_partner_id else False,
110        } for notif in self]
111