[IMP] hr_timesheet: Move timesheet_cost into a separate module.

The field timesheet_cost defined on hr_employee in hr_timesheet would be
useful in other modules (eg planning and MRP) as well. Therefore with this
commit the field is moved into a separate module upon which
hr_timesheet will have a dependency. In addition this field is renamed
to employee_hourly_wage, which is a more accurate and neutral name for
this field, as it will not only be used in timesheets.

task-2929518

closes odoo/odoo#97988

Related: odoo/enterprise#30359
Related: odoo/upgrade#3853
Signed-off-by: Kevin Baptiste <kba@odoo.com>
This commit is contained in:
Rob Coekaerts
2022-09-12 11:38:11 +02:00
committed by Kevin Baptiste
parent 04aa56e9f0
commit a3eec47f35
21 changed files with 182 additions and 131 deletions
+3
View File
@@ -0,0 +1,3 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models
+22
View File
@@ -0,0 +1,22 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Employee Hourly Wage',
'version': '1.0',
'category': 'Services/Employee Hourly Cost',
'summary': 'Employee Hourly Wage',
'description': """
This module assigns an hourly wage to employees to be used by other modules.
============================================================================
""",
'depends': ['hr'],
'data': [
'security/hr_hourly_cost_security.xml',
'views/hr_employee_views.xml',
],
'demo': [
'data/hr_hourly_cost_demo.xml',
],
'license': 'LGPL-3',
}
@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Employee -->
<record id="hr.employee_admin" model="hr.employee">
<field name="hourly_cost">100</field>
</record>
<record id="hr.employee_vad" model="hr.employee">
<field name="hourly_cost">35</field>
</record>
<record id="hr.employee_jth" model="hr.employee">
<field name="hourly_cost">25</field>
</record>
<record id="hr.employee_niv" model="hr.employee">
<field name="hourly_cost">45</field>
</record>
<record id="hr.employee_jod" model="hr.employee">
<field name="hourly_cost">55</field>
</record>
<record id="hr.employee_jve" model="hr.employee">
<field name="hourly_cost">15</field>
</record>
<record id="hr.employee_fme" model="hr.employee">
<field name="hourly_cost">45</field>
</record>
<record id="hr.employee_chs" model="hr.employee">
<field name="hourly_cost">20</field>
</record>
<record id="hr.employee_ngh" model="hr.employee">
<field name="hourly_cost">40</field>
</record>
<record id="hr.employee_jgo" model="hr.employee">
<field name="hourly_cost">45</field>
</record>
<record id="hr.employee_lur" model="hr.employee">
<field name="hourly_cost">35</field>
</record>
<record id="hr.employee_jep" model="hr.employee">
<field name="hourly_cost">25</field>
</record>
<record id="hr.employee_jog" model="hr.employee">
<field name="hourly_cost">40</field>
</record>
<record id="hr.employee_fpi" model="hr.employee">
<field name="hourly_cost">50</field>
</record>
<record id="hr.employee_mit" model="hr.employee">
<field name="hourly_cost">15</field>
</record>
<record id="hr.employee_hne" model="hr.employee">
<field name="hourly_cost">10</field>
</record>
<record id="hr.employee_qdp" model="hr.employee">
<field name="hourly_cost">75</field>
<field name="parent_id" ref="hr.employee_admin"/>
</record>
<record id="hr.employee_stw" model="hr.employee">
<field name="hourly_cost">65</field>
</record>
<record id="hr.employee_al" model="hr.employee">
<field name="hourly_cost">50</field>
</record>
<record id="hr.employee_han" model="hr.employee">
<field name="hourly_cost">35</field>
</record>
</odoo>
+3
View File
@@ -0,0 +1,3 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import hr_employee
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
@@ -7,6 +6,6 @@ from odoo import fields, models
class HrEmployee(models.Model):
_inherit = 'hr.employee'
timesheet_cost = fields.Monetary('Cost', currency_field='currency_id',
hourly_cost = fields.Monetary('Hourly Cost', currency_field='currency_id',
groups="hr.group_hr_user", default=0.0)
currency_id = fields.Many2one('res.currency', related='company_id.currency_id', readonly=True)
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data noupdate="1">
<record id="group_hourly_cost_manager" model="res.groups">
<field name="name">Administrator</field>
<field name="category_id" ref="base.module_category_services_employee_hourly_cost"/>
<field name="implied_ids" eval="[(4, ref('hr.group_hr_user'))]"/>
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
</record>
</data>
</odoo>
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_employee_form" model="ir.ui.view">
<field name="name">view.employee.form.inherit.hr.employee.hourly.wage</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_form"/>
<field name="priority" eval="40"/>
<field name="arch" type="xml">
<group name="application_group" position="attributes">
<attribute name="string">Application Settings</attribute>
</group>
<group name="application_group" position="inside">
<label for="hourly_cost" groups="hr_hourly_cost.group_hourly_cost_manager"/>
<div name="hourly_cost" groups="hr_hourly_cost.group_hourly_cost_manager">
<field name="hourly_cost" class="oe_inline"/> per hour
<field name="currency_id" invisible="1"/>
</div>
</group>
</field>
</record>
</odoo>
+1 -1
View File
@@ -20,7 +20,7 @@ It is completely integrated with the cost accounting module. It allows you to se
up a management by affair.
""",
'website': 'https://www.odoo.com/app/timesheet',
'depends': ['hr', 'analytic', 'project', 'uom'],
'depends': ['hr', 'hr_hourly_cost', 'analytic', 'project', 'uom'],
'data': [
'security/hr_timesheet_security.xml',
'security/ir.model.access.csv',
@@ -6,88 +6,6 @@
<field name="groups_id" eval="[(4, ref('group_hr_timesheet_user'))]"/>
</record>
<!-- Employee -->
<record id="hr.employee_admin" model="hr.employee">
<field name="timesheet_cost">100</field>
</record>
<record id="hr.employee_vad" model="hr.employee">
<field name="timesheet_cost">35</field>
</record>
<record id="hr.employee_jth" model="hr.employee">
<field name="timesheet_cost">25</field>
</record>
<record id="hr.employee_niv" model="hr.employee">
<field name="timesheet_cost">45</field>
</record>
<record id="hr.employee_jod" model="hr.employee">
<field name="timesheet_cost">55</field>
</record>
<record id="hr.employee_jve" model="hr.employee">
<field name="timesheet_cost">15</field>
</record>
<record id="hr.employee_fme" model="hr.employee">
<field name="timesheet_cost">45</field>
</record>
<record id="hr.employee_chs" model="hr.employee">
<field name="timesheet_cost">20</field>
</record>
<record id="hr.employee_ngh" model="hr.employee">
<field name="timesheet_cost">40</field>
</record>
<record id="hr.employee_jgo" model="hr.employee">
<field name="timesheet_cost">45</field>
</record>
<record id="hr.employee_lur" model="hr.employee">
<field name="timesheet_cost">35</field>
</record>
<record id="hr.employee_jep" model="hr.employee">
<field name="timesheet_cost">25</field>
</record>
<record id="hr.employee_jog" model="hr.employee">
<field name="timesheet_cost">40</field>
</record>
<record id="hr.employee_fpi" model="hr.employee">
<field name="timesheet_cost">50</field>
</record>
<record id="hr.employee_mit" model="hr.employee">
<field name="timesheet_cost">15</field>
</record>
<record id="hr.employee_hne" model="hr.employee">
<field name="timesheet_cost">10</field>
</record>
<record id="hr.employee_qdp" model="hr.employee">
<field name="timesheet_cost">75</field>
<field name="parent_id" ref="hr.employee_admin"/>
</record>
<record id="hr.employee_stw" model="hr.employee">
<field name="timesheet_cost">65</field>
</record>
<record id="hr.employee_al" model="hr.employee">
<field name="timesheet_cost">50</field>
</record>
<record id="hr.employee_han" model="hr.employee">
<field name="timesheet_cost">35</field>
</record>
<!-- Projects -->
<record id="project.project_project_1" model="project.project">
<field name="allow_timesheets" eval="True"/>
-1
View File
@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import hr_employee
from . import hr_timesheet
from . import ir_http
from . import ir_ui_menu
+3 -3
View File
@@ -284,7 +284,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_timesheet_cost()
cost = timesheet._hourly_cost()
amount = -timesheet.unit_amount * cost
amount_converted = timesheet.employee_id.currency_id._convert(
amount, timesheet.account_id.currency_id or timesheet.currency_id, self.env.company, timesheet.date)
@@ -306,9 +306,9 @@ class AccountAnalyticLine(models.Model):
def _get_timesheet_time_day(self):
return self._convert_hours_to_days(self.unit_amount)
def _employee_timesheet_cost(self):
def _hourly_cost(self):
self.ensure_one()
return self.employee_id.timesheet_cost or 0.0
return self.employee_id.hourly_cost or 0.0
def _get_report_base_filename(self):
task_ids = self.task_id
@@ -22,7 +22,8 @@
<record id="group_timesheet_manager" model="res.groups">
<field name="name">Administrator</field>
<field name="category_id" ref="base.module_category_services_timesheets"/>
<field name="implied_ids" eval="[(4, ref('hr_timesheet.group_hr_timesheet_approver')), (4, ref('hr.group_hr_user'))]"/>
<field name="implied_ids" eval="[(4, ref('hr_timesheet.group_hr_timesheet_approver')), (4, ref('hr.group_hr_user')),
(4, ref('hr_hourly_cost.group_hourly_cost_manager'))]"/>
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
</record>
+2 -2
View File
@@ -253,8 +253,8 @@ class TestTimesheet(TestCommonTimesheet):
def test_recompute_amount_for_multiple_timesheets(self):
""" Check that amount is recomputed correctly when setting unit_amount for multiple timesheets at once. """
Timesheet = self.env['account.analytic.line']
self.empl_employee.timesheet_cost = 5.0
self.empl_employee2.timesheet_cost = 6.0
self.empl_employee.hourly_cost = 5.0
self.empl_employee2.hourly_cost = 6.0
# create a timesheet for each employee
timesheet_1 = Timesheet.with_user(self.user_employee).create({
'project_id': self.project_customer.id,
+1 -11
View File
@@ -30,19 +30,9 @@
<record id="hr_employee_view_form_inherit_timesheet" model="ir.ui.view">
<field name="name">hr.employee.form.timesheet</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_form"/>
<field name="inherit_id" ref="hr_hourly_cost.view_employee_form"/>
<field name="priority" eval="40"/>
<field name="arch" type="xml">
<group name="application_group" position="attributes">
<attribute name="string">Application Settings</attribute>
</group>
<group name="application_group" position="inside">
<label for="timesheet_cost" groups="hr_timesheet.group_timesheet_manager"/>
<div name="timesheet" groups="hr_timesheet.group_timesheet_manager">
<field name="timesheet_cost" class="oe_inline"/> per hour
<field name="currency_id" invisible="1"/>
</div>
</group>
<xpath expr="//div[@name='button_box']" position="inside">
<button class="oe_stat_button" type="action" name="%(timesheet_action_from_employee)d" icon="fa-calendar" groups="hr_timesheet.group_hr_timesheet_user">
<div class="o_stat_info">
@@ -36,7 +36,7 @@ class TimesheetAttendance(models.Model):
FROM (
SELECT
-hr_attendance.id AS id,
hr_employee.timesheet_cost AS emp_cost,
hr_employee.hourly_cost AS emp_cost,
resource_resource.user_id AS user_id,
hr_attendance.worked_hours AS attendance,
NULL AS timesheet,
@@ -48,7 +48,7 @@ class TimesheetAttendance(models.Model):
UNION ALL
SELECT
ts.id AS id,
hr_employee.timesheet_cost AS emp_cost,
hr_employee.hourly_cost AS emp_cost,
ts.user_id AS user_id,
NULL AS attendance,
ts.unit_amount AS timesheet,
+2 -2
View File
@@ -156,9 +156,9 @@ class AccountAnalyticLine(models.Model):
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):
def _hourly_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()
return super()._hourly_cost()
@@ -24,7 +24,7 @@ class ProjectProductEmployeeMap(models.Model):
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")
help="This cost overrides the employee's default employee hourly wage in employee's HR Settings")
display_cost = fields.Monetary(currency_field='cost_currency_id', compute="_compute_display_cost", inverse="_inverse_display_cost", string="Hourly Cost")
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)
@@ -55,12 +55,12 @@ 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')
@api.depends('employee_id.hourly_cost')
def _compute_cost(self):
self.env.remove_to_compute(self._fields['is_cost_changed'], 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
map_entry.cost = map_entry.employee_id.hourly_cost or 0.0
def _get_working_hours_per_calendar(self, is_uom_day=False):
resource_calendar_per_hours = {}
@@ -104,7 +104,7 @@ class ProjectProductEmployeeMap(models.Model):
@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
map_entry.is_cost_changed = map_entry.employee_id and map_entry.cost != map_entry.employee_id.hourly_cost
@api.model_create_multi
def create(self, vals_list):
+4 -4
View File
@@ -34,23 +34,23 @@ class TestCommonSaleTimesheet(TestSaleProjectCommon):
cls.employee_user = cls.env['hr.employee'].create({
'name': 'Employee User',
'timesheet_cost': 15,
'hourly_cost': 15,
})
cls.employee_manager = cls.env['hr.employee'].create({
'name': 'Employee Manager',
'timesheet_cost': 45,
'hourly_cost': 45,
})
cls.employee_company_B = cls.env['hr.employee'].create({
'name': 'Gregor Clegane',
'user_id': cls.user_employee_company_B.id,
'timesheet_cost': 15,
'hourly_cost': 15,
})
cls.manager_company_B = cls.env['hr.employee'].create({
'name': 'Cersei Lannister',
'user_id': cls.user_manager_company_B.id,
'timesheet_cost': 45,
'hourly_cost': 45,
})
# Account and project
+5 -5
View File
@@ -112,13 +112,13 @@ class TestProject(TestCommonSaleTimesheet):
'sale_line_id': self.project_global.sale_line_id.id,
})
self.assertFalse(employee_mapping.is_cost_changed)
self.assertEqual(employee_mapping.cost, self.employee_manager.timesheet_cost)
self.assertEqual(employee_mapping.cost, self.employee_manager.hourly_cost)
employee_mapping.cost = 5
self.assertTrue(employee_mapping.is_cost_changed)
self.assertEqual(employee_mapping.cost, 5)
self.employee_manager.timesheet_cost = 80
self.employee_manager.hourly_cost = 80
self.assertTrue(employee_mapping.is_cost_changed)
self.assertEqual(employee_mapping.cost, 5)
@@ -126,9 +126,9 @@ class TestProject(TestCommonSaleTimesheet):
self.assertTrue(employee_mapping.is_cost_changed)
self.assertEqual(employee_mapping.cost, 5)
employee_mapping.cost = self.employee_user.timesheet_cost
employee_mapping.cost = self.employee_user.hourly_cost
employee_mapping.employee_id = self.employee_company_B
self.assertEqual(employee_mapping.cost, self.employee_company_B.timesheet_cost)
self.assertEqual(employee_mapping.cost, self.employee_company_B.hourly_cost)
def test_analytic_account_balance(self):
"""
@@ -150,7 +150,7 @@ class TestProject(TestCommonSaleTimesheet):
})
unit_amount = 6
expected_analytic_account_balance = - self.employee_user.timesheet_cost * unit_amount
expected_analytic_account_balance = - self.employee_user.hourly_cost * unit_amount
self.project_global.write({
'sale_line_id': sale_line.id,
'sale_line_employee_ids': [
@@ -16,7 +16,7 @@ class TestProjectBilling(TestCommonSaleTimesheet):
# set up
cls.employee_tde = cls.env['hr.employee'].create({
'name': 'Employee TDE',
'timesheet_cost': 42,
'hourly_cost': 42,
})
cls.partner_2 = cls.env['res.partner'].create({
@@ -95,13 +95,13 @@ class TestSaleTimesheetProjectProfitability(TestCommonSaleTimesheet):
{
'id': 'billable_manual',
'sequence': sequence_per_invoice_type['billable_manual'],
'billed': (timesheet1.unit_amount + timesheet2.unit_amount) * -self.employee_user.timesheet_cost,
'billed': (timesheet1.unit_amount + timesheet2.unit_amount) * -self.employee_user.hourly_cost,
'to_bill': 0.0,
},
],
'total': {
'to_bill': 0.0,
'billed': (timesheet1.unit_amount + timesheet2.unit_amount) * -self.employee_user.timesheet_cost
'billed': (timesheet1.unit_amount + timesheet2.unit_amount) * -self.employee_user.hourly_cost
},
},
}
@@ -129,21 +129,21 @@ class TestSaleTimesheetProjectProfitability(TestCommonSaleTimesheet):
{
'id': 'billable_manual',
'sequence': sequence_per_invoice_type['billable_manual'],
'billed': (timesheet1.unit_amount + timesheet2.unit_amount) * -self.employee_user.timesheet_cost,
'billed': (timesheet1.unit_amount + timesheet2.unit_amount) * -self.employee_user.hourly_cost,
'to_bill': 0.0,
},
{
'id': 'non_billable',
'sequence': sequence_per_invoice_type['non_billable'],
'billed': timesheet3.unit_amount * -self.employee_manager.timesheet_cost,
'billed': timesheet3.unit_amount * -self.employee_manager.hourly_cost,
'to_bill': 0.0,
},
],
'total': {
'to_bill': 0.0,
'billed':
(timesheet1.unit_amount + timesheet2.unit_amount) * -self.employee_user.timesheet_cost
+ timesheet3.unit_amount * -self.employee_manager.timesheet_cost,
(timesheet1.unit_amount + timesheet2.unit_amount) * -self.employee_user.hourly_cost
+ timesheet3.unit_amount * -self.employee_manager.hourly_cost,
},
},
},
@@ -172,21 +172,21 @@ class TestSaleTimesheetProjectProfitability(TestCommonSaleTimesheet):
{
'id': 'billable_time',
'sequence': sequence_per_invoice_type['billable_time'],
'billed': (timesheet1.unit_amount + timesheet2.unit_amount) * -self.employee_user.timesheet_cost,
'billed': (timesheet1.unit_amount + timesheet2.unit_amount) * -self.employee_user.hourly_cost,
'to_bill': 0.0,
},
{
'id': 'non_billable',
'sequence': sequence_per_invoice_type['non_billable'],
'billed': timesheet3.unit_amount * -self.employee_manager.timesheet_cost,
'billed': timesheet3.unit_amount * -self.employee_manager.hourly_cost,
'to_bill': 0.0,
},
],
'total': {
'to_bill': 0.0,
'billed':
(timesheet1.unit_amount + timesheet2.unit_amount) * -self.employee_user.timesheet_cost
+ timesheet3.unit_amount * -self.employee_manager.timesheet_cost,
(timesheet1.unit_amount + timesheet2.unit_amount) * -self.employee_user.hourly_cost
+ timesheet3.unit_amount * -self.employee_manager.hourly_cost,
},
},
},