Files
tradon/modules/marketing_automation/marketing_automation_reporting.py
2026-03-14 09:42:12 +00:00

260 lines
8.2 KiB
Python

# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from dateutil.relativedelta import relativedelta
from sql import Literal
from sql.aggregate import Count, Min
from sql.conditionals import NullIf
from sql.functions import DateTrunc, Round
from trytond.model import ModelSQL, ModelView, fields
from trytond.pool import Pool
from trytond.pyson import Eval, If
from trytond.transaction import Transaction
class Context(ModelView):
__name__ = 'marketing.automation.reporting.context'
from_date = fields.Date("From Date",
domain=[
If(Eval('to_date') & Eval('from_date'),
('from_date', '<=', Eval('to_date')),
()),
])
to_date = fields.Date("To Date",
domain=[
If(Eval('from_date') & Eval('to_date'),
('to_date', '>=', Eval('from_date')),
()),
])
period = fields.Selection([
('year', "Year"),
('month', "Month"),
('day', "Day"),
], "Period", required=True)
@classmethod
def default_from_date(cls):
pool = Pool()
Date = pool.get('ir.date')
context = Transaction().context
if 'from_date' in context:
return context['from_date']
return Date.today() - relativedelta(years=1)
@classmethod
def default_to_date(cls):
pool = Pool()
Date = pool.get('ir.date')
context = Transaction().context
if 'to_date' in context:
return context['to_date']
return Date.today()
@classmethod
def default_period(cls):
return Transaction().context.get('period', 'month')
class Abstract(ModelSQL, ModelView):
date = fields.Date("Date")
@classmethod
def table_query(cls):
from_item, tables, withs = cls._joins()
return from_item.select(*cls._columns(tables, withs),
where=cls._where(tables, withs),
group_by=cls._group_by(tables, withs),
with_=withs.values())
@classmethod
def _columns(cls, tables, withs):
return [
cls._column_id(tables, withs).as_('id'),
cls._column_date(tables, withs).as_('date'),
]
class Scenario(Abstract):
__name__ = 'marketing.automation.reporting.scenario'
scenario = fields.Many2One(
'marketing.automation.scenario', "Scenario")
record_count = fields.Integer("Records")
record_count_blocked = fields.Integer("Records Blocked")
block_rate = fields.Float("Block Rate")
@classmethod
def _joins(cls):
pool = Pool()
Record = pool.get('marketing.automation.record')
tables = {}
tables['record'] = record = Record.__table__()
withs = {}
return record, tables, withs
@classmethod
def _columns(cls, tables, withs):
record = tables['record']
record_count = Count(Literal('*'))
record_count_blocked = Count(Literal('*'), filter_=record.blocked)
return super()._columns(tables, withs) + [
record.scenario.as_('scenario'),
record_count.as_('record_count'),
record_count_blocked.as_('record_count_blocked'),
cls.block_rate.sql_cast(
Round(record_count_blocked / NullIf(record_count, 0), 2)).as_(
'block_rate'),
]
@classmethod
def _column_id(cls, tables, withs):
record = tables['record']
return Min(record.id)
@classmethod
def _column_date(cls, tables, withs):
context = Transaction().context
record = tables['record']
date = DateTrunc(context.get('period', 'month'), record.create_date)
return cls.date.sql_cast(date)
@classmethod
def _group_by(cls, tables, withs):
record = tables['record']
return [record.scenario, cls._column_date(tables, withs)]
@classmethod
def _where(cls, tables, withs):
context = Transaction().context
record = tables['record']
where = Literal(True)
from_date = context.get('from_date')
if from_date:
where &= record.create_date >= from_date
to_date = context.get('to_date')
if to_date:
where &= record.create_date <= to_date
return where
class Activity(Abstract):
__name__ = 'marketing.automation.reporting.activity'
activity = fields.Many2One(
'marketing.automation.activity', "Activity")
activity_action = fields.Function(
fields.Selection('get_activity_actions', "Activity Action"),
'on_change_with_activity_action')
record_count = fields.Integer("Records")
email_opened = fields.Integer(
"Emails Opened",
states={
'invisible': Eval('activity_action') != 'send_email',
})
email_clicked = fields.Integer(
"Emails Clicked",
states={
'invisible': Eval('activity_action') != 'send_email',
})
email_open_rate = fields.Float(
"Email Open Rate",
states={
'invisible': Eval('activity_action') != 'send_email',
})
email_click_rate = fields.Float(
"Email Click Rate",
states={
'invisible': Eval('activity_action') != 'send_email',
})
email_click_through_rate = fields.Float(
"Email Click-Through Rate",
states={
'invisible': Eval('activity_action') != 'send_email',
})
@classmethod
def get_activity_actions(cls):
pool = Pool()
Activity = pool.get('marketing.automation.activity')
return Activity.fields_get(['action'])['action']['selection']
@fields.depends('activity')
def on_change_with_activity_action(self, name=None):
if self.activity:
return self.activity.action
@classmethod
def _joins(cls):
pool = Pool()
RecordActivity = pool.get('marketing.automation.record.activity')
tables = {}
tables['record_activity'] = record = RecordActivity.__table__()
withs = {}
return record, tables, withs
@classmethod
def _columns(cls, tables, withs):
record_activity = tables['record_activity']
record_count = Count(
Literal('*'), filter_=record_activity.state == 'done')
email_opened = Count(
Literal('*'), filter_=record_activity.email_opened)
email_clicked = Count(
Literal('*'), filter_=record_activity.email_clicked)
return super()._columns(tables, withs) + [
record_activity.activity.as_('activity'),
record_count.as_('record_count'),
email_opened.as_('email_opened'),
email_clicked.as_('email_clicked'),
cls.email_open_rate.sql_cast(
Round(email_opened / NullIf(record_count, 0), 2)).as_(
'email_open_rate'),
cls.email_click_rate.sql_cast(
Round(email_clicked / NullIf(record_count, 0), 2)).as_(
'email_click_rate'),
cls.email_click_through_rate.sql_cast(
Round(email_clicked / NullIf(email_opened, 0), 2)).as_(
'email_click_through_rate'),
]
@classmethod
def _column_id(cls, tables, withs):
record_activity = tables['record_activity']
return Min(record_activity.id)
@classmethod
def _column_date(cls, tables, withs):
context = Transaction().context
record_activity = tables['record_activity']
date = DateTrunc(context.get('period', 'month'), record_activity.at)
return cls.date.sql_cast(date)
@classmethod
def _group_by(cls, tables, withs):
record_activity = tables['record_activity']
return [record_activity.activity, cls._column_date(tables, withs)]
@classmethod
def _where(cls, tables, withs):
context = Transaction().context
record_activity = tables['record_activity']
where = Literal(True)
from_date = context.get('from_date')
if from_date:
where &= record_activity.at >= from_date
to_date = context.get('to_date')
if to_date:
where &= record_activity.at <= to_date
return where