forked from Mapan/odoo17e
312 lines
15 KiB
Python
312 lines
15 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from ast import literal_eval
|
|
|
|
from odoo import api, Command, fields, models, _
|
|
from odoo.addons.sale.models.sale_order import SALE_ORDER_STATE
|
|
|
|
|
|
class BaseAutomation(models.Model):
|
|
_inherit = 'base.automation'
|
|
|
|
is_sale_order_alert = fields.Boolean(readonly=True, default=False, string='Is Sale Order Alert')
|
|
|
|
|
|
class SaleOrderAlert(models.Model):
|
|
_name = 'sale.order.alert'
|
|
_description = 'Sale Order Alert'
|
|
_inherits = {'base.automation': 'automation_id'}
|
|
_check_company_auto = True
|
|
|
|
@api.model
|
|
def default_get(self, default_fields):
|
|
res = super().default_get(default_fields)
|
|
if 'model_id' in default_fields:
|
|
# model_id default cannot be specified at field level
|
|
# because model_id is an inherited field from base.automation
|
|
res['model_id'] = self.env['ir.model']._get_id('sale.order')
|
|
return res
|
|
|
|
automation_id = fields.Many2one('base.automation', 'Automation Rule', required=True, ondelete='restrict')
|
|
action_id = fields.Many2one('ir.actions.server', string='Server Action', ondelete='restrict')
|
|
|
|
template_id = fields.Many2one(related='action_id.template_id', readonly=False)
|
|
sms_template_id = fields.Many2one(related='action_id.sms_template_id', readonly=False)
|
|
activity_type_id = fields.Many2one(related='action_id.activity_type_id', readonly=False)
|
|
activity_summary = fields.Char(related='action_id.activity_summary', readonly=False)
|
|
activity_note = fields.Html(related='action_id.activity_note', readonly=False)
|
|
activity_date_deadline_range = fields.Integer(related='action_id.activity_date_deadline_range', readonly=False)
|
|
activity_date_deadline_range_type = fields.Selection(related='action_id.activity_date_deadline_range_type', readonly=False)
|
|
activity_user_id = fields.Many2one(related='action_id.activity_user_id', readonly=False)
|
|
|
|
action = fields.Selection([
|
|
('next_activity', 'Create next activity'),
|
|
('mail_post', 'Send an email to the customer'),
|
|
('sms', 'Send an SMS Text Message to the customer'),
|
|
('set_health_value', 'Set Contract Health value')
|
|
], string='Action To Do', required=True, default=None)
|
|
trigger_condition = fields.Selection([
|
|
('on_create_or_write', 'Modification'), ('on_time', 'Timed Condition')], string='Trigger On', required=True, default='on_create_or_write')
|
|
currency_id = fields.Many2one('res.currency', string='Currency', default=lambda self: self.env.company.currency_id)
|
|
subscription_plan_ids = fields.Many2many('sale.subscription.plan', string='Subscription Plans', check_company=True)
|
|
customer_ids = fields.Many2many('res.partner', string='Customers')
|
|
company_id = fields.Many2one('res.company', string='Company')
|
|
mrr_min = fields.Monetary('MRR Range Min', currency_field='currency_id')
|
|
team_ids = fields.Many2many('crm.team', string='Sales Team')
|
|
mrr_max = fields.Monetary('MRR Range Max', currency_field='currency_id')
|
|
product_ids = fields.Many2many(
|
|
'product.product', string='Specific Products',
|
|
check_company=True,
|
|
domain="[('product_tmpl_id.recurring_invoice', '=', True)]")
|
|
mrr_change_amount = fields.Float('MRR Change Amount')
|
|
mrr_change_unit = fields.Selection(selection='_get_selection_mrr_change_unit', string='MRR Change Unit', default='percentage')
|
|
mrr_change_period = fields.Selection([('1month', '1 Month'), ('3months', '3 Months')], string='MRR Change Period',
|
|
default='1month', help="Period over which the KPI is calculated")
|
|
rating_percentage = fields.Integer('Rating Percentage', help="Rating Satisfaction is the ratio of positive rating to total number of rating.")
|
|
rating_operator = fields.Selection([('>', 'greater than'), ('<', 'less than')], string='Rating Operator', default='>')
|
|
subscription_state_from = fields.Selection(
|
|
string='Stage from',
|
|
selection=[
|
|
('1_draft', 'Quotation'), # Quotation for a new subscription
|
|
('3_progress', 'In Progress'), # Active Subscription or confirmed renewal for active subscription
|
|
('6_churn', 'Churned'), # Closed or ended subscription
|
|
('2_renewal', 'Renewal Quotation'), # Renewal Quotation for existing subscription
|
|
('5_renewed', 'Renewed'), # Active or ended subscription that has been renewd
|
|
('4_paused', 'Paused'), # Active subcription with paused invoicing
|
|
('7_upsell', 'Upsell'), # Quotation or SO upselling a subscription
|
|
]
|
|
)
|
|
|
|
subscription_state = fields.Selection(
|
|
string='Stage',
|
|
selection=[
|
|
('1_draft', 'Quotation'), # Quotation for a new subscription
|
|
('3_progress', 'In Progress'), # Active Subscription or confirmed renewal for active subscription
|
|
('6_churn', 'Churned'), # Closed or ended subscription
|
|
('2_renewal', 'Renewal Quotation'), # Renewal Quotation for existing subscription
|
|
('5_renewed', 'Renewed'), # Active or ended subscription that has been renewd
|
|
('4_paused', 'Paused'), # Active subcription with paused invoicing
|
|
('7_upsell', 'Upsell'), # Quotation or SO upselling a subscription
|
|
]
|
|
)
|
|
order_state = fields.Selection(selection=SALE_ORDER_STATE, string="Status")
|
|
activity_user = fields.Selection([
|
|
('contract', 'Subscription Salesperson'),
|
|
('channel_leader', 'Sales Team Leader'),
|
|
('users', 'Specific Users'),
|
|
], string='Assign To')
|
|
activity_user_ids = fields.Many2many('res.users', string='Specific Users')
|
|
subscription_count = fields.Integer(compute='_compute_subscription_count')
|
|
cron_nextcall = fields.Datetime(compute='_compute_nextcall', store=False)
|
|
health = fields.Selection([('normal', 'Neutral'), ('done', 'Good'), ('bad', 'Bad')], string="Health", help="Show the health status")
|
|
|
|
@api.onchange('trigger_condition', 'automation_id')
|
|
def _onchange_automation_trigger(self):
|
|
# This method is needed to force saving the automation_id.trigger according to trigger_condition value
|
|
# Overriding create/write does is not sufficient anymore as the automation_id.trg_date_range_type is
|
|
# evaluated before calling super/write.
|
|
for alert in self:
|
|
alert.automation_id.trigger = alert.trigger_condition
|
|
|
|
def _get_action_activity_values(self):
|
|
if self.activity_user == 'users':
|
|
action_commands = [Command.create({
|
|
'name': '%s-%s' % (self.name, seq),
|
|
'sequence': seq,
|
|
'state': 'next_activity',
|
|
'model_id': self.model_id.id,
|
|
'activity_summary': self.activity_summary,
|
|
'activity_type_id': self.activity_type_id.id,
|
|
'activity_note': self.activity_note,
|
|
'activity_date_deadline_range': self.activity_date_deadline_range,
|
|
'activity_date_deadline_range_type': self.activity_date_deadline_range_type,
|
|
'activity_user_type': 'specific',
|
|
'activity_user_id': user.id,
|
|
'usage': 'base_automation',
|
|
}) for seq, user in enumerate(self.activity_user_ids, 1)]
|
|
return {
|
|
'state': 'multi',
|
|
'child_ids': action_commands
|
|
}
|
|
elif self.activity_user == 'contract':
|
|
return {
|
|
'state': 'next_activity',
|
|
'activity_user_type': 'generic',
|
|
'activity_user_field_name': 'user_id',
|
|
}
|
|
elif self.activity_user == 'channel_leader':
|
|
return {
|
|
'state': 'next_activity',
|
|
'activity_user_type': 'generic',
|
|
'activity_user_field_name': 'team_user_id',
|
|
}
|
|
|
|
def _get_alert_domain(self):
|
|
domain = [('is_subscription', '=', True)]
|
|
if self.subscription_plan_ids:
|
|
domain += [('plan_id', 'in', self.subscription_plan_ids.ids)]
|
|
if self.customer_ids:
|
|
domain += [('partner_id', 'in', self.customer_ids.ids)]
|
|
if self.team_ids:
|
|
domain += [('team_id', 'in', self.team_ids.ids)]
|
|
if self.company_id:
|
|
domain += [('company_id', '=', self.company_id.id)]
|
|
if self.mrr_min:
|
|
domain += [('recurring_monthly', '>=', self.mrr_min)]
|
|
if self.mrr_max:
|
|
domain += [('recurring_monthly', '<=', self.mrr_max)]
|
|
if self.product_ids:
|
|
domain += [('order_line.product_id', 'in', self.product_ids.ids)]
|
|
if self.mrr_change_amount:
|
|
if self.mrr_change_unit == 'percentage':
|
|
domain += [('kpi_%s_mrr_percentage' % self.mrr_change_period, '>', self.mrr_change_amount / 100)]
|
|
else:
|
|
domain += [('kpi_%s_mrr_delta' % self.mrr_change_period, '>', self.mrr_change_amount)]
|
|
if self.rating_percentage:
|
|
domain += [('percentage_satisfaction', self.rating_operator, self.rating_percentage)]
|
|
if self.subscription_state:
|
|
domain += [('subscription_state', '=', self.subscription_state)]
|
|
elif self.subscription_state_from:
|
|
domain += [('subscription_state', '!=', self.subscription_state_from)]
|
|
if self.order_state:
|
|
domain += [('state', '=', self.order_state)]
|
|
return domain
|
|
|
|
def _get_selection_mrr_change_unit(self):
|
|
return [('percentage', '%'), ('currency', self.env.company.currency_id.symbol)]
|
|
|
|
def _compute_subscription_count(self):
|
|
for alert in self:
|
|
domain = literal_eval(alert.filter_domain) if alert.filter_domain else []
|
|
alert.subscription_count = self.env['sale.order'].search_count(domain)
|
|
|
|
def _get_action_template_values(self):
|
|
self.ensure_one()
|
|
if self.action == 'mail_post':
|
|
return {'template_id': self.template_id.id}
|
|
elif self.action == 'sms':
|
|
return {'sms_template_id': self.sms_template_id.id}
|
|
elif self.action == 'next_activity':
|
|
return {
|
|
'activity_type_id': self.activity_type_id and self.activity_type_id.id,
|
|
'activity_summary': self.activity_summary,
|
|
'activity_note': self.activity_note,
|
|
'activity_date_deadline_range': self.activity_date_deadline_range,
|
|
'activity_date_deadline_range_type': self.activity_date_deadline_range_type,
|
|
'activity_user_id': self.activity_user_id and self.activity_user_id.id,
|
|
}
|
|
return {}
|
|
|
|
def _create_actions(self):
|
|
action_values = [{
|
|
'name': alert.name,
|
|
'usage': 'base_automation',
|
|
'model_id': alert.model_id.id,
|
|
'base_automation_id': alert.automation_id.id,
|
|
**alert._get_action_template_values()
|
|
} for alert in self]
|
|
|
|
actions = self.env['ir.actions.server'].create(action_values)
|
|
|
|
for alert, action in zip(self, actions):
|
|
alert.action_id = action
|
|
alert.action_server_ids = [action.id]
|
|
|
|
def _configure_alerts(self, vals_list):
|
|
# Unlink the children server actions if not needed anymore
|
|
self.filtered(lambda alert: alert.action != 'next_activity' and alert.action_id.child_ids).action_id.child_ids.unlink()
|
|
|
|
field_names = ['subscription_state', 'health']
|
|
tag_fields = self.env['ir.model.fields'].search([('model', 'in', self.mapped('model_name')), ('name', 'in', field_names)])
|
|
|
|
for alert, vals in zip(self, vals_list):
|
|
# Configure alert
|
|
alert_values = {}
|
|
if not vals.get('filter_domain'):
|
|
alert_values['filter_domain'] = alert._get_alert_domain()
|
|
if not vals.get('filter_pre_domain'):
|
|
if alert.subscription_state_from:
|
|
alert_values['filter_pre_domain'] = [('subscription_state', '=', alert.subscription_state_from)]
|
|
elif alert.subscription_state:
|
|
alert_values['filter_pre_domain'] = [('subscription_state', '!=', alert.subscription_state)]
|
|
else:
|
|
alert_values['filter_pre_domain'] = []
|
|
if alert_values:
|
|
alert.with_context(skip_configure_alerts=True).write(alert_values)
|
|
|
|
# Configure action
|
|
action_values = {}
|
|
field_name = None
|
|
if alert.action == 'subscription_state' and alert.subscription_state:
|
|
field_name = 'subscription_state'
|
|
action_values['selection_value'] = alert.subscription_state
|
|
elif alert.action == 'set_health_value' and alert.health:
|
|
field_name = 'health'
|
|
action_values['value'] = alert.health
|
|
|
|
if field_name:
|
|
tag_field = tag_fields.filtered(lambda t: t.name == field_name)
|
|
action_values['state'] = 'object_write'
|
|
action_values['update_path'] = tag_field.name
|
|
action_values['evaluation_type'] = 'value'
|
|
elif vals.get('action') in ('mail_post', 'sms'):
|
|
action_values['state'] = vals['action']
|
|
elif vals.get('action') == 'next_activity' or vals.get('activity_user_ids') or vals.get('activity_user'):
|
|
self.action_id.child_ids.unlink()
|
|
action_values = alert._get_action_activity_values()
|
|
|
|
if action_values:
|
|
alert.action_id.write(action_values)
|
|
|
|
@api.model_create_multi
|
|
def create(self, vals_list):
|
|
for vals in vals_list:
|
|
vals['is_sale_order_alert'] = True
|
|
if vals.get('trigger_condition'):
|
|
vals['trigger'] = vals['trigger_condition']
|
|
alerts = super().create(vals_list)
|
|
alerts._create_actions()
|
|
alerts._configure_alerts(vals_list)
|
|
return alerts
|
|
|
|
def write(self, vals):
|
|
if vals.get('trigger_condition'):
|
|
vals['trigger'] = vals['trigger_condition']
|
|
res = super().write(vals)
|
|
if not self._context.get('skip_configure_alerts'):
|
|
self._configure_alerts([vals])
|
|
return res
|
|
|
|
def unlink(self):
|
|
self.automation_id.active = False
|
|
return super().unlink()
|
|
|
|
def action_view_subscriptions(self):
|
|
self.ensure_one()
|
|
domain = literal_eval(self.filter_domain) if self.filter_domain else [('is_subscription', '=', True)]
|
|
return {
|
|
'type': 'ir.actions.act_window',
|
|
'name': _('Subscriptions'),
|
|
'res_model': 'sale.order',
|
|
'view_mode': 'kanban,tree,form,pivot,graph,cohort,activity',
|
|
'domain': domain,
|
|
'context': {'create': False},
|
|
}
|
|
|
|
def run_cron_manually(self):
|
|
self.ensure_one()
|
|
domain = literal_eval(self.filter_domain)
|
|
subs = self.env['sale.order'].search(domain)
|
|
ctx = {
|
|
'active_model': 'sale.order',
|
|
'active_ids': subs.ids,
|
|
'domain_post': domain,
|
|
}
|
|
for action_server in self.action_server_ids.with_context(**ctx):
|
|
action_server.run()
|
|
|
|
def _compute_nextcall(self):
|
|
cron = self.env.ref('sale_subscription.ir_cron_sale_subscription_update_kpi', raise_if_not_found=False)
|
|
self.cron_nextcall = cron.nextcall if cron else False
|