Odoo18-Base/extra-addons/marketing_automation/models/marketing_trace.py
hoangvv b8024171a2 - add modules(marketing automation + approvals + webstudio) \n
- create new module hr_promote (depend: hr,approvals) \n
- customize approval_type (WIP)
2025-01-17 07:32:51 +07:00

125 lines
5.4 KiB
Python

# -*- coding: utf-8 -*-
# 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.fields import Datetime
class MarketingTrace(models.Model):
_name = 'marketing.trace'
_description = 'Marketing Trace'
_order = 'schedule_date DESC, id ASC'
_rec_name = 'participant_id'
participant_id = fields.Many2one(
'marketing.participant', string='Participant',
index=True, ondelete='cascade', required=True)
res_id = fields.Integer(string='Document ID', related='participant_id.res_id', index=True, store=True, readonly=False)
is_test = fields.Boolean(string='Test Trace', related='participant_id.is_test', index=True, store=True, readonly=True)
activity_id = fields.Many2one(
'marketing.activity', string='Activity',
index=True, ondelete='cascade', required=True)
activity_type = fields.Selection(related='activity_id.activity_type', readonly=True)
trigger_type = fields.Selection(related='activity_id.trigger_type', readonly=True)
state = fields.Selection([
('scheduled', 'Scheduled'),
('processed', 'Processed'),
('rejected', 'Rejected'),
('canceled', 'Cancelled'),
('error', 'Error')
], default='scheduled', index=True, required=True)
schedule_date = fields.Datetime()
state_msg = fields.Char(string='Error message')
# hierarchy
parent_id = fields.Many2one('marketing.trace', string='Parent', index=True, ondelete='cascade')
child_ids = fields.One2many('marketing.trace', 'parent_id', string='Direct child traces')
# mailing traces
mailing_trace_ids = fields.One2many('mailing.trace', 'marketing_trace_id', string='Mass mailing statistics')
mailing_trace_status = fields.Selection(related='mailing_trace_ids.trace_status', readonly=True)
links_click_datetime = fields.Datetime(compute='_compute_links_click_datetime')
@api.depends('mailing_trace_ids')
def _compute_links_click_datetime(self):
# necessary, because sometimes mailing_trace_ids aren't available
# due to failed messages, which prevents `links_click_datetime` to get assigned
self.links_click_datetime = False
mailing_trace = self.filtered(lambda x: x.mailing_trace_ids)
for trace in mailing_trace:
trace.links_click_datetime = trace.mailing_trace_ids[0].links_click_datetime
def participant_action_cancel(self):
self.action_cancel(message=_('Manually'))
def action_cancel(self, message=None):
values = {'state': 'canceled', 'schedule_date': Datetime.now()}
if message:
values['state_msg'] = message
self.write(values)
self.mapped('participant_id').check_completed()
def action_execute(self):
self.activity_id.execute_on_traces(self)
# DANE: try to make this function to work on batches later
def process_event(self, action):
"""Process event coming from customers currently centered on email actions.
It updates child traces :
* opposite actions are canceled, for example mail_not_open when mail_open is triggered;
* bounced mail cancel all child actions not being mail_bounced;
:param string action: see trigger_type field of activity
"""
self.ensure_one()
if self.participant_id.campaign_id.state not in ['draft', 'running']:
return
now = Datetime.from_string(Datetime.now())
msg = {
'mail_not_reply': _('Parent activity mail replied'),
'mail_not_click': _('Parent activity mail clicked'),
'mail_not_open': _('Parent activity mail opened'),
'mail_bounce': _('Parent activity mail bounced'),
}
opened_child = self.child_ids.filtered(lambda trace: trace.state == 'scheduled')
cron_trigger_dates = set()
for next_trace in opened_child.filtered(lambda trace: trace.activity_id.trigger_type == action):
if next_trace.activity_id.interval_number == 0:
next_trace.write({
'schedule_date': now,
})
next_trace.activity_id.execute_on_traces(next_trace)
else:
schedule_date = now + relativedelta(**{
next_trace.activity_id.interval_type: next_trace.activity_id.interval_number
})
next_trace.write({
'schedule_date': schedule_date,
})
cron_trigger_dates.add(schedule_date)
if cron_trigger_dates:
# based on updated activities, we schedule CRON triggers that match the scheduled_dates
# we use a set to only trigger the CRON once per timeslot event if there are multiple
# marketing.participants
cron = self.env.ref('marketing_automation.ir_cron_campaign_execute_activities')
cron._trigger(cron_trigger_dates)
if action in ['mail_reply', 'mail_click', 'mail_open']:
opposite_trigger = action.replace('_', '_not_')
opened_child.filtered(
lambda trace: trace.activity_id.trigger_type == opposite_trigger
).action_cancel(message=msg[opposite_trigger])
elif action == 'mail_bounce':
opened_child.filtered(
lambda trace: trace.activity_id.trigger_type != 'mail_bounce'
).action_cancel(message=msg[action])
return True