Files
odoo_source/addons/mail/wizard/mail_activity_schedule.py
T
Pierre-Yves Dufays 55d51b0882 [FIX] mail: fix activity due date format in the chatter
How to reproduce:
- Install crm with demo data
- Open the crm app
- From the menu "Configuration -> Activity Plans", create a plan
- Open a lead
- Schedule the plan just created for the lead

The log message in the chatter display due date in the wrong format:
Year(4)-month(2)-day(2) instead of the user date format (if your date format is
that one, please change it for testing).

This fixes the problem by displaying the date in the user format.

Task-3639909

closes odoo/odoo#152217

Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
2024-02-13 10:07:51 +00:00

330 lines
14 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from markupsafe import Markup
from odoo import api, fields, models, _
from odoo.addons.mail.tools.parser import parse_res_ids
from odoo.exceptions import ValidationError
from odoo.tools.misc import clean_context, format_date
class MailActivitySchedule(models.TransientModel):
_name = 'mail.activity.schedule'
_description = 'Activity schedule plan Wizard'
_batch_size = 500
@api.model
def default_get(self, fields_list):
res = super().default_get(fields_list)
context = self.env.context
active_res_ids = parse_res_ids(context.get('active_ids'))
if 'res_ids' in fields_list:
if active_res_ids and len(active_res_ids) <= self._batch_size:
res['res_ids'] = f"{context['active_ids']}"
elif not active_res_ids and context.get('active_id'):
res['res_ids'] = f"{[context['active_id']]}"
res_model = context.get('active_model') or context.get('params', {}).get('active_model', False)
if 'res_model' in fields_list:
res['res_model'] = res_model
return res
res_model_id = fields.Many2one(
'ir.model', string="Applies to",
compute="_compute_res_model_id", compute_sudo=True,
ondelete="cascade", precompute=True, readonly=False, required=True, store=True)
res_model = fields.Char("Model", readonly=True, required=True)
res_ids = fields.Text(
'Document IDs', compute='_compute_res_ids',
readonly=True, store=True, precompute=True)
is_batch_mode = fields.Boolean('Use in batch', compute='_compute_is_batch_mode')
company_id = fields.Many2one('res.company', 'Company',
compute='_compute_company_id', required=True)
# usage
error = fields.Html(compute='_compute_error')
has_error = fields.Boolean(compute='_compute_error')
# plan-based
plan_available_ids = fields.Many2many('mail.activity.plan', compute='_compute_plan_available_ids',
store=True, compute_sudo=True)
plan_id = fields.Many2one('mail.activity.plan', domain="[('id', 'in', plan_available_ids)]",
compute='_compute_plan_id', store=True, readonly=False)
plan_has_user_on_demand = fields.Boolean(related="plan_id.has_user_on_demand")
plan_assignation_summary = fields.Html(related='plan_id.assignation_summary')
plan_on_demand_user_id = fields.Many2one(
'res.users', 'Assigned To',
help='Choose assignation for activities with on demand assignation.',
default=lambda self: self.env.user)
plan_date_deadline = fields.Date(
'Plan Date', compute='_compute_plan_date_deadline',
store=True, readonly=False)
# activity-based
activity_type_id = fields.Many2one(
'mail.activity.type', string='Activity Type',
compute='_compute_activity_type_id', store=True, readonly=False,
domain="['|', ('res_model', '=', False), ('res_model', '=', res_model)]", ondelete='set null')
activity_category = fields.Selection(related='activity_type_id.category', readonly=True)
date_deadline = fields.Date(
'Due Date', compute="_compute_date_deadline",
readonly=False, store=True)
summary = fields.Char(
'Summary', compute="_compute_summary",
readonly=False, store=True)
note = fields.Html(
'Note', compute="_compute_note",
readonly=False, store=True, sanitize_style=True)
activity_user_id = fields.Many2one(
'res.users', 'Assigned to', compute='_compute_activity_user_id',
readonly=False, store=True)
chaining_type = fields.Selection(related='activity_type_id.chaining_type', readonly=True)
@api.depends('res_model')
def _compute_res_model_id(self):
for scheduler in self:
scheduler.res_model_id = self.env['ir.model']._get_id(scheduler.res_model)
@api.depends_context('active_ids')
def _compute_res_ids(self):
context = self.env.context
for scheduler in self.filtered(lambda scheduler: not scheduler.res_ids):
active_res_ids = parse_res_ids(context.get('active_ids'))
if active_res_ids and len(active_res_ids) <= self._batch_size:
scheduler.res_ids = f"{context['active_ids']}"
elif not active_res_ids and context.get('active_id'):
scheduler.res_ids = f"{[context['active_id']]}"
@api.depends('res_model_id', 'res_ids')
def _compute_company_id(self):
for scheduler in self:
applied_on = scheduler._get_applied_on_records()
scheduler.company_id = (applied_on and 'company_id' in applied_on[0]._fields and applied_on[0].company_id
) or self.env.company
@api.depends('company_id', 'res_model_id', 'res_ids',
'plan_id', 'plan_on_demand_user_id', 'plan_available_ids', # plan specific
'activity_type_id', 'activity_user_id') # activity specific
def _compute_error(self):
for scheduler in self:
errors = set()
applied_on = scheduler._get_applied_on_records()
if applied_on and ('company_id' in scheduler.env[applied_on._name]._fields and
len(applied_on.mapped('company_id')) > 1):
errors.add(_('The records must belong to the same company.'))
if scheduler.plan_id:
errors |= set(scheduler._check_plan_templates_error(applied_on))
if errors:
error_header = (
_('The plan "%(plan_name)s" cannot be launched:', plan_name=scheduler.plan_id.name) if scheduler.plan_id
else _('The activity cannot be launched:')
)
error_body = Markup('<ul>%s</ul>') % (
Markup().join(Markup('<li>%s</li>') % error for error in errors)
)
scheduler.error = f'{error_header}{error_body}'
scheduler.has_error = True
else:
scheduler.error = False
scheduler.has_error = False
@api.depends('res_ids')
def _compute_is_batch_mode(self):
for scheduler in self:
scheduler.is_batch_mode = len(scheduler._evaluate_res_ids()) > 1
@api.depends('company_id', 'res_model_id')
def _compute_plan_available_ids(self):
for scheduler in self:
scheduler.plan_available_ids = self.env['mail.activity.plan'].search(self._get_plan_available_base_domain())
@api.depends_context('plan_mode')
@api.depends('plan_available_ids')
def _compute_plan_id(self):
for scheduler in self:
if self.env.context.get('plan_mode'):
scheduler.plan_id = scheduler.env['mail.activity.plan'].search(
[('id', 'in', self.plan_available_ids.ids)], order='id', limit=1)
else:
scheduler.plan_id = False
@api.depends('res_model_id', 'res_ids')
def _compute_plan_date_deadline(self):
self.plan_date_deadline = False
@api.depends('res_model')
def _compute_activity_type_id(self):
for scheduler in self:
if not scheduler.activity_type_id:
scheduler.activity_type_id = scheduler.env['mail.activity']._default_activity_type_for_model(scheduler.res_model)
@api.depends('activity_type_id')
def _compute_date_deadline(self):
for scheduler in self:
if scheduler.activity_type_id:
scheduler.date_deadline = scheduler.env['mail.activity']._calculate_date_deadline(scheduler.activity_type_id)
elif not scheduler.date_deadline:
scheduler.date_deadline = fields.Date.context_today(scheduler)
@api.depends('activity_type_id')
def _compute_summary(self):
for scheduler in self:
if scheduler.activity_type_id.summary:
scheduler.summary = scheduler.activity_type_id.summary
@api.depends('activity_type_id')
def _compute_note(self):
for scheduler in self:
if scheduler.activity_type_id.default_note:
scheduler.note = scheduler.activity_type_id.default_note
@api.depends('activity_type_id')
def _compute_activity_user_id(self):
for scheduler in self:
if scheduler.activity_type_id.default_user_id:
scheduler.activity_user_id = scheduler.activity_type_id.default_user_id
elif not scheduler.activity_user_id:
scheduler.activity_user_id = self.env.user
# Any writable fields that can change error computed field
@api.constrains('res_model_id', 'res_ids', # records (-> responsible)
'plan_id', 'plan_on_demand_user_id', # plan specific
'activity_type_id', 'activity_user_id') # activity specific
def _check_consistency(self):
for scheduler in self.filtered('error'):
raise ValidationError(scheduler.error)
@api.constrains('res_ids')
def _check_res_ids(self):
""" Check res_ids is a valid list of integers (or Falsy). """
for scheduler in self:
scheduler._evaluate_res_ids()
# ------------------------------------------------------------
# PLAN-BASED SCHEDULING API
# ------------------------------------------------------------
def action_schedule_plan(self):
applied_on = self._get_applied_on_records()
for record in applied_on:
body = _('The plan "%(plan_name)s" has been started', plan_name=self.plan_id.name)
activity_descriptions = []
for template in self._plan_filter_activity_templates_to_schedule():
if template.responsible_type == 'on_demand':
responsible = self.plan_on_demand_user_id
else:
responsible = template._determine_responsible(self.plan_on_demand_user_id, record)['responsible']
date_deadline = self.env['mail.activity']._calculate_date_deadline(
template.activity_type_id, force_base_date=self.plan_date_deadline)
record.activity_schedule(
activity_type_id=template.activity_type_id.id,
automated=False,
summary=template.summary,
note=template.note,
user_id=responsible.id,
date_deadline=date_deadline
)
activity_descriptions.append(
_('%(activity)s, assigned to %(name)s, due on the %(deadline)s',
activity=template.summary or template.activity_type_id.name,
name=responsible.name, deadline=format_date(self.env, date_deadline)))
if activity_descriptions:
body += Markup('<ul>%s</ul>') % (
Markup().join(Markup('<li>%s</li>') % description for description in activity_descriptions)
)
record.message_post(body=body)
if len(applied_on) == 1:
return {
'type': 'ir.actions.act_window',
'res_model': self.res_model,
'res_id': applied_on.id,
'name': applied_on.display_name,
'view_mode': 'form',
'views': [(False, "form")],
}
return {
'type': 'ir.actions.act_window',
'res_model': self.res_model,
'name': _('Launch Plans'),
'view_mode': 'tree,form',
'target': 'current',
'domain': [('id', 'in', applied_on.ids)],
}
def _check_plan_templates_error(self, applied_on):
self.ensure_one()
return filter(
None, [
activity_template._determine_responsible(self.plan_on_demand_user_id, record)['error']
for activity_template in self.plan_id.template_ids
for record in applied_on
]
)
# ------------------------------------------------------------
# ACTIVITY-BASED SCHEDULING API
# ------------------------------------------------------------
def action_schedule_activities(self):
self._action_schedule_activities()
def action_schedule_activities_done(self):
self._action_schedule_activities().action_done()
def action_schedule_activities_done_and_schedule(self):
ctx = dict(
clean_context(self.env.context),
default_previous_activity_type_id=self.activity_type_id.id,
activity_previous_deadline=self.date_deadline,
default_res_ids=self.res_ids,
default_res_model=self.res_model,
)
_messages, next_activities = self._action_schedule_activities()._action_done()
if next_activities:
return False
return {
'name': _("Schedule Activity On Selected Records") if self.is_batch_mode else _("Schedule Activity"),
'context': ctx,
'view_mode': 'form',
'res_model': 'mail.activity.schedule',
'views': [(False, 'form')],
'type': 'ir.actions.act_window',
'target': 'new',
}
def _action_schedule_activities(self):
return self._get_applied_on_records().activity_schedule(
activity_type_id=self.activity_type_id.id,
automated=False,
summary=self.summary,
note=self.note,
user_id=self.activity_user_id.id,
date_deadline=self.date_deadline
)
# ------------------------------------------------------------
# TOOLS
# ------------------------------------------------------------
def _evaluate_res_ids(self):
""" Parse composer res_ids, which can be: an already valid list or
tuple (generally in code), a list or tuple as a string (coming from
actions). Void strings / missing values are evaluated as an empty list.
:return: a list of IDs (empty list in case of falsy strings)"""
self.ensure_one()
return parse_res_ids(self.res_ids) or []
def _get_applied_on_records(self):
return self.env[self.res_model].browse(self._evaluate_res_ids())
def _get_plan_available_base_domain(self):
self.ensure_one()
return [
'&', '|', ('company_id', '=', False), ('company_id', '=', self.company_id.id),
'|', ('res_model', '=', False), ('res_model', '=', self.res_model)
]
def _plan_filter_activity_templates_to_schedule(self):
return self.plan_id.template_ids