[IMP] sale_{project,timesheet}: convert SO field in project into related field
Before this commit, the user can select the SO in the project to filter the SOLs in its tasks and in SOL fields in the project form view. But since, the user can now select any SOLs of the customer defined in the project or task. Then this field is useless to defined in the project form view. This commit converts the sale_order_id field into a related field of the one in the sale_line_id field of the project. Moreover, the sale_order_id field is now invisible in the view, the user just need to select the default sales order item in the project and the SO of the project will be the SO of the default sales order item. Then, some unit tests have been updated to essentially remove the sale_order_id in the using of create and write methods of project.project model. Finally, the sale_timesheet_tour has been updated in this commit to remove the step when the user selects the default sales order of the project. task-2442683 closes #66181
This commit is contained in:
committed by
Yannick Tivisse
parent
3918060e6d
commit
12d30b3726
@@ -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):
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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.',
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<field name="display_create_order" invisible="1"/>
|
||||
<field name="pricing_type" invisible="1" widget="radio"/>
|
||||
<field name="timesheet_product_id" string="Default Service" invisible="1" context="{'default_type': 'service', 'default_service_policy': 'delivered_timesheet', 'default_service_type': 'timesheet'}"/>
|
||||
<field name="sale_order_id" attrs="{'readonly': [('sale_order_id', '!=', False)]}" force_save="1" options="{'no_create': True, 'no_edit': True, 'delete': False, 'no_open': True}"/>
|
||||
<field name="sale_order_id" invisible="1" options="{'no_create': True, 'no_edit': True, 'delete': False, 'no_open': True}"/>
|
||||
<field name="sale_line_id" string="Default Sales Order Item" options="{'no_create': True, 'no_edit': True, 'delete': False, 'no_open': True}"/>
|
||||
</group>
|
||||
</group>
|
||||
@@ -63,9 +63,6 @@
|
||||
<field name="inherit_id" ref="sale_timesheet.project_project_view_form"/>
|
||||
<field name="groups_id" eval="[(4, ref('sales_team.group_sale_salesman'))]"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="sale_order_id" position="attributes">
|
||||
<attribute name="options">{'no_create': True, 'no_edit': True, 'delete': False}</attribute>
|
||||
</field>
|
||||
<field name="sale_line_id" position="attributes">
|
||||
<attribute name="options">{'no_create': True, 'no_edit': True, 'delete': False}</attribute>
|
||||
</field>
|
||||
|
||||
Reference in New Issue
Block a user