-
Notifications
You must be signed in to change notification settings - Fork 25.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[ADD] sale_project: Move task generation from SO feature
Purpose ======= If the user doesn't have the timesheets app, it is not possible to generate a task from an SOL. However, the user may want to generate a task without necessarily timesheeting on it (e.g. a task in Field Service). Specification ============= Move the feature to a new module sale_project, sale_timesheet becomes dependent on it. Task-2123707 closes #40594 Closes: #40594 Related: odoo/enterprise#6851 Signed-off-by: Yannick Tivisse (yti) <[email protected]>
- Loading branch information
Showing
24 changed files
with
774 additions
and
498 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# -*- coding: utf-8 -*- | ||
# Part of Odoo. See LICENSE file for full copyright and licensing details. | ||
|
||
from . import models |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# -*- coding: utf-8 -*- | ||
# Part of Odoo. See LICENSE file for full copyright and licensing details. | ||
{ | ||
'name': "Sales - Project", | ||
'summary': "Task Generation from Sales Orders", | ||
'description': """ | ||
Allows to create task from your sales order | ||
============================================= | ||
This module allows to generate a project/task from sales orders. | ||
""", | ||
'category': 'Hidden', | ||
'depends': ['sale_management', 'project'], | ||
'data': [ | ||
'security/ir.model.access.csv', | ||
'security/sale_project_security.xml', | ||
'views/product_views.xml', | ||
'views/project_task_views.xml', | ||
'views/sale_order_views.xml', | ||
], | ||
'auto_install': True, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
from . import product | ||
from . import project | ||
from . import sale_order |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# -*- coding: utf-8 -*- | ||
# Part of Odoo. See LICENSE file for full copyright and licensing details. | ||
|
||
from odoo import api, fields, models, _ | ||
from odoo.exceptions import ValidationError | ||
|
||
|
||
class ProductTemplate(models.Model): | ||
_inherit = 'product.template' | ||
|
||
service_tracking = fields.Selection([ | ||
('no', 'Don\'t create task'), | ||
('task_global_project', 'Create a task in an existing project'), | ||
('task_in_project', 'Create a task in sales order\'s project'), | ||
('project_only', 'Create a new project but no task')], | ||
string="Service Tracking", default="no", | ||
help="On Sales order confirmation, this product can generate a project and/or task. \ | ||
From those, you can track the service you are selling.\n \ | ||
'In sale order\'s project': Will use the sale order\'s configured project if defined or fallback to \ | ||
creating a new project based on the selected template.") | ||
project_id = fields.Many2one( | ||
'project.project', 'Project', company_dependent=True, | ||
help='Select a non billable project on which tasks can be created. This setting must be set for each company.') | ||
project_template_id = fields.Many2one( | ||
'project.project', 'Project Template', company_dependent=True, copy=True, | ||
help='Select a non billable project to be the skeleton of the new created project when selling the current product. Its stages and tasks will be duplicated.') | ||
|
||
@api.constrains('project_id', 'project_template_id') | ||
def _check_project_and_template(self): | ||
""" NOTE 'service_tracking' should be in decorator parameters but since ORM check constraints twice (one after setting | ||
stored fields, one after setting non stored field), the error is raised when company-dependent fields are not set. | ||
So, this constraints does cover all cases and inconsistent can still be recorded until the ORM change its behavior. | ||
""" | ||
for product in self: | ||
if product.service_tracking == 'no' and (product.project_id or product.project_template_id): | ||
raise ValidationError(_('The product %s should not have a project nor a project template since it will not generate project.') % (product.name,)) | ||
elif product.service_tracking == 'task_global_project' and product.project_template_id: | ||
raise ValidationError(_('The product %s should not have a project template since it will generate a task in a global project.') % (product.name,)) | ||
elif product.service_tracking in ['task_in_project', 'project_only'] and product.project_id: | ||
raise ValidationError(_('The product %s should not have a global project since it will generate a project.') % (product.name,)) | ||
|
||
@api.onchange('service_tracking') | ||
def _onchange_service_tracking(self): | ||
if self.service_tracking == 'no': | ||
self.project_id = False | ||
self.project_template_id = False | ||
elif self.service_tracking == 'task_global_project': | ||
self.project_template_id = False | ||
elif self.service_tracking in ['task_in_project', 'project_only']: | ||
self.project_id = False | ||
|
||
|
||
class ProductProduct(models.Model): | ||
_inherit = 'product.product' | ||
|
||
@api.onchange('service_tracking') | ||
def _onchange_service_tracking(self): | ||
if self.service_tracking == 'no': | ||
self.project_id = False | ||
self.project_template_id = False | ||
elif self.service_tracking == 'task_global_project': | ||
self.project_template_id = False | ||
elif self.service_tracking in ['task_in_project', 'project_only']: | ||
self.project_id = False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
# -*- coding: utf-8 -*- | ||
# Part of Odoo. See LICENSE file for full copyright and licensing details. | ||
|
||
from odoo import api, fields, models, _ | ||
from odoo.exceptions import ValidationError | ||
|
||
|
||
class Project(models.Model): | ||
_inherit = 'project.project' | ||
|
||
sale_line_id = fields.Many2one( | ||
'sale.order.line', 'Sales Order Item', copy=False, | ||
domain="[('is_expense', '=', False), ('order_id', '=', sale_order_id), ('state', 'in', ['sale', 'done']), '|', ('company_id', '=', False), ('company_id', '=', company_id)]", | ||
help="Sales order item to which the project is linked. If an employee timesheets on a task that does not have a " | ||
"sale order item defines, and if this employee is not in the 'Employee/Sales Order Item Mapping' of the project, " | ||
"the timesheet entry will be linked to the sales order item defined on the project.") | ||
sale_order_id = fields.Many2one('sale.order', 'Sales Order', domain="[('partner_id', '=', partner_id)]", readonly=True, copy=False, help="Sales order to which the project is linked.") | ||
|
||
_sql_constraints = [ | ||
('sale_order_required_if_sale_line', "CHECK((sale_line_id IS NOT NULL AND sale_order_id IS NOT NULL) OR (sale_line_id IS NULL))", 'The Project should be linked to a Sale Order to select an Sale Order Items.'), | ||
] | ||
|
||
@api.model | ||
def _map_tasks_default_valeus(self, task, project): | ||
defaults = super()._map_tasks_default_valeus(task, project) | ||
defaults['sale_line_id'] = False | ||
return defaults | ||
|
||
|
||
class ProjectTask(models.Model): | ||
_inherit = "project.task" | ||
|
||
sale_order_id = fields.Many2one('sale.order', 'Sales Order', help="Sales order to which the task is linked.") | ||
sale_line_id = fields.Many2one( | ||
'sale.order.line', 'Sales Order Item', domain="[('is_service', '=', True), ('order_partner_id', 'child_of', commercial_partner_id), ('is_expense', '=', False), ('state', 'in', ['sale', 'done']), ('order_id', '=?', project_sale_order_id)]", | ||
compute='_compute_sale_line', store=True, readonly=False, copy=False, | ||
help="Sales order item to which the task is linked. If an employee timesheets on a this task, " | ||
"and if this employee is not in the 'Employee/Sales Order Item Mapping' of the project, the " | ||
"timesheet entry will be linked to this sales order item.") | ||
project_sale_order_id = fields.Many2one('sale.order', string="project's sale order", related='project_id.sale_order_id') | ||
|
||
@api.depends('project_id.sale_line_id.order_partner_id') | ||
def _compute_partner_id(self): | ||
for task in self: | ||
if not task.partner_id: | ||
task.partner_id = task.project_id.sale_line_id.order_partner_id | ||
super()._compute_partner_id() | ||
|
||
@api.depends('partner_id.commercial_partner_id', 'sale_line_id.order_partner_id.commercial_partner_id', 'parent_id.sale_line_id', 'project_id.sale_line_id') | ||
def _compute_sale_line(self): | ||
for task in self: | ||
if not task.sale_line_id: | ||
task.sale_line_id = task.parent_id.sale_line_id or task.project_id.sale_line_id | ||
# check sale_line_id and customer are coherent | ||
if task.sale_line_id.order_partner_id.commercial_partner_id != task.partner_id.commercial_partner_id: | ||
task.sale_line_id = False | ||
|
||
@api.constrains('sale_line_id') | ||
def _check_sale_line_type(self): | ||
for task in self.sudo(): | ||
if task.sale_line_id: | ||
if not task.sale_line_id.is_service or task.sale_line_id.is_expense: | ||
raise ValidationError(_('You cannot link the order item %s - %s to this task because it is a re-invoiced expense.' % (task.sale_line_id.order_id.id, task.sale_line_id.product_id.name))) | ||
|
||
def unlink(self): | ||
if any(task.sale_line_id for task in self): | ||
raise ValidationError(_('You have to unlink the task from the sale order item in order to delete it.')) | ||
return super().unlink() | ||
|
||
# --------------------------------------------------- | ||
# Actions | ||
# --------------------------------------------------- | ||
|
||
def action_view_so(self): | ||
self.ensure_one() | ||
return { | ||
"type": "ir.actions.act_window", | ||
"res_model": "sale.order", | ||
"views": [[False, "form"]], | ||
"res_id": self.sale_order_id.id, | ||
"context": {"create": False, "show_sale": True}, | ||
} | ||
|
||
def rating_get_partner_id(self): | ||
partner = self.partner_id or self.sale_line_id.order_id.partner_id | ||
if partner: | ||
return partner | ||
return super().rating_get_partner_id() |
Oops, something went wrong.