File: mail_notification.py

package info (click to toggle)
odoo 18.0.0%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 878,716 kB
  • sloc: javascript: 927,937; python: 685,670; xml: 388,524; sh: 1,033; sql: 415; makefile: 26
file content (143 lines) | stat: -rw-r--r-- 6,544 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from dateutil.relativedelta import relativedelta

from odoo import api, fields, models
from odoo.exceptions import AccessError
from odoo.tools.translate import _
from odoo.addons.mail.tools.discuss import Store


class MailNotification(models.Model):
    _name = 'mail.notification'
    _table = 'mail_notification'
    _rec_name = 'res_partner_id'
    _log_access = False
    _description = 'Message Notifications'

    # origin
    author_id = fields.Many2one('res.partner', 'Author', ondelete='set null')
    mail_message_id = fields.Many2one('mail.message', 'Message', index=True, ondelete='cascade', required=True)
    mail_mail_id = fields.Many2one('mail.mail', 'Mail', index=True, help='Optional mail_mail ID. Used mainly to optimize searches.')
    # recipient
    res_partner_id = fields.Many2one('res.partner', 'Recipient', index=True, ondelete='cascade')
    # status
    notification_type = fields.Selection([
        ('inbox', 'Inbox'), ('email', 'Email')
        ], string='Notification Type', default='inbox', index=True, required=True)
    notification_status = fields.Selection([
        ('ready', 'Ready to Send'),
        ('process', 'Processing'),  # being checked by intermediary like IAP for sms
        ('pending', 'Sent'),  # used with SMS; mail does not differentiate sent from delivered
        ('sent', 'Delivered'),
        ('bounce', 'Bounced'),
        ('exception', 'Exception'),
        ('canceled', 'Cancelled')
        ], string='Status', default='ready', index=True)
    is_read = fields.Boolean('Is Read', index=True)
    read_date = fields.Datetime('Read Date', copy=False)
    failure_type = fields.Selection(selection=[
        # generic
        ("unknown", "Unknown error"),
        # mail
        ("mail_bounce", "Bounce"),
        ("mail_email_invalid", "Invalid email address"),
        ("mail_email_missing", "Missing email address"),
        ("mail_from_invalid", "Invalid from address"),
        ("mail_from_missing", "Missing from address"),
        ("mail_smtp", "Connection failed (outgoing mail server problem)"),
        ], string='Failure type')
    failure_reason = fields.Text('Failure reason', copy=False)

    _sql_constraints = [
        # email notification: partner is required
        ('notification_partner_required',
         "CHECK(notification_type NOT IN ('email', 'inbox') OR res_partner_id IS NOT NULL)",
         'Customer is required for inbox / email notification'),
    ]

    # ------------------------------------------------------------
    # CRUD
    # ------------------------------------------------------------

    def init(self):
        self._cr.execute("""
            CREATE INDEX IF NOT EXISTS mail_notification_res_partner_id_is_read_notification_status_mail_message_id
                                    ON mail_notification (res_partner_id, is_read, notification_status, mail_message_id);
            CREATE INDEX IF NOT EXISTS mail_notification_author_id_notification_status_failure
                                    ON mail_notification (author_id, notification_status)
                                 WHERE notification_status IN ('bounce', 'exception');
        """)
        self.env.cr.execute(
            """CREATE UNIQUE INDEX IF NOT EXISTS unique_mail_message_id_res_partner_id_if_set
                                              ON %s (mail_message_id, res_partner_id)
                                           WHERE res_partner_id IS NOT NULL""" % self._table
        )

    @api.model_create_multi
    def create(self, vals_list):
        messages = self.env['mail.message'].browse(vals['mail_message_id'] for vals in vals_list)
        messages.check_access('read')
        for vals in vals_list:
            if vals.get('is_read'):
                vals['read_date'] = fields.Datetime.now()
        return super(MailNotification, self).create(vals_list)

    def write(self, vals):
        if ('mail_message_id' in vals or 'res_partner_id' in vals) and not self.env.is_admin():
            raise AccessError(_("Can not update the message or recipient of a notification."))
        if vals.get('is_read'):
            vals['read_date'] = fields.Datetime.now()
        return super(MailNotification, self).write(vals)

    @api.model
    def _gc_notifications(self, max_age_days=180):
        domain = [
            ('is_read', '=', True),
            ('read_date', '<', fields.Datetime.now() - relativedelta(days=max_age_days)),
            ('res_partner_id.partner_share', '=', False),
            ('notification_status', 'in', ('sent', 'canceled'))
        ]
        records = self.search(domain, limit=models.GC_UNLINK_LIMIT)
        if len(records) >= models.GC_UNLINK_LIMIT:
            self.env.ref('base.autovacuum_job')._trigger()
        return records.unlink()

    # ------------------------------------------------------------
    # TOOLS
    # ------------------------------------------------------------

    def format_failure_reason(self):
        self.ensure_one()
        if self.failure_type != 'unknown':
            return dict(self._fields['failure_type'].selection).get(self.failure_type, _('No Error'))
        else:
            if self.failure_reason:
                return _("Unknown error: %(error)s", error=self.failure_reason)
            return _("Unknown error")

    # ------------------------------------------------------------
    # DISCUSS
    # ------------------------------------------------------------

    def _filtered_for_web_client(self):
        """Returns only the notifications to show on the web client."""
        def _filter_unimportant_notifications(notif):
            if notif.notification_status in ['bounce', 'exception', 'canceled'] \
                    or notif.res_partner_id.partner_share:
                return True
            subtype = notif.mail_message_id.subtype_id
            return not subtype or subtype.track_recipients

        return self.filtered(_filter_unimportant_notifications)

    def _to_store(self, store: Store, /):
        """Returns the current notifications in the format expected by the web
        client."""
        for notif in self:
            data = notif._read_format(
                ["failure_type", "notification_status", "notification_type"], load=False
            )[0]
            data["message"] = Store.one(notif.mail_message_id, only_id=True)
            data["persona"] = Store.one(notif.res_partner_id, fields=["name"])
            store.add(notif, data)