diff --git a/addons/sale_project/models/project.py b/addons/sale_project/models/project.py
index e4bd59b0d26..449db8d2c90 100644
--- a/addons/sale_project/models/project.py
+++ b/addons/sale_project/models/project.py
@@ -16,29 +16,23 @@ class Project(models.Model):
domain="[('is_service', '=', True), ('is_expense', '=', False), ('state', 'in', ['sale', 'done']), ('order_partner_id', '=', partner_id), '|', ('company_id', '=', False), ('company_id', '=', company_id)]",
help="Sales order item to which the project is linked. Link the timesheet entry to the sales order item defined on the project. "
"Only applies on tasks without sale order item defined, and if the employee is not in the 'Employee/Sales Order Item Mapping' of the project.")
- sale_order_id = fields.Many2one('sale.order', 'Sales Order',
- compute="_compute_sale_order_id", store=True, readonly=False,
- domain="[('order_line.product_id.type', '=', 'service'), ('partner_id', '=', partner_id), ('state', 'in', ['sale', 'done'])]",
- copy=False, help="Sales order to which the project is linked.")
+ sale_order_id = fields.Many2one(string='Sales Order', related='sale_line_id.order_id', help="Sales order to which the project is linked.")
project_overview = fields.Boolean('Show Project Overview', compute='_compute_project_overview')
- _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 a sale order item.'),
- ]
-
@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
- @api.depends('sale_order_id')
- def _compute_sale_line_id(self):
- self.filtered(lambda p: p.sale_line_id and (not p.sale_order_id or p.sale_order_id != p.sale_line_id.order_id)).update({'sale_line_id': False})
-
@api.depends('partner_id')
- def _compute_sale_order_id(self):
- self.filtered(lambda p: p.sale_order_id and (not p.partner_id or p.sale_order_id.partner_id != p.partner_id)).update({'sale_order_id': False})
+ def _compute_sale_line_id(self):
+ self.filtered(
+ lambda p:
+ p.sale_line_id and (
+ not p.partner_id or p.sale_line_id.order_partner_id.commercial_partner_id != p.partner_id.commercial_partner_id
+ )
+ ).update({'sale_line_id': False})
@api.depends('analytic_account_id')
def _compute_project_overview(self):
diff --git a/addons/sale_project/models/sale_order.py b/addons/sale_project/models/sale_order.py
index cca78bd03f8..6652df26f1d 100644
--- a/addons/sale_project/models/sale_order.py
+++ b/addons/sale_project/models/sale_order.py
@@ -211,7 +211,6 @@ class SaleOrderLine(models.Model):
'analytic_account_id': account.id,
'partner_id': self.order_id.partner_id.id,
'sale_line_id': self.id,
- 'sale_order_id': self.order_id.id,
'active': True,
'company_id': self.company_id.id,
}
diff --git a/addons/sale_timesheet/models/project.py b/addons/sale_timesheet/models/project.py
index 0e8ac1b730b..90d5ab01f15 100644
--- a/addons/sale_timesheet/models/project.py
+++ b/addons/sale_timesheet/models/project.py
@@ -50,13 +50,13 @@ class Project(models.Model):
default=_default_timesheet_product_id)
warning_employee_rate = fields.Boolean(compute='_compute_warning_employee_rate')
- @api.depends('sale_order_id', 'sale_line_employee_ids', 'allow_billable')
+ @api.depends('sale_line_id', 'sale_line_employee_ids', 'allow_billable')
def _compute_pricing_type(self):
billable_projects = self.filtered('allow_billable')
for project in billable_projects:
if project.sale_line_employee_ids:
project.pricing_type = 'employee_rate'
- elif project.sale_order_id:
+ elif project.sale_line_id:
project.pricing_type = 'fixed_rate'
else:
project.pricing_type = 'task_rate'
@@ -67,14 +67,14 @@ class Project(models.Model):
This method returns a domain based on the operator and the value given in parameter:
- operator = '=':
- - value = 'task_rate': [('sale_line_employee_ids', '=', False), ('sale_order_id', '=', False), ('allow_billable', '=', True)]
- - value = 'fixed_rate': [('sale_line_employee_ids', '=', False), ('sale_order_id', '!=', False), ('allow_billable', '=', True)]
- - value = 'employee_rate': [('sale_line_employee_ids', '!=', False), ('sale_order_id', '!=', False), ('allow_billable', '=', True)]
+ - value = 'task_rate': [('sale_line_employee_ids', '=', False), ('sale_line_id', '=', False), ('allow_billable', '=', True)]
+ - value = 'fixed_rate': [('sale_line_employee_ids', '=', False), ('sale_line_id', '!=', False), ('allow_billable', '=', True)]
+ - value = 'employee_rate': [('sale_line_employee_ids', '!=', False), ('allow_billable', '=', True)]
- value is False: [('allow_billable', '=', False)]
- operator = '!=':
- - value = 'task_rate': ['|', ('sale_line_employee_ids', '!=', False), ('sale_order_id', '!=', False), ('allow_billable', '=', True)]
- - value = 'fixed_rate': ['|', ('sale_line_employee_ids', '!=', False), ('sale_order_id', '=', False), ('allow_billable', '=', True)]
- - value = 'employee_rate': [('sale_line_employee_ids', '=', False), ('sale_order_id', '=', False), ('allow_billable', '=', True)]
+ - value = 'task_rate': ['|', ('sale_line_employee_ids', '!=', False), ('sale_line_id', '!=', False), ('allow_billable', '=', True)]
+ - value = 'fixed_rate': ['|', ('sale_line_employee_ids', '!=', False), ('sale_line_id', '=', False), ('allow_billable', '=', True)]
+ - value = 'employee_rate': [('sale_line_employee_ids', '=', False), ('allow_billable', '=', True)]
- value is False: [('allow_billable', '!=', False)]
:param operator: the supported operator is either '=' or '!='.
@@ -89,14 +89,14 @@ class Project(models.Model):
if value is False:
return [('allow_billable', operator, value)]
- so_cond = ('sale_order_id', '!=', False)
+ sol_cond = ('sale_line_id', '!=', False)
mapping_cond = ('sale_line_employee_ids', '!=', False)
if value == 'task_rate':
- domain = [expression.NOT_OPERATOR, so_cond, expression.NOT_OPERATOR, mapping_cond]
+ domain = [expression.NOT_OPERATOR, sol_cond, expression.NOT_OPERATOR, mapping_cond]
elif value == 'fixed_rate':
- domain = [so_cond, expression.NOT_OPERATOR, mapping_cond]
+ domain = [sol_cond, expression.NOT_OPERATOR, mapping_cond]
else: # value == 'employee_rate'
- domain = [so_cond, mapping_cond]
+ domain = [sol_cond, mapping_cond]
domain = expression.normalize_domain(domain)
if operator != '=':
@@ -105,7 +105,7 @@ class Project(models.Model):
domain = expression.AND([domain, [('allow_billable', '=', True)]])
return domain
- @api.depends('sale_order_id', 'partner_id', 'pricing_type')
+ @api.depends('partner_id', 'pricing_type')
def _compute_display_create_order(self):
for project in self:
project.display_create_order = project.partner_id and project.pricing_type == 'task_rate'
diff --git a/addons/sale_timesheet/models/project_sale_line_employee_map.py b/addons/sale_timesheet/models/project_sale_line_employee_map.py
index 7fe52e9136d..c43c48305b0 100644
--- a/addons/sale_timesheet/models/project_sale_line_employee_map.py
+++ b/addons/sale_timesheet/models/project_sale_line_employee_map.py
@@ -26,9 +26,9 @@ class ProjectProductEmployeeMap(models.Model):
('uniqueness_employee', 'UNIQUE(project_id,employee_id)', 'An employee cannot be selected more than once in the mapping. Please remove duplicate(s) and try again.'),
]
- @api.depends('project_id.sale_order_id')
+ @api.depends('project_id.partner_id')
def _compute_sale_line_id(self):
- self.filtered(lambda map_entry: not map_entry.project_id.sale_order_id and map_entry.sale_line_id).update({'sale_line_id': None})
+ self.filtered(lambda map_entry: map_entry.sale_line_id.order_partner_id != map_entry.project_id.partner_id).update({'sale_line_id': False})
@api.depends('sale_line_id', 'sale_line_id.price_unit')
def _compute_price_unit(self):
diff --git a/addons/sale_timesheet/static/tests/tours/sale_timesheet_tour.js b/addons/sale_timesheet/static/tests/tours/sale_timesheet_tour.js
index bcace6da789..7ec13918753 100644
--- a/addons/sale_timesheet/static/tests/tours/sale_timesheet_tour.js
+++ b/addons/sale_timesheet/static/tests/tours/sale_timesheet_tour.js
@@ -185,7 +185,7 @@ tour.register('sale_timesheet_tour', {
trigger: 'div.o_notebook_headers',
content: 'Click on Invoicing tab to configure the invoicing of this project.',
run: function (actions) {
- const notebookId = $('div[name="sale_order_id"]').closest("div.tab-pane").attr('id');
+ const notebookId = $('div[name="sale_line_id"]').closest("div.tab-pane").attr('id');
actions.click(this.$anchor.find(`a[data-toggle="tab"][href="#${notebookId}"]`));
},
}, {
@@ -198,16 +198,6 @@ tour.register('sale_timesheet_tour', {
trigger: 'ul.o_partner_autocomplete_dropdown > li:first-child > a',
content: 'Select the customer in the autocomplete dropdown',
run: 'click',
-}, {
- trigger: 'div[name="sale_order_id"]',
- content: 'Select the first Sales Order for this customer to select a SOL.',
- run: function (actions) {
- actions.text('S', this.$anchor.find('input'));
- },
-}, {
- trigger: 'ul.ui-autocomplete > li:first-child > a',
- content: 'Select the Sales Order in autocomplete dropdown.',
- run: 'click',
}, {
trigger: 'div[name="sale_line_id"]',
content: 'Select a Sales Order Item as Default Sales Order Item for each task in this project.',
diff --git a/addons/sale_timesheet/tests/test_project_billing.py b/addons/sale_timesheet/tests/test_project_billing.py
index 02187ca1243..9ddaa94399e 100644
--- a/addons/sale_timesheet/tests/test_project_billing.py
+++ b/addons/sale_timesheet/tests/test_project_billing.py
@@ -77,12 +77,10 @@ class TestProjectBilling(TestCommonSaleTimesheet):
cls.project_project_rate = cls.project_task_rate.copy({
'name': 'Project with pricing_type="project_rate"',
- 'sale_order_id': cls.sale_order_1.id,
'sale_line_id': cls.so1_line_order_no_task.id,
})
cls.project_employee_rate = cls.project_task_rate.copy({
'name': 'Project with pricing_type="employee_rate"',
- 'sale_order_id': cls.sale_order_1.id,
'partner_id': cls.sale_order_1.partner_id.id,
})
cls.project_employee_rate_manager = cls.env['project.sale.line.employee.map'].create({
@@ -416,16 +414,16 @@ class TestProjectBilling(TestCommonSaleTimesheet):
self.project_project_rate.write({
'partner_id': self.partner_2.id,
})
- self.assertFalse(self.project_project_rate.sale_order_id, "The SO in the project should be False because the previous SO does not for the actual customer of the project.")
- self.assertFalse(self.project_project_rate.sale_line_id, "The SOL in the project should be False because the SO is removed too.")
+ self.assertFalse(self.project_project_rate.sale_order_id, "The SO in the project should be False because the previous SO customer does not match the actual customer of the project.")
+ self.assertFalse(self.project_project_rate.sale_line_id, "The SOL in the project should be False because the previous SOL customer does not match the actual customer of the project.")
self.assertEqual(self.project_project_rate.pricing_type, 'task_rate', 'Since there is no SO and SOL in the project, the pricing type should be task rate.')
# 2) Take project with pricing_type="employee_rate", change the existing customer to another and check if the SO and SOL are equal to False.
self.project_employee_rate.write({
'partner_id': self.partner_2.id,
})
- self.assertFalse(self.project_employee_rate.sale_order_id, "The SO in the project should be False because the previous SO does not for the actual customer of the project.")
- self.assertFalse(self.project_employee_rate.sale_line_id, "The SOL in the project should be False because the SO is removed too.")
+ self.assertFalse(self.project_employee_rate.sale_order_id, "The SO in the project should be False because the previous SO customer does not match the actual customer of the project.")
+ self.assertFalse(self.project_employee_rate.sale_line_id, "The SOL in the project should be False because the previous SOL customer does not match the actual customer of the project.")
# 2.1) Check if the SOL in mapping is also equal to False
self.assertFalse(self.project_employee_rate_manager.sale_line_id, "The SOL in the mapping should be False because the actual customer in the project has not this SOL.")
diff --git a/addons/sale_timesheet/tests/test_so_line_determined_in_timesheet.py b/addons/sale_timesheet/tests/test_so_line_determined_in_timesheet.py
index 56108cd888b..21562969796 100644
--- a/addons/sale_timesheet/tests/test_so_line_determined_in_timesheet.py
+++ b/addons/sale_timesheet/tests/test_so_line_determined_in_timesheet.py
@@ -68,7 +68,6 @@ class TestSoLineDeterminedInTimesheet(TestCommonSaleTimesheet):
# 1) Define a SO and SOL in the project
self.project_project_rate = self.project_task_rate.copy({
'name': 'Project with pricing_type="project_rate"',
- 'sale_order_id': self.so.id,
'sale_line_id': self.so.order_line[0].id,
})
@@ -110,7 +109,6 @@ class TestSoLineDeterminedInTimesheet(TestCommonSaleTimesheet):
# 1) Define a SO, SOL and mapping for an employee in the project,
self.project_employee_rate = self.project_task_rate.copy({
'name': 'Project with pricing_type="employee_rate"',
- 'sale_order_id': self.so.id,
'sale_line_id': self.so.order_line[0].id,
'sale_line_employee_ids': [(0, 0, {
'employee_id': self.employee_user.id,
diff --git a/addons/sale_timesheet/views/project_task_views.xml b/addons/sale_timesheet/views/project_task_views.xml
index 49182a2f643..57ec55aafd4 100644
--- a/addons/sale_timesheet/views/project_task_views.xml
+++ b/addons/sale_timesheet/views/project_task_views.xml
@@ -24,7 +24,7 @@
-
+
@@ -63,9 +63,6 @@
-
- {'no_create': True, 'no_edit': True, 'delete': False}
-
{'no_create': True, 'no_edit': True, 'delete': False}