diff --git a/addons/hr_timesheet/models/hr_timesheet.py b/addons/hr_timesheet/models/hr_timesheet.py index 42ce7a8d4c8..c1dbb52cdd9 100644 --- a/addons/hr_timesheet/models/hr_timesheet.py +++ b/addons/hr_timesheet/models/hr_timesheet.py @@ -52,7 +52,7 @@ class AccountAnalyticLine(models.Model): 'project.task', 'Task', index='btree_not_null', compute='_compute_task_id', store=True, readonly=False, domain="[('company_id', '=', company_id), ('project_id.allow_timesheets', '=', True), ('project_id', '=?', project_id)]") - ancestor_task_id = fields.Many2one('project.task', related='task_id.ancestor_id', store=True, index='btree_not_null') + parent_task_id = fields.Many2one('project.task', related='task_id.parent_id', store=True) project_id = fields.Many2one( 'project.project', 'Project', domain=_domain_project_id, index=True, compute='_compute_project_id', store=True, readonly=False) diff --git a/addons/hr_timesheet/report/timesheets_analysis_report.py b/addons/hr_timesheet/report/timesheets_analysis_report.py index 3487a0c6ed5..01f1c18c0a3 100644 --- a/addons/hr_timesheet/report/timesheets_analysis_report.py +++ b/addons/hr_timesheet/report/timesheets_analysis_report.py @@ -14,7 +14,7 @@ class TimesheetsAnalysisReport(models.Model): user_id = fields.Many2one("res.users", string="User", readonly=True) project_id = fields.Many2one("project.project", string="Project", readonly=True) task_id = fields.Many2one("project.task", string="Task", readonly=True) - ancestor_task_id = fields.Many2one("project.task", string="Ancestor Task", readonly=True) + parent_task_id = fields.Many2one("project.task", string="Parent Task", readonly=True) employee_id = fields.Many2one("hr.employee", string="Employee", readonly=True) manager_id = fields.Many2one("hr.employee", "Manager", readonly=True) company_id = fields.Many2one("res.company", string="Company", readonly=True) @@ -37,7 +37,7 @@ class TimesheetsAnalysisReport(models.Model): A.user_id AS user_id, A.project_id AS project_id, A.task_id AS task_id, - A.ancestor_task_id AS ancestor_task_id, + A.parent_task_id AS parent_task_id, A.employee_id AS employee_id, A.manager_id AS manager_id, A.company_id AS company_id, diff --git a/addons/hr_timesheet/views/hr_timesheet_views.xml b/addons/hr_timesheet/views/hr_timesheet_views.xml index cc0dd03b26b..327a6298da3 100644 --- a/addons/hr_timesheet/views/hr_timesheet_views.xml +++ b/addons/hr_timesheet/views/hr_timesheet_views.xml @@ -178,8 +178,8 @@ - + @@ -188,7 +188,7 @@ - + diff --git a/addons/project/models/project.py b/addons/project/models/project.py index 34ba75bacce..7220d0881d8 100644 --- a/addons/project/models/project.py +++ b/addons/project/models/project.py @@ -27,13 +27,11 @@ PROJECT_TASK_READABLE_FIELDS = { 'project_id', 'display_project_id', 'color', - 'partner_is_company', 'commercial_partner_id', 'allow_subtasks', 'subtask_count', 'child_text', 'is_closed', - 'email_from', 'create_date', 'write_date', 'company_id', @@ -62,7 +60,6 @@ PROJECT_TASK_WRITABLE_FIELDS = { 'name', 'description', 'partner_id', - 'partner_email', 'date_deadline', 'tag_ids', 'sequence', @@ -317,12 +314,6 @@ class Project(models.Model): help="If the active field is set to False, it will allow you to hide the project without removing it.") sequence = fields.Integer(default=10) partner_id = fields.Many2one('res.partner', string='Customer', auto_join=True, tracking=True, check_company=True) - partner_email = fields.Char( - compute='_compute_partner_email', inverse='_inverse_partner_email', - string='Email', readonly=False, store=True, copy=False) - partner_phone = fields.Char( - compute='_compute_partner_phone', inverse='_inverse_partner_phone', - string="Phone", readonly=False, store=True, copy=False) commercial_partner_id = fields.Many2one(related="partner_id.commercial_partner_id") company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env.company) currency_id = fields.Many2one('res.currency', related="company_id.currency_id", string="Currency", readonly=True) @@ -436,28 +427,6 @@ class Project(models.Model): ('project_date_greater', 'check(date >= date_start)', "The project's start date must be before its end date.") ] - @api.depends('partner_id.email') - def _compute_partner_email(self): - for project in self: - if project.partner_id.email != project.partner_email: - project.partner_email = project.partner_id.email - - def _inverse_partner_email(self): - for project in self: - if project.partner_id and project.partner_email != project.partner_id.email: - project.partner_id.email = project.partner_email - - @api.depends('partner_id.phone') - def _compute_partner_phone(self): - for project in self: - if project.partner_phone != project.partner_id.phone: - project.partner_phone = project.partner_id.phone - - def _inverse_partner_phone(self): - for project in self: - if project.partner_id and project.partner_phone != project.partner_id.phone: - project.partner_id.phone = project.partner_phone - @api.onchange('alias_enabled') def _onchange_alias_name(self): if not self.alias_enabled: @@ -1203,17 +1172,12 @@ class Task(models.Model): string='Customer', recursive=True, tracking=True, compute='_compute_partner_id', store=True, readonly=False, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]") - partner_is_company = fields.Boolean(related='partner_id.is_company', readonly=True) commercial_partner_id = fields.Many2one(related='partner_id.commercial_partner_id') - partner_email = fields.Char( - compute='_compute_partner_email', inverse='_inverse_partner_email', - string='Email', readonly=False, store=True, copy=False) partner_phone = fields.Char( compute='_compute_partner_phone', inverse='_inverse_partner_phone', string="Phone", readonly=False, store=True, copy=False) partner_city = fields.Char(related='partner_id.city', readonly=False) email_cc = fields.Char(help='Email addresses that were in the CC of the incoming emails from this task and that are not currently linked to an existing customer.') - manager_id = fields.Many2one('res.users', string='Project Manager', related='project_id.user_id', readonly=True) company_id = fields.Many2one( 'res.company', string='Company', compute='_compute_company_id', store=True, readonly=False, required=True, copy=True, default=_default_company_id) @@ -1229,13 +1193,10 @@ class Task(models.Model): legend_normal = fields.Char(related='stage_id.legend_normal', string='Kanban Ongoing Explanation', readonly=True) is_closed = fields.Boolean(related="stage_id.fold", string="Closing Stage", store=True, index=True, help="Folded in Kanban stages are closing stages.") parent_id = fields.Many2one('project.task', string='Parent Task', index=True) - ancestor_id = fields.Many2one('project.task', string='Ancestor Task', compute='_compute_ancestor_id', index='btree_not_null', recursive=True, store=True) child_ids = fields.One2many('project.task', 'parent_id', string="Sub-tasks", domain="[('recurring_task', '=', False)]") child_text = fields.Char(compute="_compute_child_text") allow_subtasks = fields.Boolean(string="Allow Sub-tasks", related="project_id.allow_subtasks", readonly=True) subtask_count = fields.Integer("Sub-task Count", compute='_compute_subtask_count') - email_from = fields.Char(string='Email From', help="These people will receive email.", index='trigram', - compute='_compute_email_from', recursive=True, store=True, readonly=False, copy=False) project_privacy_visibility = fields.Selection(related='project_id.privacy_visibility', string="Project Visibility") # Computed field about working time elapsed between record creation and assignation/closing. working_hours_open = fields.Float(compute='_compute_elapsed', string='Working Hours to Assign', digits=(16, 2), store=True, group_operator="avg") @@ -1366,8 +1327,6 @@ class Task(models.Model): help="Analytic account to which this task and its timesheets are linked.\n" "Track the costs and revenues of your task by setting its analytic account on your related documents (e.g. sales orders, invoices, purchase orders, vendor bills, expenses etc.).\n" "By default, the analytic account of the project is set. However, it can be changed on each task individually if necessary.") - is_analytic_account_id_changed = fields.Boolean('Is Analytic Account Manually Changed', compute='_compute_is_analytic_account_id_changed', store=True) - project_analytic_account_id = fields.Many2one('account.analytic.account', string='Project Analytic Account', related='project_id.analytic_account_id') _sql_constraints = [ ('recurring_task_has_no_parent', 'CHECK (NOT (recurring_task IS TRUE AND parent_id IS NOT NULL))', "A subtask cannot be recurrent.") @@ -1383,15 +1342,8 @@ class Task(models.Model): @api.depends('project_id.analytic_account_id') def _compute_analytic_account_id(self): - self.env.remove_to_compute(self._fields['is_analytic_account_id_changed'], self) for task in self: - if not task.is_analytic_account_id_changed: - task.analytic_account_id = task.project_id.analytic_account_id - - @api.depends('analytic_account_id') - def _compute_is_analytic_account_id_changed(self): - for task in self: - task.is_analytic_account_id_changed = task.project_id and task.analytic_account_id != task.project_id.analytic_account_id + task.analytic_account_id = task.project_id.analytic_account_id @api.depends('project_id', 'parent_id') def _compute_is_private(self): @@ -1413,11 +1365,6 @@ class Task(models.Model): def _compute_kanban_state(self): self.kanban_state = 'normal' - @api.depends('parent_id.ancestor_id') - def _compute_ancestor_id(self): - for task in self: - task.ancestor_id = task.parent_id.ancestor_id or task.parent_id - @api.depends_context('uid') @api.depends('user_ids') def _compute_personal_stage_id(self): @@ -1598,17 +1545,6 @@ class Task(models.Model): for task in self: task.is_blocked = any(not blocking_task.is_closed or blocking_task.is_blocked for blocking_task in task.depend_on_ids) - @api.depends('partner_id.email') - def _compute_partner_email(self): - for task in self: - if task.partner_id.email != task.partner_email: - task.partner_email = task.partner_id.email - - def _inverse_partner_email(self): - for task in self: - if task.partner_id and task.partner_email != task.partner_id.email: - task.partner_id.email = task.partner_email - @api.depends('partner_id.phone') def _compute_partner_phone(self): for task in self: @@ -2128,7 +2064,10 @@ class Task(models.Model): recurrence_update = vals.pop('recurrence_update', 'this') if recurrence_update == 'future': for task in self: - recurrent_task = task.ancestor_id if task.ancestor_id.recurring_task else task + ancestor_id = task + while ancestor_id.parent_id: + ancestor_id = ancestor_id.parent_id + recurrent_task = ancestor_id if ancestor_id.recurring_task else task task_for_recurrence[recurrent_task.recurrence_id] = task # The sudo is required for a portal user as the record update @@ -2200,11 +2139,6 @@ class Task(models.Model): project = task.display_project_id if task.parent_id and task.display_project_id else task.project_id task.partner_id = self._get_default_partner_id(project, task.parent_id) - @api.depends('partner_id.email', 'parent_id.email_from') - def _compute_email_from(self): - for task in self: - task.email_from = task.partner_id.email or ((task.partner_id or task.parent_id) and task.email_from) or task.parent_id.email_from - @api.depends('parent_id.project_id', 'display_project_id') def _compute_project_id(self): # Avoid recomputing kanban_state @@ -2465,6 +2399,13 @@ class Task(models.Model): create_context['default_user_ids'] = False if custom_values is None: custom_values = {} + # Auto create partner if not existant when the task is created from email + if not msg.get('author_id') and msg.get('email_from'): + msg['author_id'] = self.env['res.partner'].create({ + 'email': msg['email_from'], + 'name': msg['email_from'], + }).id + defaults = { 'name': msg.get('subject') or _("No Subject"), 'planned_hours': 0.0, @@ -2491,8 +2432,6 @@ class Task(models.Model): if task.partner_id: reason = _('Customer Email') if task.partner_id.email else _('Customer') task._message_add_suggested_recipient(recipients, partner=task.partner_id, reason=reason) - elif task.email_from: - task._message_add_suggested_recipient(recipients, email=task.email_from, reason=_('Customer Email')) return recipients def _notify_by_email_get_headers(self): @@ -2511,16 +2450,6 @@ class Task(models.Model): if image_attachments: self.displayed_image_id = image_attachments[0] - if self.email_from and not self.partner_id: - # we consider that posting a message with a specified recipient (not a follower, a specific one) - # on a document without customer means that it was created through the chatter using - # suggested recipients. This heuristic allows to avoid ugly hacks in JS. - new_partner = message.partner_ids.filtered(lambda partner: partner.email == self.email_from) - if new_partner: - self.search([ - ('partner_id', '=', False), - ('email_from', '=', new_partner.email), - ('is_closed', '=', False)]).write({'partner_id': new_partner.id}) # use the sanitized body of the email from the message thread to populate the task's description if not self.description and message.subtype_id == self._creation_subtype() and self.partner_id == message.author_id: self.description = message.body @@ -2700,7 +2629,7 @@ class Task(models.Model): # --------------------------------------------------- def _get_task_analytic_account_id(self): self.ensure_one() - return self.analytic_account_id or self.project_analytic_account_id + return self.analytic_account_id or self.project_id.analytic_account_id @api.model def get_unusual_days(self, date_from, date_to=None): diff --git a/addons/project/models/project_task_recurrence.py b/addons/project/models/project_task_recurrence.py index 73b1d2be70c..0d6ff554ced 100644 --- a/addons/project/models/project_task_recurrence.py +++ b/addons/project/models/project_task_recurrence.py @@ -146,7 +146,7 @@ class ProjectTaskRecurrence(models.Model): @api.model def _get_recurring_fields(self): return ['message_partner_ids', 'company_id', 'description', 'displayed_image_id', 'email_cc', - 'parent_id', 'partner_email', 'partner_id', 'partner_phone', 'planned_hours', + 'parent_id', 'partner_id', 'partner_phone', 'planned_hours', 'project_id', 'display_project_id', 'project_privacy_visibility', 'sequence', 'tag_ids', 'recurrence_id', 'name', 'recurring_task', 'analytic_account_id', 'user_ids'] diff --git a/addons/project/tests/test_project_flow.py b/addons/project/tests/test_project_flow.py index d1824f4d0a5..e8b1d171f24 100644 --- a/addons/project/tests/test_project_flow.py +++ b/addons/project/tests/test_project_flow.py @@ -124,6 +124,27 @@ class TestProjectFlow(TestProjectCommon, MailCommon): self.assertEqual(task.project_id, self.project_goats, 'project_task: incorrect project') self.assertEqual(task.stage_id.sequence, 1, "project_task: should have a stage with sequence=1") + @mute_logger('odoo.addons.mail.models.mail_thread') + def test_auto_create_partner(self): + email = 'unknown@test.com' + new_partner = self.env['res.partner'].search([('email', '=', email)]) + self.assertFalse(new_partner) + + task = self.format_and_process( + EMAIL_TPL, to='project+pigs@mydomain.com, valid.lelitre@agrolait.com', + cc='valid.other@gmail.com', + email_from=email, + subject='subject', + msg_id='<1198923581.41972151344608186760.JavaMail@agrolait.com>', + target_model='project.task' + ) + + self.assertEqual(len(task), 1) + new_partner = self.env['res.partner'].search([('email', '=', email)]) + self.assertTrue(new_partner) + self.assertEqual(task.partner_id, new_partner) + self.assertEqual(task.message_ids.author_id, new_partner) + def test_subtask_process(self): """ Check subtask mecanism and change it from project. diff --git a/addons/project/views/project_sharing_views.xml b/addons/project/views/project_sharing_views.xml index 29c2467c542..7df6487dc34 100644 --- a/addons/project/views/project_sharing_views.xml +++ b/addons/project/views/project_sharing_views.xml @@ -36,7 +36,6 @@ - @@ -69,14 +68,8 @@
- - - - - - + -
diff --git a/addons/project/views/project_views.xml b/addons/project/views/project_views.xml index 3af288906b3..2fe71676061 100644 --- a/addons/project/views/project_views.xml +++ b/addons/project/views/project_views.xml @@ -1322,12 +1322,10 @@ - - @@ -1403,7 +1401,6 @@ - @@ -1445,14 +1442,8 @@
- - - - - - + -
diff --git a/addons/sale_project/models/sale_order_line.py b/addons/sale_project/models/sale_order_line.py index 09b2f516ba0..ef6cf32dc2f 100644 --- a/addons/sale_project/models/sale_order_line.py +++ b/addons/sale_project/models/sale_order_line.py @@ -124,7 +124,6 @@ class SaleOrderLine(models.Model): project.tasks.write({ 'sale_line_id': self.id, 'partner_id': self.order_id.partner_id.id, - 'email_from': self.order_id.partner_id.email, }) # duplicating a project doesn't set the SO on sub-tasks project.tasks.filtered('parent_id').write({ @@ -162,7 +161,6 @@ class SaleOrderLine(models.Model): 'analytic_account_id': project.analytic_account_id.id, 'planned_hours': planned_hours, 'partner_id': self.order_id.partner_id.id, - 'email_from': self.order_id.partner_id.email, 'description': description, 'project_id': project.id, 'sale_line_id': self.id, diff --git a/addons/sale_project/tests/test_child_tasks.py b/addons/sale_project/tests/test_child_tasks.py index 723819676f6..0ea1bcf81d1 100644 --- a/addons/sale_project/tests/test_child_tasks.py +++ b/addons/sale_project/tests/test_child_tasks.py @@ -53,11 +53,6 @@ class TestNestedTaskUpdate(TransactionCase): child = self.env['project.task'].with_context(default_parent_id=parent.id).create({'name': 'child'}) self.assertEqual(child.partner_id, self.user.partner_id) - def test_creating_subtask_email_from_on_parent_goes_on_child(self): - parent = self.env['project.task'].create({'name': 'parent', 'email_from': 'a@c.be'}) - child = self.env['project.task'].create({'name': 'child', 'parent_id': parent.id}) - self.assertEqual(child.email_from, 'a@c.be') - def test_creating_subtask_sale_line_id_on_parent_goes_on_child_if_same_partner_in_values(self): parent = self.env['project.task'].create({'name': 'parent', 'partner_id': self.partner.id, 'sale_line_id': self.order_line.id, 'project_id': self.project.id}) child = self.env['project.task'].create({'name': 'child', 'partner_id': self.partner.id, 'parent_id': parent.id}) @@ -110,15 +105,6 @@ class TestNestedTaskUpdate(TransactionCase): parent.write({'partner_id': False}) self.assertNotEqual(child.partner_id, self.user.partner_id) - def test_write_email_from_on_parent_write_on_child(self): - parent = self.env['project.task'].create({'name': 'parent'}) - child = self.env['project.task'].create({'name': 'child', 'parent_id': parent.id}) - self.assertFalse(child.email_from) - parent.write({'email_from': 'a@c.be'}) - self.assertEqual(child.email_from, parent.email_from) - parent.write({'email_from': ''}) - self.assertEqual(child.email_from, 'a@c.be') - def test_write_sale_line_id_on_parent_write_on_child_if_same_partner(self): parent = self.env['project.task'].create({'name': 'parent', 'partner_id': self.partner.id, 'project_id': self.project.id}) child = self.env['project.task'].create({'name': 'child', 'parent_id': parent.id, 'partner_id': self.partner.id}) @@ -166,13 +152,6 @@ class TestNestedTaskUpdate(TransactionCase): child.write({'parent_id': parent.id}) self.assertEqual(child.partner_id, self.user.partner_id) - def test_linking_email_from_on_parent_write_on_child(self): - parent = self.env['project.task'].create({'name': 'parent', 'email_from': 'a@c.be'}) - child = self.env['project.task'].create({'name': 'child', 'email_from': False}) - self.assertFalse(child.email_from) - child.write({'parent_id': parent.id}) - self.assertEqual(child.email_from, 'a@c.be') - def test_linking_sale_line_id_on_parent_write_on_child_if_same_partner(self): parent = self.env['project.task'].create({'name': 'parent', 'partner_id': self.partner.id, 'sale_line_id': self.order_line.id, 'project_id': self.project.id}) child = self.env['project.task'].create({'name': 'child', 'partner_id': self.partner.id}) diff --git a/addons/sale_timesheet/tests/test_sale_service.py b/addons/sale_timesheet/tests/test_sale_service.py index c974b2bec9b..4a60a79376a 100644 --- a/addons/sale_timesheet/tests/test_sale_service.py +++ b/addons/sale_timesheet/tests/test_sale_service.py @@ -39,7 +39,6 @@ class TestSaleService(TestCommonSaleTimesheet): task = project.task_ids.filtered(lambda t: t.name == '%s - %s' % (self.sale_order.name, self.product_delivery_timesheet2.name)) self.assertTrue(task, 'Sale Service: task is not created, or it badly named') self.assertEqual(task.partner_id, self.sale_order.partner_id, 'Sale Service: customer should be the same on task and on SO') - self.assertEqual(task.email_from, self.sale_order.partner_id.email, 'Sale Service: Task Email should be the same as the SO customer Email') # log timesheet on task self.env['account.analytic.line'].create({ diff --git a/addons/sale_timesheet/wizard/project_create_sale_order.py b/addons/sale_timesheet/wizard/project_create_sale_order.py index 1cf01a6303e..c13668b22d6 100644 --- a/addons/sale_timesheet/wizard/project_create_sale_order.py +++ b/addons/sale_timesheet/wizard/project_create_sale_order.py @@ -159,7 +159,6 @@ class ProjectCreateSalesOrder(models.TransientModel): task_ids.write({ 'sale_line_id': sale_order_line.id, 'partner_id': sale_order.partner_id.id, - 'email_from': sale_order.partner_id.email, }) # assign SOL to timesheets @@ -234,7 +233,6 @@ class ProjectCreateSalesOrder(models.TransientModel): }) non_billable_tasks.write({ 'partner_id': sale_order.partner_id.id, - 'email_from': sale_order.partner_id.email, }) # assign SOL to timesheets