[IMP] hr_timesheet: ability to track cost without selling time
The cost of an employee should be in hr_timesheet for a long time, to allow user to track time on project/task: hours spent and their cost. This commit simply moves fields and related views. It also moves the 'postprocess' method from sale_timesheet to hr_timesheet since this method is responsible to set the amount (cost) on AAL. User can now track their time and cost, without having sale_timesheet installed.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import hr
|
||||
from . import hr_timesheet
|
||||
from . import res_config_settings
|
||||
from . import project
|
||||
|
||||
@@ -7,6 +7,5 @@ from odoo import fields, models
|
||||
class HrEmployee(models.Model):
|
||||
_inherit = 'hr.employee'
|
||||
|
||||
# FIXME: this field should be in module hr_timesheet, not sale_timesheet
|
||||
timesheet_cost = fields.Monetary('Timesheet Cost', currency_field='currency_id', default=0.0)
|
||||
currency_id = fields.Many2one('res.currency', related='company_id.currency_id', readonly=True)
|
||||
@@ -40,14 +40,20 @@ class AccountAnalyticLine(models.Model):
|
||||
line.department_id = line.employee_id.department_id
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
vals = self._timesheet_preprocess(vals)
|
||||
return super(AccountAnalyticLine, self).create(vals)
|
||||
def create(self, values):
|
||||
values = self._timesheet_preprocess(values)
|
||||
result = super(AccountAnalyticLine, self).create(values)
|
||||
if result.project_id: # applied only for timesheet
|
||||
result._timesheet_postprocess(values)
|
||||
return result
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
vals = self._timesheet_preprocess(vals)
|
||||
return super(AccountAnalyticLine, self).write(vals)
|
||||
def write(self, values):
|
||||
values = self._timesheet_preprocess(values)
|
||||
result = super(AccountAnalyticLine, self).write(values)
|
||||
# applied only for timesheet
|
||||
self.filtered(lambda t: t.project_id)._timesheet_postprocess(values)
|
||||
return result
|
||||
|
||||
def _timesheet_preprocess(self, vals):
|
||||
""" Deduce other field values from the one given.
|
||||
@@ -79,3 +85,34 @@ class AccountAnalyticLine(models.Model):
|
||||
if partner_id:
|
||||
vals['partner_id'] = partner_id
|
||||
return vals
|
||||
|
||||
@api.multi
|
||||
def _timesheet_postprocess(self, values):
|
||||
""" Hook to update record one by one according to the values of a `write` or a `create`. """
|
||||
sudo_self = self.sudo() # this creates only one env for all operation that required sudo() in `_timesheet_postprocess_values`override
|
||||
values_to_write = self._timesheet_postprocess_values(values)
|
||||
for timesheet in sudo_self:
|
||||
if values_to_write[timesheet.id]:
|
||||
timesheet.write(values_to_write[timesheet.id])
|
||||
return values
|
||||
|
||||
@api.multi
|
||||
def _timesheet_postprocess_values(self, values):
|
||||
""" Get the addionnal values to write on record
|
||||
:param dict values: values for the model's fields, as a dictionary::
|
||||
{'field_name': field_value, ...}
|
||||
:return: a dictionary mapping each record id to its corresponding
|
||||
dictionnary values to write (may be empty).
|
||||
"""
|
||||
result = dict.fromkeys(self.ids, dict())
|
||||
sudo_self = self.sudo() # this creates only one env for all operation that required sudo()
|
||||
# (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
|
||||
amount = -timesheet.unit_amount * cost
|
||||
amount_converted = timesheet.employee_id.currency_id.compute(amount, timesheet.account_id.currency_id)
|
||||
result[timesheet.id].update({
|
||||
'amount': amount_converted,
|
||||
})
|
||||
return result
|
||||
|
||||
@@ -19,8 +19,12 @@
|
||||
<field name="inherit_id" ref="hr.view_employee_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//group[@name='active_group']" position="before">
|
||||
<!-- Empty invisible group to be filled by timesheet_grid and sale_timesheet -->
|
||||
<group string="Timesheets" name="timesheet" invisible="1" groups="hr_timesheet.group_timesheet_manager">
|
||||
<group string="Timesheets" name="timesheet" groups="hr_timesheet.group_timesheet_manager">
|
||||
<label for="timesheet_cost"/>
|
||||
<div>
|
||||
<field name="timesheet_cost" class="oe_inline"/> per hour
|
||||
<field name="currency_id" invisible="1"/>
|
||||
</div>
|
||||
</group>
|
||||
</xpath>
|
||||
<xpath expr="//button[@name='toggle_active']" position="before">
|
||||
|
||||
@@ -21,7 +21,6 @@ have real delivered quantities in sales orders.
|
||||
'views/account_invoice_views.xml',
|
||||
'views/sale_order_views.xml',
|
||||
'views/product_views.xml',
|
||||
'views/hr_views.xml',
|
||||
'views/project_task_views.xml',
|
||||
'views/hr_timesheet_views.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
from . import account
|
||||
from . import account_invoice
|
||||
from . import hr
|
||||
from . import product
|
||||
from . import project
|
||||
from . import sale_order
|
||||
|
||||
@@ -21,14 +21,6 @@ class AccountAnalyticLine(models.Model):
|
||||
timesheet_invoice_id = fields.Many2one('account.invoice', string="Invoice", readonly=True, copy=False, help="Invoice created from the timesheet")
|
||||
timesheet_revenue = fields.Monetary("Revenue", default=0.0, readonly=True, copy=False)
|
||||
|
||||
@api.model
|
||||
def create(self, values):
|
||||
result = super(AccountAnalyticLine, self).create(values)
|
||||
# applied only for timesheet
|
||||
if result.project_id:
|
||||
result._timesheet_postprocess(values)
|
||||
return result
|
||||
|
||||
@api.multi
|
||||
def write(self, values):
|
||||
# prevent to update invoiced timesheets if one line is of type delivery
|
||||
@@ -36,8 +28,6 @@ class AccountAnalyticLine(models.Model):
|
||||
if any([field_name in values for field_name in ['unit_amount', 'employee_id', 'task_id', 'timesheet_revenue', 'so_line', 'amount', 'date']]):
|
||||
raise UserError(_('You can not modify already invoiced timesheets (linked to a Sales order items invoiced on Time and material).'))
|
||||
result = super(AccountAnalyticLine, self).write(values)
|
||||
# applied only for timesheet
|
||||
self.filtered(lambda t: t.project_id)._timesheet_postprocess(values)
|
||||
return result
|
||||
|
||||
@api.model
|
||||
@@ -55,30 +45,23 @@ class AccountAnalyticLine(models.Model):
|
||||
return values
|
||||
|
||||
@api.multi
|
||||
def _timesheet_postprocess(self, values):
|
||||
def _timesheet_postprocess_values(self, values):
|
||||
sudo_self = self.sudo() # this creates only one env for all operation that required sudo()
|
||||
# (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']]):
|
||||
result = super(AccountAnalyticLine, self)._timesheet_postprocess_values(values)
|
||||
# (re)compute the UoM from the employee company
|
||||
if any([field_name in values for field_name in ['employee_id']]):
|
||||
for timesheet in sudo_self:
|
||||
uom = timesheet.employee_id.company_id.project_time_mode_id
|
||||
cost = timesheet.employee_id.timesheet_cost or 0.0
|
||||
amount = -timesheet.unit_amount * cost
|
||||
amount_converted = timesheet.employee_id.currency_id.compute(amount, timesheet.account_id.currency_id)
|
||||
timesheet.write({
|
||||
'amount': amount_converted,
|
||||
result[timesheet.id].update({
|
||||
'product_uom_id': uom.id,
|
||||
})
|
||||
# (re)compute the theorical revenue
|
||||
if any([field_name in values for field_name in ['so_line', 'unit_amount', 'account_id']]):
|
||||
sudo_self._timesheet_compute_theorical_revenue()
|
||||
return values
|
||||
|
||||
@api.multi
|
||||
def _timesheet_compute_theorical_revenue(self):
|
||||
for timesheet in self:
|
||||
values = timesheet._timesheet_compute_theorical_revenue_values()
|
||||
timesheet.write(values)
|
||||
return True
|
||||
for timesheet in sudo_self:
|
||||
values_to_write = timesheet._timesheet_compute_theorical_revenue_values()
|
||||
if values_to_write:
|
||||
result[timesheet.id].update(values_to_write)
|
||||
return result
|
||||
|
||||
@api.multi
|
||||
def _timesheet_compute_theorical_revenue_values(self):
|
||||
@@ -93,6 +76,9 @@ class AccountAnalyticLine(models.Model):
|
||||
)
|
||||
else:
|
||||
0
|
||||
|
||||
:return: a dictionary mapping each record id to its corresponding
|
||||
dictionnary values to write (may be empty).
|
||||
"""
|
||||
self.ensure_one()
|
||||
timesheet = self
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="hr_timesheet_employee_extd_form" model="ir.ui.view">
|
||||
<field name="name">hr.timesheet.employee.extd_form</field>
|
||||
<field name="model">hr.employee</field>
|
||||
<field name="inherit_id" ref="hr_timesheet.hr_employee_view_form_inherit_timesheet"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//group[@name='timesheet']" position="inside">
|
||||
<label for="timesheet_cost"/>
|
||||
<div>
|
||||
<field name="timesheet_cost" class="oe_inline"/> per hour
|
||||
<field name="currency_id" invisible="1"/>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//group[@name='timesheet']" position="attributes">
|
||||
<attribute name="invisible">0</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user