From dbbfce18a4d0f76360f7635c585bf01ba4df0275 Mon Sep 17 00:00:00 2001 From: Thibault Libioulle Date: Fri, 7 May 2021 11:31:52 +0200 Subject: [PATCH] [IMP] hr{,sale}_timesheet: add employee mapping cost Prior to this commit, the timesheet amount was computed based on the employee timesheet cost. The sale order line related to the employee was also required This commit allows the project owner to configure a employee mapping cost that will override the employee timesheet cost if defined. This commit removes the fact that the sale order line is no more required, thus the mapping will have no effect. PR : #70527 task-2458135 --- addons/hr_timesheet/models/hr_timesheet.py | 6 +++++- addons/sale_timesheet/models/account.py | 13 ++++++++++++- .../models/project_sale_line_employee_map.py | 17 ++++++++++++++++- .../sale_timesheet/views/project_task_views.xml | 2 ++ 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/addons/hr_timesheet/models/hr_timesheet.py b/addons/hr_timesheet/models/hr_timesheet.py index 9533ed792f3..b12a9e58308 100644 --- a/addons/hr_timesheet/models/hr_timesheet.py +++ b/addons/hr_timesheet/models/hr_timesheet.py @@ -237,7 +237,7 @@ class AccountAnalyticLine(models.Model): # (re)compute the amount (depending on unit_amount, employee_id for the cost, and account_id for currency) if any(field_name in values for field_name in ['unit_amount', 'employee_id', 'account_id']): for timesheet in sudo_self: - cost = timesheet.employee_id.timesheet_cost or 0.0 + cost = timesheet._employee_timesheet_cost() amount = -timesheet.unit_amount * cost amount_converted = timesheet.employee_id.currency_id._convert( amount, timesheet.account_id.currency_id, self.env.company, timesheet.date) @@ -258,3 +258,7 @@ class AccountAnalyticLine(models.Model): def _get_timesheet_time_day(self): return self._convert_hours_to_days(self.unit_amount) + + def _employee_timesheet_cost(self): + self.ensure_one() + return self.employee_id.timesheet_cost or 0.0 diff --git a/addons/sale_timesheet/models/account.py b/addons/sale_timesheet/models/account.py index 3704da2b4da..8b9e65168b4 100644 --- a/addons/sale_timesheet/models/account.py +++ b/addons/sale_timesheet/models/account.py @@ -98,7 +98,7 @@ class AccountAnalyticLine(models.Model): if not self.task_id: if self.project_id.pricing_type == 'employee_rate': - map_entry = self.env['project.sale.line.employee.map'].search([('project_id', '=', self.project_id.id), ('employee_id', '=', self.employee_id.id)]) + map_entry = self._get_employee_mapping_entry() if map_entry: return map_entry.sale_line_id if self.project_id.sale_line_id: @@ -149,3 +149,14 @@ class AccountAnalyticLine(models.Model): def _unlink_except_invoiced(self): if any(line.timesheet_invoice_id and line.timesheet_invoice_id.state == 'posted' for line in self): raise UserError(_('You cannot remove a timesheet that has already been invoiced.')) + + def _get_employee_mapping_entry(self): + self.ensure_one() + return self.env['project.sale.line.employee.map'].search([('project_id', '=', self.project_id.id), ('employee_id', '=', self.employee_id.id)]) + + def _employee_timesheet_cost(self): + if self.project_id.pricing_type == 'employee_rate': + mapping_entry = self._get_employee_mapping_entry() + if mapping_entry: + return mapping_entry.cost + return super()._employee_timesheet_cost() 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 7e12c500274..187e0bcbb60 100644 --- a/addons/sale_timesheet/models/project_sale_line_employee_map.py +++ b/addons/sale_timesheet/models/project_sale_line_employee_map.py @@ -10,7 +10,7 @@ class ProjectProductEmployeeMap(models.Model): project_id = fields.Many2one('project.project', "Project", required=True) employee_id = fields.Many2one('hr.employee', "Employee", required=True) - sale_line_id = fields.Many2one('sale.order.line', "Sale Order Item", compute="_compute_sale_line_id", store=True, readonly=False, required=True, + sale_line_id = fields.Many2one('sale.order.line', "Sale Order Item", compute="_compute_sale_line_id", store=True, readonly=False, domain="""[ ('is_service', '=', True), ('is_expense', '=', False), @@ -21,6 +21,10 @@ class ProjectProductEmployeeMap(models.Model): partner_id = fields.Many2one(related='project_id.partner_id') price_unit = fields.Float("Unit Price", compute='_compute_price_unit', store=True, readonly=True) currency_id = fields.Many2one('res.currency', string="Currency", compute='_compute_currency_id', store=True, readonly=False) + cost = fields.Monetary(currency_field='cost_currency_id', compute='_compute_cost', store=True, readonly=False, + help="This cost overrides the employee's default timesheet cost in employee's HR Settings") + cost_currency_id = fields.Many2one('res.currency', string="Cost Currency", related='employee_id.currency_id', readonly=True) + is_cost_changed = fields.Boolean('Is Cost Manually Changed', compute='_compute_is_cost_changed', store=True) _sql_constraints = [ ('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.'), @@ -48,6 +52,17 @@ class ProjectProductEmployeeMap(models.Model): for line in self: line.currency_id = line.sale_line_id.currency_id if line.sale_line_id else False + @api.depends('employee_id.timesheet_cost') + def _compute_cost(self): + for map_entry in self: + if not map_entry.is_cost_changed: + map_entry.cost = map_entry.employee_id.timesheet_cost or 0.0 + + @api.depends('cost') + def _compute_is_cost_changed(self): + for map_entry in self: + map_entry.is_cost_changed = map_entry.employee_id and map_entry.cost != map_entry.employee_id.timesheet_cost + @api.model def create(self, values): res = super(ProjectProductEmployeeMap, self).create(values) diff --git a/addons/sale_timesheet/views/project_task_views.xml b/addons/sale_timesheet/views/project_task_views.xml index 91c69de17f3..159a4514528 100644 --- a/addons/sale_timesheet/views/project_task_views.xml +++ b/addons/sale_timesheet/views/project_task_views.xml @@ -33,7 +33,9 @@ + +