[IMP] mail: better prevent activity type removal and add a fallback mechanism
Current activity types master data cannot be unlinked through a quite harsh
override of unlink using xml id existence, introduced at 2602642a78.
Some of them are real master data, other could be unlinked if users want it.
Finally custom activity types may cause errors when removed if used in some
side models like server actions or automated actions.
To allow more flexibility we remove the removal constraint and implement
a fallback mechanism. It is implemented in mail.activity.mixin when calling
activity_schedule(xmlid, ...). If the given xmlid is not found
_default_activity_type() specifies the default activity to use. It can
be overridden in modules if some specific behavior is wanted.
e.g.
def _default_activity_type(self):
"""Define a default fallback activity type when xml id not found
only used in in activity_schedule() for now.
"""
try:
return self.env.ref('mail.mail_activity_data_todo')
except Exception:
return False
In this commit we also
* restrict deletion of activity type (hr.plan and ir.actions.server);
* fallback on default activity type if xmlid ref not found in
activity_schedule;
* correct calls to activity_schedule and replace some manual creation calls
by activity_schedule;
Task ID 1961156
PR #39013
Related: odoo/enterprise#6238
Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
Co-authored-by: Victor Feyens <vfe@odoo.com>
Co-authored-by: Michaël Mattiello <mcm@odoo.com>
This commit is contained in:
committed by
Thibault Delavallée
co-authored by
Michaël Mattiello
parent
b7983df87a
commit
0eadc0aa5e
@@ -13,7 +13,8 @@ class HrPlanActivityType(models.Model):
|
||||
activity_type_id = fields.Many2one(
|
||||
'mail.activity.type', 'Activity Type',
|
||||
default=lambda self: self.env.ref('mail.mail_activity_data_todo'),
|
||||
domain=lambda self: ['|', ('res_model_id', '=', False), ('res_model_id', '=', self.env['ir.model']._get('hr.employee').id)]
|
||||
domain=lambda self: ['|', ('res_model_id', '=', False), ('res_model_id', '=', self.env['ir.model']._get('hr.employee').id)],
|
||||
ondelete='restrict'
|
||||
)
|
||||
summary = fields.Char('Summary')
|
||||
responsible = fields.Selection([
|
||||
|
||||
@@ -24,14 +24,12 @@ class HrPlanWizard(models.TransientModel):
|
||||
responsible = activity_type.get_responsible_id(self.employee_id)
|
||||
|
||||
if self.env['hr.employee'].with_user(responsible).check_access_rights('read', raise_exception=False):
|
||||
self.env['mail.activity'].create({
|
||||
'res_id': self.employee_id.id,
|
||||
'res_model_id': self.env['ir.model']._get('hr.employee').id,
|
||||
'summary': activity_type.summary,
|
||||
'note': activity_type.note,
|
||||
'activity_type_id': activity_type.activity_type_id.id,
|
||||
'user_id': responsible.id,
|
||||
})
|
||||
self.employee_id.activity_schedule(
|
||||
activity_type_id=activity_type.activity_type_id.id,
|
||||
summary=activity_type.summary,
|
||||
note=activity_type.note,
|
||||
user_id=responsible.id
|
||||
)
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
|
||||
@@ -244,11 +244,10 @@ class FetchmailServer(models.Model):
|
||||
related_invoice.message_post(
|
||||
body=(_("Errors in the E-Invoice :<br/>%s") % (error))
|
||||
)
|
||||
activity_vals = {
|
||||
'activity_type_id': self.env.ref('mail.mail_activity_data_todo').id,
|
||||
'invoice_user_id': related_invoice.invoice_user_id.id if related_invoice.invoice_user_id else self.env.user.id
|
||||
}
|
||||
related_invoice.activity_schedule(summary='Rejection notice', **activity_vals)
|
||||
related_invoice.activity_schedule(
|
||||
'mail.mail_activity_data_todo',
|
||||
summary='Rejection notice',
|
||||
user_id=related_invoice.invoice_user_id.id if related_invoice.invoice_user_id else self.env.user.id)
|
||||
|
||||
elif receipt_type == 'MC':
|
||||
# Failed delivery notice
|
||||
@@ -304,11 +303,10 @@ class FetchmailServer(models.Model):
|
||||
body=(_("Outcome notice: %s<br/>%s") % (related_invoice.l10n_it_send_state, info))
|
||||
)
|
||||
if related_invoice.l10n_it_send_state == 'delivered_refused':
|
||||
activity_vals = {
|
||||
'activity_type_id': self.env.ref('mail.mail_activity_data_todo').id,
|
||||
'invoice_user_id': related_invoice.invoice_user_id.id if related_invoice.invoice_user_id else self.env.user.id
|
||||
}
|
||||
related_invoice.activity_schedule(summary='Outcome notice: Refused', **activity_vals)
|
||||
related_invoice.activity_schedule(
|
||||
'mail.mail_activity_todo',
|
||||
user_id=related_invoice.invoice_user_id.id if related_invoice.invoice_user_id else self.env.user.id,
|
||||
summary='Outcome notice: Refused')
|
||||
|
||||
# elif receipt_type == 'MT':
|
||||
# Metadata file
|
||||
|
||||
@@ -29,7 +29,8 @@ class ServerActions(models.Model):
|
||||
# Next Activity
|
||||
activity_type_id = fields.Many2one(
|
||||
'mail.activity.type', string='Activity',
|
||||
domain="['|', ('res_model_id', '=', False), ('res_model_id', '=', model_id)]")
|
||||
domain="['|', ('res_model_id', '=', False), ('res_model_id', '=', model_id)]",
|
||||
ondelete='restrict')
|
||||
activity_summary = fields.Char('Summary')
|
||||
activity_note = fields.Html('Note')
|
||||
activity_date_deadline_range = fields.Integer(string='Due Date In')
|
||||
|
||||
@@ -59,7 +59,7 @@ class MailActivityType(models.Model):
|
||||
help='Specify a model if the activity should be specific to a model'
|
||||
' and not available when managing activities for other models.')
|
||||
default_next_type_id = fields.Many2one('mail.activity.type', 'Default Next Activity',
|
||||
domain="['|', ('res_model_id', '=', False), ('res_model_id', '=', res_model_id)]")
|
||||
domain="['|', ('res_model_id', '=', False), ('res_model_id', '=', res_model_id)]", ondelete='restrict')
|
||||
force_next = fields.Boolean("Trigger Next Activity", default=False)
|
||||
next_type_ids = fields.Many2many(
|
||||
'mail.activity.type', 'mail_activity_rel', 'activity_id', 'recommended_id',
|
||||
@@ -91,11 +91,6 @@ class MailActivityType(models.Model):
|
||||
for activity_type in self:
|
||||
activity_type.initial_res_model_id = activity_type.res_model_id
|
||||
|
||||
def unlink(self):
|
||||
if any(self.get_external_id().values()) and not self._context.get(MODULE_UNINSTALL_FLAG):
|
||||
raise exceptions.ValidationError("You can not delete activity type that are used as master data.")
|
||||
return super(MailActivityType, self).unlink()
|
||||
|
||||
|
||||
class MailActivity(models.Model):
|
||||
""" An actual activity to perform. Activities are linked to
|
||||
@@ -575,6 +570,16 @@ class MailActivityMixin(models.AbstractModel):
|
||||
_name = 'mail.activity.mixin'
|
||||
_description = 'Activity Mixin'
|
||||
|
||||
def _default_activity_type(self):
|
||||
"""Define a default fallback activity type when requested xml id wasn't found.
|
||||
|
||||
Can be overriden to specify the default activity type of a model.
|
||||
It is only called in in activity_schedule() for now.
|
||||
"""
|
||||
return self.env.ref('mail.mail_activity_data_todo', raise_if_not_found=False) \
|
||||
or self.env['mail.activity.type'].search([('res_model', '=', self._name)], limit=1) \
|
||||
or self.env['mail.activity.type'].search([('res_model_id', '=', False)], limit=1)
|
||||
|
||||
activity_ids = fields.One2many(
|
||||
'mail.activity', 'res_id', 'Activities',
|
||||
auto_join=True,
|
||||
@@ -732,15 +737,16 @@ class MailActivityMixin(models.AbstractModel):
|
||||
if isinstance(date_deadline, datetime):
|
||||
_logger.warning("Scheduled deadline should be a date (got %s)", date_deadline)
|
||||
if act_type_xmlid:
|
||||
activity_type = self.sudo().env.ref(act_type_xmlid)
|
||||
activity_type = self.env.ref(act_type_xmlid, raise_if_not_found=False) or self._default_activity_type()
|
||||
else:
|
||||
activity_type = self.env['mail.activity.type'].sudo().browse(act_values['activity_type_id'])
|
||||
activity_type_id = act_values.get('activity_type_id', False)
|
||||
activity_type = activity_type_id and self.env['mail.activity.type'].sudo().browse(activity_type_id)
|
||||
|
||||
model_id = self.env['ir.model']._get(self._name).id
|
||||
activities = self.env['mail.activity']
|
||||
for record in self:
|
||||
create_vals = {
|
||||
'activity_type_id': activity_type.id,
|
||||
'activity_type_id': activity_type and activity_type.id,
|
||||
'summary': summary or activity_type.summary,
|
||||
'automated': True,
|
||||
'note': note or activity_type.default_description,
|
||||
@@ -792,7 +798,10 @@ class MailActivityMixin(models.AbstractModel):
|
||||
return False
|
||||
|
||||
Data = self.env['ir.model.data'].sudo()
|
||||
activity_types_ids = [Data.xmlid_to_res_id(xmlid) for xmlid in act_type_xmlids]
|
||||
activity_types_ids = [Data.xmlid_to_res_id(xmlid, raise_if_not_found=False) for xmlid in act_type_xmlids]
|
||||
activity_types_ids = [act_type_id for act_type_id in activity_types_ids if act_type_id]
|
||||
if not any(activity_types_ids):
|
||||
return False
|
||||
domain = [
|
||||
'&', '&', '&',
|
||||
('res_model', '=', self._name),
|
||||
@@ -819,7 +828,10 @@ class MailActivityMixin(models.AbstractModel):
|
||||
return False
|
||||
|
||||
Data = self.env['ir.model.data'].sudo()
|
||||
activity_types_ids = [Data.xmlid_to_res_id(xmlid) for xmlid in act_type_xmlids]
|
||||
activity_types_ids = [Data.xmlid_to_res_id(xmlid, raise_if_not_found=False) for xmlid in act_type_xmlids]
|
||||
activity_types_ids = [act_type_id for act_type_id in activity_types_ids if act_type_id]
|
||||
if not any(activity_types_ids):
|
||||
return False
|
||||
domain = [
|
||||
'&', '&', '&',
|
||||
('res_model', '=', self._name),
|
||||
@@ -841,7 +853,10 @@ class MailActivityMixin(models.AbstractModel):
|
||||
return False
|
||||
|
||||
Data = self.env['ir.model.data'].sudo()
|
||||
activity_types_ids = [Data.xmlid_to_res_id(xmlid) for xmlid in act_type_xmlids]
|
||||
activity_types_ids = [Data.xmlid_to_res_id(xmlid, raise_if_not_found=False) for xmlid in act_type_xmlids]
|
||||
activity_types_ids = [act_type_id for act_type_id in activity_types_ids if act_type_id]
|
||||
if not any(activity_types_ids):
|
||||
return False
|
||||
domain = [
|
||||
'&', '&', '&',
|
||||
('res_model', '=', self._name),
|
||||
|
||||
@@ -12,16 +12,9 @@ class NoteController(http.Controller):
|
||||
""" Route to create note and their activity directly from the systray """
|
||||
note = request.env['note.note'].create({'memo': note})
|
||||
if date_deadline:
|
||||
activity_values = {
|
||||
'note': note.memo,
|
||||
'date_deadline': date_deadline,
|
||||
'res_model_id': request.env.ref("note.model_note_note").id,
|
||||
'res_id': note.id,
|
||||
'note_id': note.id,
|
||||
}
|
||||
if not activity_type_id:
|
||||
activity_type_id = request.env['mail.activity.type'].sudo().search([('category', '=', 'reminder')], limit=1).id
|
||||
if activity_type_id:
|
||||
activity_values['activity_type_id'] = activity_type_id
|
||||
request.env['mail.activity'].create(activity_values)
|
||||
note.activity_schedule(
|
||||
activity_type_id=activity_type_id or request.env['mail.activity.type'].sudo().search([('category', '=', 'reminder')], limit=1).id,
|
||||
note=note.memo,
|
||||
date_deadline=date_deadline
|
||||
)
|
||||
return note.id
|
||||
|
||||
@@ -306,13 +306,9 @@ class PurchaseOrderLine(models.Model):
|
||||
if float_compare(line.product_qty, line.qty_invoiced, line.product_uom.rounding) == -1:
|
||||
# If the quantity is now below the invoiced quantity, create an activity on the vendor bill
|
||||
# inviting the user to create a refund.
|
||||
activity = self.env['mail.activity'].sudo().create({
|
||||
'activity_type_id': self.env.ref('mail.mail_activity_data_warning').id,
|
||||
'note': _('The quantities on your purchase order indicate less than billed. You should ask for a refund. '),
|
||||
'res_id': line.invoice_lines[0].invoice_id.id,
|
||||
'res_model_id': self.env.ref('account.model_account_move').id,
|
||||
})
|
||||
activity._onchange_activity_type_id()
|
||||
line.invoice_lines[0].move_id.activity_schedule(
|
||||
'mail.mail_activity_data_warning',
|
||||
note=_('The quantities on your purchase order indicate less than billed. You should ask for a refund.'))
|
||||
|
||||
# If the user increased quantity of existing line or created a new line
|
||||
pickings = line.order_id.picking_ids.filtered(lambda x: x.state not in ('done', 'cancel') and x.location_dest_id.usage in ('internal', 'transit'))
|
||||
|
||||
@@ -298,18 +298,11 @@ class StockRule(models.Model):
|
||||
existing_activity = self.env['mail.activity'].search([('res_id', '=', product_id.product_tmpl_id.id), ('res_model_id', '=', self.env.ref('product.model_product_template').id),
|
||||
('note', '=', note)])
|
||||
if not existing_activity:
|
||||
# If the user deleted warning activity type.
|
||||
try:
|
||||
activity_type_id = self.env.ref('mail.mail_activity_data_warning').id
|
||||
except:
|
||||
activity_type_id = False
|
||||
self.env['mail.activity'].create({
|
||||
'activity_type_id': activity_type_id,
|
||||
'note': note,
|
||||
'user_id': product_id.responsible_id.id or SUPERUSER_ID,
|
||||
'res_id': product_id.product_tmpl_id.id,
|
||||
'res_model_id': self.env.ref('product.model_product_template').id,
|
||||
})
|
||||
product_id.product_tmpl_id.activity_schedule(
|
||||
'mail.mail_activity_data_warning',
|
||||
note=note,
|
||||
user_id=product_id.responsible_id.id or SUPERUSER_ID,
|
||||
)
|
||||
|
||||
|
||||
class ProcurementGroup(models.Model):
|
||||
|
||||
Reference in New Issue
Block a user