[REF] Refactored hr_timesheet: Refactored the "timesheet" model in several modules. hr.analytic.timesheet is removed, and account.analytic.line is used directly to represent timesheets.

Also removed worklogs on tasks and replaced them by generic timesheets, i.e. account.analytic.line(s)
This commit is contained in:
odoo-tac
2015-04-28 10:31:18 +02:00
parent eb91b14837
commit 14183ba2be
46 changed files with 915 additions and 1273 deletions
@@ -642,7 +642,7 @@ class account_analytic_account(osv.osv):
return True
def hr_to_invoice_timesheets(self, cr, uid, ids, context=None):
domain = [('invoice_id','=',False),('to_invoice','!=',False), ('journal_id.type', '=', 'general'), ('account_id', 'in', ids)]
domain = [('invoice_id','=',False),('to_invoice','!=',False), ('journal_id.type', '=', 'general'), ('account_id', 'in', ids), ('unit_amount', '>', 0)]
names = [record.name for record in self.browse(cr, uid, ids, context=context)]
name = _('Timesheets to Invoice of %s') % ','.join(names)
return {
@@ -83,18 +83,18 @@
context="{'default_partner_id': [partner_id], 'search_default_project_id': [active_id],'default_project_id': [active_id], 'default_pricelist_id': pricelist_id}"
/>
</td>
</tr><tr>
</tr><tr attrs="{'invisible': [('invoice_on_timesheets','=',False)]}">
<td class="oe_timesheet_grey">
<label string="On Timesheets"/>
</td><td class="oe_timesheet_grey">
</td><td>
<field class="oe_inline" name="hours_qtt_est" attrs="{'invisible': [('invoice_on_timesheets','=',False)]}"/>
<field class="oe_inline" name="hours_qtt_est"/>
</td><td>
<field class="oe_inline" name="timesheet_ca_invoiced" attrs="{'invisible': [('invoice_on_timesheets','=',False)]}"/>
<field class="oe_inline" name="timesheet_ca_invoiced"/>
</td><td>
<field class="oe_inline" name="remaining_hours_to_invoice" attrs="{'invisible': [('invoice_on_timesheets','=',False)]}"/>
<field class="oe_inline" name="remaining_hours_to_invoice"/>
</td><td>
<field class="oe_inline" name="ca_to_invoice" attrs="{'invisible': [('invoice_on_timesheets','=',False)]}"/>
<field class="oe_inline" name="ca_to_invoice"/>
</td><td class="oe_timesheet_action" attrs="{'invisible': ['|',('invoice_on_timesheets','=',False),('type','=','template')]}">
<span attrs="{'invisible': [('ca_to_invoice','=',0.0)]}" class="oe_grey">
<button name="hr_to_invoice_timesheets"
@@ -15,7 +15,7 @@
<field name="project_id" ref="analytic.analytic_consultancy"/>
</record>
<record id="hr_timesheet.working_hours_maintenance" model="hr.analytic.timesheet">
<record id="hr_timesheet.working_hours_maintenance" model="account.analytic.line">
<field name="account_id" ref="analytic.analytic_consultancy"/>
<field name="to_invoice" ref="hr_timesheet_invoice.timesheet_invoice_factor1"/>
<field name="unit_amount">100</field>
+2 -1
View File
@@ -357,7 +357,8 @@ class account_analytic_line(osv.osv):
_defaults = {
'date': __get_default_date,
'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.analytic.line', context=c),
'amount': 0.00
'amount': 0.00,
'user_id': lambda self, cr, uid, ctx: ctx.get('user_id') or uid,
}
_order = 'date desc'
@@ -19,22 +19,24 @@
#
##############################################################################
from openerp.osv import fields,osv
from openerp.osv import fields, osv
from openerp.tools.translate import _
import openerp.addons.decimal_precision as dp
from openerp.exceptions import UserError
class analytic_user_funct_grid(osv.osv):
_name="analytic.user.funct.grid"
_description= "Price per User"
_rec_name="user_id"
_columns={
_name = "analytic.user.funct.grid"
_description = "Price per User"
_rec_name = "user_id"
_columns = {
'user_id': fields.many2one("res.users", "User", required=True,),
'product_id': fields.many2one("product.product", "Service", required=True,),
'account_id': fields.many2one("account.analytic.account", "Analytic Account", required=True,),
'uom_id': fields.related("product_id", "uom_id", relation="product.uom", string="Unit of Measure", type="many2one", readonly=True),
'price': fields.float('Price', digits_compute=dp.get_precision('Product Price'), help="Price per hour for this user.", required=True),
}
def onchange_user_product_id(self, cr, uid, ids, user_id, product_id, context=None):
if not user_id:
return {}
@@ -46,7 +48,7 @@ class analytic_user_funct_grid(osv.osv):
value = {}
prod = False
if product_id:
prod = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
prod = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
emp = emp_obj.browse(cr, uid, emp_id[0], context=context)
if emp.product_id and not product_id:
value['product_id'] = emp.product_id.id
@@ -64,92 +66,73 @@ class account_analytic_account(osv.osv):
}
class hr_analytic_timesheet(osv.osv):
_inherit = "hr.analytic.timesheet"
# Look in account, if no value for the user => look in parent until there is no more parent to look
# Take the first found... if nothing found => return False
def _get_related_user_account_recursiv(self, cr, uid, user_id, account_id):
temp=self.pool.get('analytic.user.funct.grid').search(cr, uid, [('user_id', '=', user_id),('account_id', '=', account_id) ])
account=self.pool.get('account.analytic.account').browse(cr, uid, account_id)
if temp:
return temp
else:
if account.parent_id:
return self._get_related_user_account_recursiv(cr, uid, user_id, account.parent_id.id)
else:
return False
def on_change_account_id(self, cr, uid, ids, account_id, user_id=False, unit_amount=0):
res = {}
if not (account_id):
#avoid a useless call to super
return res
if not (user_id):
return super(hr_analytic_timesheet, self).on_change_account_id(cr, uid, ids, account_id)
#get the browse record related to user_id and account_id
temp = self._get_related_user_account_recursiv(cr, uid, user_id, account_id)
if not temp:
#if there isn't any record for this user_id and account_id
return super(hr_analytic_timesheet, self).on_change_account_id(cr, uid, ids, account_id)
else:
#get the old values from super and add the value from the new relation analytic_user_funct_grid
r = self.pool.get('analytic.user.funct.grid').browse(cr, uid, temp)[0]
res.setdefault('value',{})
res['value']= super(hr_analytic_timesheet, self).on_change_account_id(cr, uid, ids, account_id)['value']
res['value']['product_id'] = r.product_id.id
res['value']['product_uom_id'] = r.product_id.uom_id.id
#the change of product has to impact the amount, uom and general_account_id
a = r.product_id.property_account_expense.id
if not a:
a = r.product_id.categ_id.property_account_expense_categ.id
if not a:
raise UserError(_('There is no expense account defined for this product: "%s" (id:%d)') % (r.product_id.name, r.product_id.id,))
# Compute based on pricetype
if unit_amount:
amount_unit = self.on_change_unit_amount(cr, uid, ids,
r.product_id.id, unit_amount, False, r.product_id.uom_id.id)['value']['amount']
amount = unit_amount * amount_unit
res ['value']['amount']= - round(amount, 2)
res ['value']['general_account_id']= a
return res
def on_change_user_id(self, cr, uid, ids, user_id, account_id, unit_amount=0):
res = super(hr_analytic_timesheet, self).on_change_user_id(cr, uid, ids, user_id)
if account_id:
#get the browse record related to user_id and account_id
temp = self._get_related_user_account_recursiv(cr, uid, user_id, account_id)
if temp:
#add the value from the new relation analytic_user_funct_grid
r = self.pool.get('analytic.user.funct.grid').browse(cr, uid, temp)[0]
res['value']['product_id'] = r.product_id.id
#the change of product has to impact the amount, uom and general_account_id
a = r.product_id.property_account_expense.id
if not a:
a = r.product_id.categ_id.property_account_expense_categ.id
if not a:
raise UserError(
_('There is no expense account defined ' \
'for this product: "%s" (id:%d)') % \
(r.product_id.name, r.product_id.id,))
# Compute based on pricetype
if unit_amount:
amount_unit = self.on_change_unit_amount(cr, uid, ids,
r.product_id.id, unit_amount, False, r.product_id.uom_id.id)['value']['amount']
amount = unit_amount * amount_unit
res ['value']['amount']= - round(amount, 2)
res ['value']['general_account_id']= a
return res
class account_analytic_line(osv.osv):
_inherit = "account.analytic.line"
def _get_invoice_price(self, cr, uid, account, product_id, user_id, qty, context = {}):
# Look in account, if no value for the user => look in parent until there is no more parent to look
# Take the first found... if nothing found => return False
def _get_related_user_account_recursiv(self, cr, uid, user_id, account_id, context):
user_function_id = self.pool.get('analytic.user.funct.grid').search(cr, uid, [('user_id', '=', user_id), ('account_id', '=', account_id)])
account = self.pool['account.analytic.account'].browse(cr, uid, account_id)
if user_function_id:
return user_function_id
elif account.parent_id:
return self._get_related_user_account_recursiv(cr, uid, user_id, account.parent_id.id, context)
else:
return False
def on_change_account_id(self, cr, uid, ids, account_id, user_id=False, unit_amount=0, is_timesheet=False, context=None):
res = {}
if not (account_id):
return res
if not (user_id):
return super(account_analytic_line, self).on_change_account_id(cr, uid, ids, account_id, user_id, is_timesheet, context)
# get the browse record related to user_id and account_id
user_function_id = self._get_related_user_account_recursiv(cr, uid, user_id, account_id, context)
if not user_function_id:
# if there isn't any record for this user_id and account_id
return super(account_analytic_line, self).on_change_account_id(cr, uid, ids, account_id, user_id, is_timesheet, context)
else:
# get the old values from super and add the value from the new relation analytic_user_funct_grid
res['value'] = super(account_analytic_line, self).on_change_account_id(cr, uid, ids, account_id, user_id, is_timesheet, context)['value']
res['value'].update(self._get_values_based_on_user_function(cr, uid, ids, user_function_id, unit_amount, context=context))
return res
def on_change_user_id(self, cr, uid, ids, user_id, unit_amount=0, account_id=None, is_timesheet=False, context=None):
res = {}
res = super(account_analytic_line, self).on_change_user_id(cr, uid, ids, user_id, is_timesheet, context=context)
if account_id:
# get the browse record related to user_id and account_id
user_function_id = self._get_related_user_account_recursiv(cr, uid, user_id, account_id, context)
if user_function_id:
res['value'].update(self._get_values_based_on_user_function(cr, uid, ids, user_function_id, unit_amount, context=context))
return res
# Returns appropriate values for an analytic line if the user has a specific user_function
def _get_values_based_on_user_function(self, cr, uid, ids, user_function_id, unit_amount=0, context=None):
res = {}
user_function = self.pool['analytic.user.funct.grid'].browse(cr, uid, user_function_id, context=context)
product = user_function.product_id
res['product_id'] = product.id
res['uom_id'] = product.uom_id.id
expense_account = product.property_account_expense.id
if not expense_account:
expense_account = product.categ_id.property_account_expense_categ.id
if not expense_account:
raise UserError(_('There is no expense account defined for this product: "%s" (id:%d)') % (product.name, product.id,))
res['general_account_id'] = expense_account
if unit_amount:
new_amount = self.on_change_unit_amount(cr, uid, ids, product.id, unit_amount, False, product.uom_id.id)
res['amount'] = new_amount['value']['amount']
return res
def _get_invoice_price(self, cr, uid, account, product_id, user_id, qty, context={}):
for grid in account.user_product_ids:
if grid.user_id.id==user_id:
if grid.user_id.id == user_id:
return grid.price
return super(account_analytic_line, self)._get_invoice_price(cr, uid, account, product_id, user_id, qty, context)
@@ -66,7 +66,7 @@
<field name="inherit_id" ref="hr_timesheet_sheet.hr_timesheet_sheet_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='timesheet_ids']/tree/field[@name='account_id']" position="attributes">
<attribute name="on_change">on_change_account_id(account_id, user_id, unit_amount)</attribute>
<attribute name="on_change">on_change_account_id(account_id, user_id, unit_amount, is_timesheet, context)</attribute>
</xpath>
</field>
</record>
@@ -79,8 +79,7 @@
<field name="inherit_id" ref="hr_timesheet_sheet.hr_timesheet_sheet_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='timesheet_ids']/form/field[@name='account_id']" position="attributes">
<attribute name="domain">[('type','=','normal'),('state', '&lt;&gt;', 'close')]</attribute>
<attribute name="on_change">on_change_account_id(account_id, user_id, unit_amount)</attribute>
<attribute name="on_change">on_change_account_id(account_id, user_id, unit_amount, is_timesheet, context)</attribute>
<attribute name="context">{'default_invoice_on_timesheets': 1}</attribute>
</xpath>
</field>
@@ -88,50 +87,39 @@
<!-- hr.analytic.timesheet inherited views -->
<record model="ir.ui.view" id="hr_timesheet_line_form_inherit">
<field name="name">hr.analytic.timesheet.form</field>
<field name="model">hr.analytic.timesheet</field>
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_form"/>
<field name="name">account.analytic.line.form</field>
<field name="model">account.analytic.line</field>
<field name="inherit_id" ref="hr_timesheet_invoice.hr_timesheet_line_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='account_id']" position="attributes">
<attribute name="domain">[('type','=','normal'),('state', '&lt;&gt;', 'close')]</attribute>
<attribute name="on_change">on_change_account_id(account_id, user_id, unit_amount)</attribute>
<attribute name="on_change">on_change_account_id(account_id, user_id, unit_amount, is_timesheet, context)</attribute>
</xpath>
</field>
</record>
<record model="ir.ui.view" id="hr_timesheet_line_form_inherit_2">
<field name="name">hr.analytic.timesheet.form</field>
<field name="model">hr.analytic.timesheet</field>
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_form"/>
<field name="name">account.analytic.line.form</field>
<field name="model">account.analytic.line</field>
<field name="inherit_id" ref="hr_timesheet_invoice.hr_timesheet_line_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='user_id']" position="replace">
<field name="user_id" required="1" on_change="on_change_user_id(user_id, account_id, unit_amount)"/>
<field name="user_id" required="1" on_change="on_change_user_id(user_id, unit_amount, account_id, is_timesheet, context)"/>
</xpath>
</field>
</record>
<record model="ir.ui.view" id="hr_timesheet_line_tree_inherit">
<field name="name">hr.analytic.timesheet.tree</field>
<field name="model">hr.analytic.timesheet</field>
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_tree"/>
<field name="name">account.analytic.line.tree</field>
<field name="model">account.analytic.line</field>
<field name="inherit_id" ref="hr_timesheet_invoice.hr_timesheet_line_tree"/>
<field name="arch" type="xml">
<xpath expr="/tree/field[@name='account_id']" position="attributes">
<attribute name="domain">[('type','=','normal'),('state', '&lt;&gt;', 'close')]</attribute>
<attribute name="on_change">on_change_account_id(account_id, user_id, unit_amount)</attribute>
<attribute name="on_change">on_change_account_id(account_id, user_id, unit_amount, is_timesheet, context)</attribute>
</xpath>
</field>
</record>
<record model="ir.ui.view" id="hr_timesheet_line_tree_inherit_2">
<field name="name">hr.analytic.timesheet.tree</field>
<field name="model">hr.analytic.timesheet</field>
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_tree"/>
<field name="arch" type="xml">
<xpath expr="/tree/field[@name='user_id']" position="replace">
<field name="user_id" required="1" on_change="on_change_user_id(user_id, account_id, unit_amount)" context="{'default_groups_ref': ['base.group_user']}"/>
<field name="user_id" required="1" on_change="on_change_user_id(user_id, unit_amount, account_id, is_timesheet, context)" context="{'default_groups_ref': ['base.group_user']}"/>
</xpath>
</field>
</record>
</data>
</openerp>
-2
View File
@@ -43,7 +43,6 @@ up a management by affair.
'depends': ['account', 'hr', 'base', 'hr_attendance'],
'data': [
'security/ir.model.access.csv',
'security/hr_timesheet_security.xml',
'hr_timesheet_view.xml',
'report/hr_timesheet_report_view.xml',
'hr_timesheet_installer.xml',
@@ -52,7 +51,6 @@ up a management by affair.
'demo': ['hr_timesheet_demo.xml'],
'test': [
'test/hr_timesheet_users.yml',
'test/hr_timesheet_demo.yml',
],
'installable': True,
'auto_install': False,
+126 -173
View File
@@ -19,79 +19,121 @@
#
##############################################################################
import time
from openerp import models, api, fields, exceptions
from openerp.osv import fields
from openerp.osv import osv
from openerp.tools.translate import _
import openerp
from openerp.exceptions import UserError
class hr_employee(osv.osv):
_name = "hr.employee"
class HrEmployee(models.Model):
_inherit = "hr.employee"
_columns = {
'product_id': fields.many2one('product.product', 'Product', help="If you want to reinvoice working time of employees, link this employee to a service to determinate the cost price of the job."),
'journal_id': fields.many2one('account.analytic.journal', 'Analytic Journal'),
'uom_id': fields.related('product_id', 'uom_id', type='many2one', relation='product.uom', string='Unit of Measure', store=True, readonly=True)
}
def _getAnalyticJournal(self, cr, uid, context=None):
md = self.pool.get('ir.model.data')
try:
dummy, res_id = md.get_object_reference(cr, uid, 'hr_timesheet', 'analytic_journal')
#search on id found in result to check if current user has read access right
check_right = self.pool.get('account.analytic.journal').search(cr, uid, [('id', '=', res_id)], context=context)
if check_right:
return res_id
except ValueError:
pass
return False
@api.model
def _get_default_analytic_journal(self):
# We need the "or ..." because we need an empty recordset of the right type, 'False' is not enough.
return self.env.ref('hr_timesheet.analytic_journal', raise_if_not_found=False) or self.env['account.analytic.journal']
def _getEmployeeProduct(self, cr, uid, context=None):
md = self.pool.get('ir.model.data')
try:
dummy, res_id = md.get_object_reference(cr, uid, 'product', 'product_product_consultant')
#search on id found in result to check if current user has read access right
check_right = self.pool.get('product.template').search(cr, uid, [('id', '=', res_id)], context=context)
if check_right:
return res_id
except ValueError:
pass
return False
@api.model
def _get_default_employee_product(self):
# We need the "or ..." because we need an empty recordset of the right type, 'False' is not enough.
return self.env.ref('product.product_product_consultant', raise_if_not_found=False) or self.env['account.analytic.journal']
_defaults = {
'journal_id': _getAnalyticJournal,
'product_id': _getEmployeeProduct
}
product_id = fields.Many2one('product.product', 'Product', help="If you want to reinvoice working time of employees, link this employee to a service to determinate the cost price of the job.", default=_get_default_employee_product)
journal_id = fields.Many2one('account.analytic.journal', 'Analytic Journal', default=_get_default_analytic_journal)
uom_id = fields.Many2one('product.uom', related='product_id.uom_id', string='Unit of Measure', store=True, readonly=True)
class hr_analytic_timesheet(osv.osv):
_name = "hr.analytic.timesheet"
_table = 'hr_analytic_timesheet'
_description = "Timesheet Line"
_inherits = {'account.analytic.line': 'line_id'}
_order = "id desc"
_columns = {
'line_id': fields.many2one('account.analytic.line', 'Analytic Line', ondelete='cascade', required=True),
'partner_id': fields.related('account_id', 'partner_id', type='many2one', string='Partner', relation='res.partner', store=True),
}
class account_analytic_line(models.Model):
_inherit = 'account.analytic.line'
def unlink(self, cr, uid, ids, context=None):
toremove = {}
for obj in self.browse(cr, uid, ids, context=context):
toremove[obj.line_id.id] = True
super(hr_analytic_timesheet, self).unlink(cr, uid, ids, context=context)
self.pool.get('account.analytic.line').unlink(cr, uid, toremove.keys(), context=context)
return True
def default_get(self, cr, uid, fields, context=None):
values = super(account_analytic_line, self).default_get(cr, uid, fields, context=context)
if values.get('is_timesheet'):
if 'product_id' in fields:
values['product_id'] = self._get_employee_product(cr, uid, context=context)
if 'product_uom_id' in fields:
values['product_uom_id'] = self._get_employee_unit(cr, uid, context=context)
if 'general_account_id' in fields:
values['general_account_id'] = self._get_general_account(cr, uid, context=context)
if 'journal_id' in fields:
values['journal_id'] = self._get_analytic_journal(cr, uid, context=context)
return values
is_timesheet = fields.Boolean()
@api.model
def _get_employee_product(self, user_id=None):
emp = self.env['hr.employee'].search([('user_id', '=', user_id or self.env.uid)], limit=1)
if emp and emp.product_id.id:
return emp.product_id.id
else:
raise exceptions.ValidationError("This user or employee is not associated to a valid Product")
@api.model
def _get_employee_unit(self, user_id=None):
emp = self.env['hr.employee'].search([('user_id', '=', user_id or self.env.uid)], limit=1)
if emp and emp.product_id.uom_id.id:
return emp.product_id.uom_id.id
else:
raise exceptions.ValidationError("This user or employee is not associated to a Product with a valid Amount Type")
@api.model
def _get_general_account(self, user_id=None):
emp = self.env['hr.employee'].search([('user_id', '=', user_id or self.env.uid)], limit=1)
if emp and emp.product_id.categ_id.property_account_expense_categ.id:
return emp.product_id.categ_id.property_account_expense_categ.id
else:
raise exceptions.ValidationError("This user or employee is not associated to a Product with a valid Financial Account")
@api.model
def _get_analytic_journal(self, user_id=None):
emp = self.env['hr.employee'].search([('user_id', '=', user_id or self.env.uid)], limit=1)
if emp and emp.journal_id.id:
return emp.journal_id.id
else:
raise exceptions.ValidationError("This user or employee is not associated to a valid Journal ID")
@api.one
@api.constrains('user_id')
def check_user_id(self):
if self.is_timesheet:
emp = self.env['hr.employee'].search([('user_id', '=', self.user_id.id)], limit=1)
if not emp:
raise exceptions.ValidationError("There is no employee defined for user " + self.user_id.name)
else:
if not emp.journal_id.id:
raise exceptions.ValidationError("The employee " + emp.name + " is not associated to a valid Analytic Journal. Please define one for him or select another user.")
elif not emp.product_id:
raise exceptions.ValidationError("The employee " + emp.name + " is not associated to a valid Product. Please define one for him or select another user.")
elif not emp.product_id.uom_id.id:
raise exceptions.ValidationError("The employee " + emp.name + " is not associated to a valid Product Unit of Measure. Please define one for him or select another user.")
elif not emp.product_id.categ_id.property_account_expense_categ.id:
raise exceptions.ValidationError("The employee " + emp.name + " is not associated to a valid Financial Account. Please define one for him or select another user.")
@api.onchange('user_id')
def V8_on_change_user_id(self):
new_values = self.on_change_user_id(self.user_id.id, self.is_timesheet)
if new_values.get("value"):
for key, value in new_values["value"].iteritems():
setattr(self, key, value)
def on_change_user_id(self, cr, uid, ids, user_id, is_timesheet=False, context=None):
if not is_timesheet or not user_id:
return {}
res = {"value": {
'journal_id': self._get_analytic_journal(cr, uid, user_id, context=context),
'product_id': self._get_employee_product(cr, uid, user_id, context=context),
'uom_id': self._get_employee_unit(cr, uid, user_id, context=context),
'general_account_id': self._get_general_account(cr, uid, user_id, context=context)
}
}
return res
def on_change_unit_amount(self, cr, uid, id, prod_id, unit_amount, company_id, unit=False, journal_id=False, context=None):
res = {'value':{}}
res = {'value': {}}
if prod_id and unit_amount:
# find company
company_id = self.pool.get('res.company')._company_default_get(cr, uid, 'account.analytic.line', context=context)
r = self.pool.get('account.analytic.line').on_change_unit_amount(cr, uid, id, prod_id, unit_amount, company_id, unit, journal_id, context=context)
r = super(account_analytic_line, self).on_change_unit_amount(cr, uid, id, prod_id, unit_amount, company_id, unit, journal_id, context=context)
if r:
res.update(r)
# update unit of measurement
@@ -103,114 +145,36 @@ class hr_analytic_timesheet(osv.osv):
res['value'].update({'product_uom_id': False})
return res
def _getEmployeeProduct(self, cr, uid, context=None):
if context is None:
context = {}
emp_obj = self.pool.get('hr.employee')
emp_id = emp_obj.search(cr, uid, [('user_id', '=', context.get('user_id') or uid)], context=context)
if emp_id:
emp = emp_obj.browse(cr, uid, emp_id[0], context=context)
if emp.product_id:
return emp.product_id.id
return False
def _getEmployeeUnit(self, cr, uid, context=None):
emp_obj = self.pool.get('hr.employee')
if context is None:
context = {}
emp_id = emp_obj.search(cr, uid, [('user_id', '=', context.get('user_id') or uid)], context=context)
if emp_id:
emp = emp_obj.browse(cr, uid, emp_id[0], context=context)
if emp.product_id:
return emp.product_id.uom_id.id
return False
def _getGeneralAccount(self, cr, uid, context=None):
emp_obj = self.pool.get('hr.employee')
if context is None:
context = {}
emp_id = emp_obj.search(cr, uid, [('user_id', '=', context.get('user_id') or uid)], context=context)
if emp_id:
emp = emp_obj.browse(cr, uid, emp_id[0], context=context)
if bool(emp.product_id):
a = emp.product_id.property_account_expense.id
if not a:
a = emp.product_id.categ_id.property_account_expense_categ.id
if a:
return a
return False
def _getAnalyticJournal(self, cr, uid, context=None):
emp_obj = self.pool.get('hr.employee')
if context is None:
context = {}
if context.get('employee_id'):
emp_id = [context.get('employee_id')]
else:
emp_id = emp_obj.search(cr, uid, [('user_id','=',context.get('user_id') or uid)], limit=1, context=context)
if not emp_id:
model, action_id = self.pool['ir.model.data'].get_object_reference(cr, uid, 'hr', 'open_view_employee_list_my')
msg = _("Employee is not created for this user. Please create one from configuration panel.")
raise openerp.exceptions.RedirectWarning(msg, action_id, _('Go to the configuration panel'))
emp = emp_obj.browse(cr, uid, emp_id[0], context=context)
if emp.journal_id:
return emp.journal_id.id
else :
raise UserError(_('No analytic journal defined for \'%s\'.\nYou should assign an analytic journal on the employee form.') % (emp.name,))
@api.onchange('date')
def on_change_date(self):
if self.is_timesheet and self._origin.date and (self._origin.date != self.date):
raise exceptions.Warning(_('Changing the date will let this entry appear in the timesheet of the new date.'))
_defaults = {
'product_uom_id': _getEmployeeUnit,
'product_id': _getEmployeeProduct,
'general_account_id': _getGeneralAccount,
'journal_id': _getAnalyticJournal,
'date': lambda self, cr, uid, ctx: ctx.get('date', fields.date.context_today(self,cr,uid,context=ctx)),
'user_id': lambda obj, cr, uid, ctx: ctx.get('user_id') or uid,
}
def on_change_account_id(self, cr, uid, ids, account_id, context=None):
return {'value':{}}
def on_change_date(self, cr, uid, ids, date):
if ids:
new_date = self.read(cr, uid, ids[0], ['date'])['date']
if date != new_date:
warning = {'title':_('User Alert!'),'message':_('Changing the date will let this entry appear in the timesheet of the new date.')}
return {'value':{},'warning':warning}
return {'value':{}}
def create(self, cr, uid, vals, context=None):
if context is None:
context = {}
emp_obj = self.pool.get('hr.employee')
emp_id = emp_obj.search(cr, uid, [('user_id', '=', context.get('user_id') or uid)], context=context)
ename = ''
if emp_id:
ename = emp_obj.browse(cr, uid, emp_id[0], context=context).name
if not vals.get('journal_id'):
raise UserError(_('No \'Analytic Journal\' is defined for employee %s \nDefine an employee for the selected user and assign an \'Analytic Journal\'!') % (ename,))
if not vals.get('account_id'):
raise UserError(_('No analytic account is defined on the project.\nPlease set one or we cannot automatically fill the timesheet.'))
return super(hr_analytic_timesheet, self).create(cr, uid, vals, context=context)
def on_change_user_id(self, cr, uid, ids, user_id):
if not user_id:
return {}
context = {'user_id': user_id}
return {'value': {
'product_id': self. _getEmployeeProduct(cr, uid, context),
'product_uom_id': self._getEmployeeUnit(cr, uid, context),
'general_account_id': self._getGeneralAccount(cr, uid, context),
'journal_id': self._getAnalyticJournal(cr, uid, context),
}}
class account_analytic_account(osv.osv):
class account_analytic_account(models.Model):
_inherit = 'account.analytic.account'
_description = 'Analytic Account'
_columns = {
'use_timesheets': fields.boolean('Timesheets', help="Check this field if this project manages timesheets", deprecated=True),
'invoice_on_timesheets': fields.boolean('Timesheets', help="Check this field if this project manages timesheets"),
}
use_timesheets = fields.Boolean('Timesheets', help="Check this field if this project manages timesheets", deprecated=True)
invoice_on_timesheets = fields.Boolean('Timesheets', help="Check this field if this project manages timesheets")
@api.onchange('invoice_on_timesheets')
def onchange_invoice_on_timesheets(self):
result = {}
if not self.invoice_on_timesheets:
return {'value': {'to_invoice': False}}
try:
to_invoice = self.env['ir.model.data'].xmlid_to_res_id('hr_timesheet_invoice.timesheet_invoice_factor1')
result['to_invoice'] = to_invoice
except ValueError:
pass
return result
@api.onchange('template_id')
def V8_on_change_template(self):
new_values = self.on_change_template(selftemplate_id.id, self.date_start)
if new_values.get("value"):
for key, value in new_values["value"].iteritems():
setattr(self, key, value)
def on_change_template(self, cr, uid, ids, template_id, date_start=False, context=None):
res = super(account_analytic_account, self).on_change_template(cr, uid, ids, template_id, date_start=date_start, context=context)
@@ -218,14 +182,3 @@ class account_analytic_account(osv.osv):
template = self.browse(cr, uid, template_id, context=context)
res['value']['invoice_on_timesheets'] = template.invoice_on_timesheets
return res
def onchange_invoice_on_timesheets(self, cr, uid, ids, invoice_on_timesheets, context=None):
result = {}
if not invoice_on_timesheets:
return {'value': {'to_invoice': False}}
try:
to_invoice = self.pool.get('ir.model.data').xmlid_to_res_id(cr, uid, 'hr_timesheet_invoice.timesheet_invoice_factor1')
result['to_invoice'] = to_invoice
except ValueError:
pass
return result
+10 -5
View File
@@ -26,7 +26,7 @@
<field name="invoice_on_timesheets">True</field>
</record>
<record id="working_hours_requirements" model="hr.analytic.timesheet">
<record id="working_hours_requirements" model="account.analytic.line">
<field name="name">Requirements analysis and specification</field>
<field name="user_id" ref="base.user_root"/>
<field eval="time.strftime('%Y-%m-%d')" name="date"/>
@@ -37,9 +37,10 @@
<field eval="-60.00" name="amount"/>
<field name="general_account_id" ref="account.a_expense"/>
<field name="journal_id" ref="analytic_journal"/>
<field name="is_timesheet" eval="True"/>
</record>
<record id="working_hours_design" model="hr.analytic.timesheet">
<record id="working_hours_design" model="account.analytic.line">
<field name="name">Design and specification</field>
<field name="user_id" ref="base.user_root"/>
<field eval="time.strftime('%Y-%m-%d')" name="date"/>
@@ -50,9 +51,10 @@
<field eval="-30.00" name="amount"/>
<field name="general_account_id" ref="account.a_expense"/>
<field name="journal_id" ref="analytic_journal"/>
<field name="is_timesheet" eval="True"/>
</record>
<record id="working_hours_coding" model="hr.analytic.timesheet">
<record id="working_hours_coding" model="account.analytic.line">
<field name="name">Coding and module testing</field>
<field name="user_id" ref="base.user_root"/>
<field eval="time.strftime('%Y-%m-%d')" name="date"/>
@@ -63,9 +65,10 @@
<field eval="-90.00" name="amount"/>
<field name="general_account_id" ref="account.a_expense"/>
<field name="journal_id" ref="analytic_journal"/>
<field name="is_timesheet" eval="True"/>
</record>
<record id="working_hours_testing" model="hr.analytic.timesheet">
<record id="working_hours_testing" model="account.analytic.line">
<field name="name">Integration and system testing</field>
<field name="user_id" ref="base.user_root"/>
<field eval="time.strftime('%Y-%m-%d')" name="date"/>
@@ -76,9 +79,10 @@
<field eval="-30.00" name="amount"/>
<field name="general_account_id" ref="account.a_expense"/>
<field name="journal_id" ref="analytic_journal"/>
<field name="is_timesheet" eval="True"/>
</record>
<record id="working_hours_maintenance" model="hr.analytic.timesheet">
<record id="working_hours_maintenance" model="account.analytic.line">
<field name="name">Delivery and maintenance</field>
<field name="user_id" ref="base.user_root"/>
<field eval="time.strftime('%Y-%m-%d')" name="date"/>
@@ -89,6 +93,7 @@
<field eval="-30.00" name="amount"/>
<field name="general_account_id" ref="account.a_expense"/>
<field name="journal_id" ref="analytic_journal"/>
<field name="is_timesheet" eval="True"/>
</record>
<record id="product.product_product_consultant" model="product.product">
+27 -21
View File
@@ -3,12 +3,12 @@
<data>
<record id="hr_timesheet_line_tree" model="ir.ui.view">
<field name="name">hr.analytic.timesheet.tree</field>
<field name="model">hr.analytic.timesheet</field>
<field name="name">account.analytic.line.tree</field>
<field name="model">account.analytic.line</field>
<field name="arch" type="xml">
<tree editable="top" string="Timesheet Activities">
<field name="date" on_change="on_change_date(date)"/>
<field name="user_id" on_change="on_change_user_id(user_id)" required="1" options='{"no_open": True}'
<field name="date"/>
<field name="user_id" required="1" options='{"no_open": True}'
context="{'default_groups_ref': ['base.group_user']}"/>
<field name="name"/>
<field domain="[('type','=','normal'),('invoice_on_timesheets','=',1)]" name="account_id" context="{'default_use_timesheets': 1, 'default_type': 'contract'}" options="{'no_open': True, 'no_create': True}"/>
@@ -18,23 +18,25 @@
<field name="amount" sum="Total cost" invisible="1"/>
<field name="general_account_id" invisible="1"/>
<field name="product_id" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)" required="1" domain="[('type','=','service')]" invisible="1"/>
<field name="is_timesheet" invisible="1"/>
</tree>
</field>
</record>
<record id="hr_timesheet_line_form" model="ir.ui.view">
<field name="name">hr.analytic.timesheet.form</field>
<field name="model">hr.analytic.timesheet</field>
<field name="name">account.analytic.line.form</field>
<field name="model">account.analytic.line</field>
<field name="arch" type="xml">
<form string="Timesheet Activities">
<sheet>
<group>
<group>
<field name="name"/>
<field name="user_id" on_change="on_change_user_id(user_id)" required="1"
<field name="user_id" required="1"
context="{'default_groups_ref': ['base.group_user']}"/>
<field name="is_timesheet" invisible="1"/>
</group>
<group>
<field name="date" on_change="on_change_date(date)"/>
<field name="date"/>
</group>
</group>
<notebook>
@@ -44,8 +46,8 @@
<field name="product_id" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)" required="1" domain="[('type','=','service')]"/>
<label string="Duration" for="unit_amount"/>
<div>
<field name="unit_amount" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)" class="oe_inline"/>
<field name="product_uom_id" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)" class="oe_inline"/>
<field name="unit_amount" class="oe_inline" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)"/>
<field name="product_uom_id" class="oe_inline"/>
</div>
</group>
<group string="Accounting">
@@ -61,10 +63,9 @@
</form>
</field>
</record>
<record id="hr_timesheet_line_search" model="ir.ui.view">
<field name="name">hr.analytic.timesheet.search</field>
<field name="model">hr.analytic.timesheet</field>
<field name="name">account.analytic.line.search</field>
<field name="model">account.analytic.line</field>
<field name="arch" type="xml">
<search string="Timesheet">
<field name="date"/>
@@ -87,7 +88,7 @@
<field name="res_model">account.analytic.line</field>
<field name="src_model">account.analytic.account</field>
<field name="view_mode">tree,form</field>
<field name="view_type">form</field>
<field name="view_type">tree</field>
<field name="help" type="html">
<p>
No activity yet on this contract.
@@ -114,7 +115,7 @@
<field name="inherit_id" ref="analytic.view_account_analytic_account_form"/>
<field name="arch" type="xml">
<xpath expr='//div[@name="project"]' position='inside'>
<field name="invoice_on_timesheets" on_change="onchange_invoice_on_timesheets(invoice_on_timesheets)"/>
<field name="invoice_on_timesheets"/>
<label for="invoice_on_timesheets"/>
</xpath>
<xpath expr='//div[@name="buttons"]' position='inside'>
@@ -140,11 +141,10 @@
<record id="act_hr_timesheet_line_evry1_all_form" model="ir.actions.act_window">
<field name="name">Activities</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">hr.analytic.timesheet</field>
<field name="res_model">account.analytic.line</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="context">{"search_default_today":1}</field>
<field name="view_id" ref="hr_timesheet_line_form"/>
<field name="context">{"search_default_today":1, "default_is_timesheet":1}</field>
<field name="search_view_id" ref="hr_timesheet_line_search"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
@@ -158,8 +158,14 @@
</field>
</record>
<menuitem id="menu_hr_working_hours" parent="hr_attendance.menu_hr_time_tracking" action="act_hr_timesheet_line_evry1_all_form"
groups="base.group_no_one"/>
<record model="ir.actions.act_window.view" id="action_open_timesheet_tree">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="hr_timesheet_line_tree"/>
<field name="act_window_id" ref="act_hr_timesheet_line_evry1_all_form"/>
</record>
<menuitem id="menu_hr_working_hours" parent="hr_attendance.menu_hr_time_tracking" action="act_hr_timesheet_line_evry1_all_form"/>
<record id="hr_timesheet_employee_extd_form" model="ir.ui.view">
<field name="name">hr.timesheet.employee.extd_form</field>
@@ -1,28 +1,27 @@
from openerp import tools
from openerp.osv import fields,osv
from openerp import tools, models, fields
from openerp.addons.decimal_precision import decimal_precision as dp
class hr_timesheet_report(osv.osv):
class hr_timesheet_report(models.Model):
_name = "hr.timesheet.report"
_description = "Timesheet"
_auto = False
_columns = {
'date': fields.date('Date', readonly=True),
'name': fields.char('Description', size=64,readonly=True),
'product_id' : fields.many2one('product.product', 'Product',readonly=True),
'journal_id' : fields.many2one('account.analytic.journal', 'Journal',readonly=True),
'general_account_id' : fields.many2one('account.account', 'General Account', readonly=True),
'user_id': fields.many2one('res.users', 'User',readonly=True),
'account_id': fields.many2one('account.analytic.account', 'Analytic Account',readonly=True),
'company_id': fields.many2one('res.company', 'Company',readonly=True),
'cost': fields.float('Cost', readonly=True, digits_compute=dp.get_precision('Account')),
'quantity': fields.float('Time', readonly=True), # TDE FIXME master: rename into time
}
date = fields.Date('Date', readonly=True)
name = fields.Char('Description', size=64, readonly=True)
product_id = fields.Many2one('product.product', 'Product', readonly=True)
journal_id = fields.Many2one('account.analytic.journal', 'Journal', readonly=True)
general_account_id = fields.Many2one('account.account', 'General Account', readonly=True)
user_id = fields.Many2one('res.users', 'User', readonly=True)
account_id = fields.Many2one('account.analytic.account', 'Analytic Account', readonly=True)
company_id = fields.Many2one('res.company', 'Company', readonly=True)
cost = fields.Float('Cost', readonly=True, digits_compute=dp.get_precision('Account'))
quantity = fields.Float('Time', readonly=True) # TDE FIXME master: rename into time
def _select(self):
select_str = """
SELECT min(hat.id) as id,
SELECT min(aal.id) as id,
aal.date as date,
sum(aal.amount) as cost,
sum(aal.unit_amount) as quantity,
@@ -38,8 +37,7 @@ class hr_timesheet_report(osv.osv):
def _from(self):
from_str = """
account_analytic_line as aal
inner join hr_analytic_timesheet as hat ON (hat.line_id=aal.id)
FROM account_analytic_line as aal
"""
return from_str
@@ -56,11 +54,18 @@ class hr_timesheet_report(osv.osv):
"""
return group_by_str
def _where(self):
where_str = """
WHERE aal.is_timesheet IS TRUE
"""
return where_str
def init(self, cr):
# self._table = hr_timesheet_report
tools.drop_view_if_exists(cr, self._table)
cr.execute("""CREATE or REPLACE VIEW %s as (
%s
FROM ( %s )
%s
)""" % (self._table, self._select(), self._from(), self._group_by()))
%s
%s
)""" % (self._table, self._select(), self._from(), self._where(), self._group_by()))
@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<record id="property_rule_timesheet_manager" model="ir.rule">
<field name="name">Manager HR Analytic Timesheet</field>
<field name="model_id" ref="model_hr_analytic_timesheet"/>
<field name="domain_force">[(1,'=',1)]</field>
<field name="groups" eval="[(4,ref('base.group_hr_manager')), (4,ref('base.group_hr_user'))]"/>
</record>
<record id="property_rule_timesheet_employee" model="ir.rule">
<field name="name">Employee HR Analytic Timesheet</field>
<field name="model_id" ref="model_hr_analytic_timesheet"/>
<field name="domain_force">[('user_id', '=', user.id)]</field>
<field name="groups" eval="[(4,ref('base.group_user'))]"/>
</record>
</data>
</openerp>
@@ -1,6 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_hr_analytic_timesheet,hr.analytic.timesheet,model_hr_analytic_timesheet,base.group_hr_user,1,1,1,1
access_hr_analytic_timesheet_employee,hr.analytic.timesheet,model_hr_analytic_timesheet,base.group_user,1,1,1,1
access_hr_account_analytic_line,account.account.analytic.line,account.model_account_analytic_line,base.group_hr_user,1,1,1,1
access_hr_account_analytic_line_employee,account.account.analytic.line employee,account.model_account_analytic_line,base.group_user,1,1,1,1
access_account_analytic_journal,account.account.analytic.journal,analytic.model_account_analytic_journal,base.group_hr_user,1,1,1,1
@@ -8,6 +6,6 @@ access_product_product_user,product.product user,product.model_product_product,b
access_product_template_hr_timesheet,product.template.hr.timesheet,product.model_product_template,base.group_hr_user,1,1,1,1
access_product_uom_hr_timesheet,product.uom.hr.timesheet,product.model_product_uom,base.group_hr_user,1,1,1,1
access_account_fiscalyear_hr_user,account.account.fiscalyear.user,account.model_account_fiscalyear,base.group_hr_user,1,1,1,1
access_hr_analytic_timesheet_user,hr.analytic.timesheet.user,model_hr_analytic_timesheet,base.group_user,1,1,1,0
access_account_analytic_line_user,account.analytic.line.user,model_account_analytic_line,base.group_user,1,1,1,0
access_account_analytic_journal_user,account.account.analytic.journal.user,analytic.model_account_analytic_journal,base.group_user,1,0,0,0
access_hr_timesheet_report,hr.timesheet.report,model_hr_timesheet_report,base.group_hr_manager,1,1,0,0
access_hr_timesheet_report,hr.timesheet.report,model_hr_timesheet_report,base.group_hr_manager,1,1,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
access_hr_analytic_timesheet hr.analytic.timesheet model_hr_analytic_timesheet base.group_hr_user 1 1 1 1
access_hr_analytic_timesheet_employee hr.analytic.timesheet model_hr_analytic_timesheet base.group_user 1 1 1 1
2 access_hr_account_analytic_line account.account.analytic.line account.model_account_analytic_line base.group_hr_user 1 1 1 1
3 access_hr_account_analytic_line_employee account.account.analytic.line employee account.model_account_analytic_line base.group_user 1 1 1 1
4 access_account_analytic_journal account.account.analytic.journal analytic.model_account_analytic_journal base.group_hr_user 1 1 1 1
6 access_product_template_hr_timesheet product.template.hr.timesheet product.model_product_template base.group_hr_user 1 1 1 1
7 access_product_uom_hr_timesheet product.uom.hr.timesheet product.model_product_uom base.group_hr_user 1 1 1 1
8 access_account_fiscalyear_hr_user account.account.fiscalyear.user account.model_account_fiscalyear base.group_hr_user 1 1 1 1
9 access_hr_analytic_timesheet_user access_account_analytic_line_user hr.analytic.timesheet.user account.analytic.line.user model_hr_analytic_timesheet model_account_analytic_line base.group_user 1 1 1 0
10 access_account_analytic_journal_user account.account.analytic.journal.user analytic.model_account_analytic_journal base.group_user 1 0 0 0
11 access_hr_timesheet_report hr.timesheet.report model_hr_timesheet_report base.group_hr_manager 1 1 0 0
@@ -1,11 +0,0 @@
-
Give the access rights of Hr Officer to create employee.
-
!context
uid: 'res_hr_timesheet_officer'
-
!record {model: hr.analytic.timesheet, id: working_hours_coding, view: False}:
user_id: base.user_demo
date: !eval time.strftime('%Y-%m-%d')
account_id: analytic.analytic_administratif
unit_amount: 1.0
+2 -1
View File
@@ -34,7 +34,8 @@ You can define price lists in analytic account, make some theoretical revenue
reports.""",
'author': 'OpenERP SA',
'website': 'https://www.odoo.com/page/employees',
'depends': ['account', 'hr_timesheet', 'report'],
'images': ['images/hr_bill_task_work.jpeg','images/hr_type_of_invoicing.jpeg'],
'depends': ['hr_timesheet', 'report'],
'data': [
'security/ir.model.access.csv',
'hr_timesheet_invoice_data.xml',
@@ -39,38 +39,13 @@ class hr_timesheet_invoice_factor(osv.osv):
}
class account_analytic_account(osv.osv):
def _invoiced_calc(self, cr, uid, ids, name, arg, context=None):
obj_invoice = self.pool.get('account.invoice')
res = {}
cr.execute('SELECT account_id as account_id, l.invoice_id '
'FROM hr_analytic_timesheet h LEFT JOIN account_analytic_line l '
'ON (h.line_id=l.id) '
'WHERE l.account_id = ANY(%s)', (ids,))
account_to_invoice_map = {}
for rec in cr.dictfetchall():
account_to_invoice_map.setdefault(rec['account_id'], []).append(rec['invoice_id'])
for account in self.browse(cr, uid, ids, context=context):
invoice_ids = filter(None, list(set(account_to_invoice_map.get(account.id, []))))
for invoice in obj_invoice.browse(cr, uid, invoice_ids, context=context):
res.setdefault(account.id, 0.0)
res[account.id] += invoice.amount_untaxed
for id in ids:
res[id] = round(res.get(id, 0.0),2)
return res
_inherit = "account.analytic.account"
_columns = {
'pricelist_id': fields.many2one('product.pricelist', 'Pricelist',
help="The product to invoice is defined on the employee form, the price will be deducted by this pricelist on the product."),
'amount_max': fields.float('Max. Invoice Price',
help="Keep empty if this contract is not limited to a total fixed price."),
'amount_invoiced': fields.function(_invoiced_calc, string='Invoiced Amount',
help="Total invoiced"),
'to_invoice': fields.many2one('hr_timesheet_invoice.factor', 'Timesheet Invoicing Ratio',
help="You usually invoice 100% of the timesheets. But if you mix fixed price and timesheet invoicing, you may use another ratio. For instance, if you do a 20% advance invoice (fixed price, based on a sales order), you should invoice the rest on timesheet with a 80% ratio."),
}
@@ -78,7 +53,7 @@ class account_analytic_account(osv.osv):
_defaults = {
'pricelist_id': lambda self, cr, uid, c: self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'product.list0')
}
def on_change_partner_id(self, cr, uid, ids, partner_id, name, context=None):
res = super(account_analytic_account, self).on_change_partner_id(cr, uid, ids, partner_id, name, context=context)
if partner_id:
@@ -108,14 +83,6 @@ class account_analytic_line(osv.osv):
'to_invoice': fields.many2one('hr_timesheet_invoice.factor', 'Invoiceable', help="It allows to set the discount while making invoice, keep empty if the activities should not be invoiced."),
}
def _default_journal(self, cr, uid, context=None):
proxy = self.pool.get('hr.employee')
record_ids = proxy.search(cr, uid, [('user_id', '=', uid)], context=context)
if record_ids:
employee = proxy.browse(cr, uid, record_ids[0], context=context)
return employee.journal_id and employee.journal_id.id or False
return False
def _default_general_account(self, cr, uid, context=None):
proxy = self.pool.get('hr.employee')
record_ids = proxy.search(cr, uid, [('user_id', '=', uid)], context=context)
@@ -126,30 +93,34 @@ class account_analytic_line(osv.osv):
return False
_defaults = {
'journal_id' : _default_journal,
'general_account_id' : _default_general_account,
'general_account_id': _default_general_account,
}
def write(self, cr, uid, ids, vals, context=None):
self._check_inv(cr, uid, ids, vals)
return super(account_analytic_line,self).write(cr, uid, ids, vals,
return super(account_analytic_line, self).write(cr, uid, ids, vals,
context=context)
def _check_inv(self, cr, uid, ids, vals):
def unlink(self, cr, uid, ids, context=None):
if any(line.invoice_id.id for line in self.browse(cr, uid, ids, context)):
raise UserError(_('You cannot delete an invoiced analytic line!'))
return super(account_analytic_line, self).unlink(cr, uid, ids, context)
def _check_inv(self, cr, uid, ids, vals, context=None):
select = ids
if isinstance(select, (int, long)):
select = [ids]
if ( not vals.has_key('invoice_id')) or vals['invoice_id' ] == False:
for line in self.browse(cr, uid, select):
if (not vals.has_key('invoice_id')) or vals['invoice_id'] == False:
for line in self.browse(cr, uid, select, context):
if line.invoice_id:
raise UserError(_('You cannot modify an invoiced analytic line!'))
return True
def _get_invoice_price(self, cr, uid, account, product_id, user_id, qty, context = {}):
def _get_invoice_price(self, cr, uid, account, product_id, user_id, qty, context={}):
pro_price_obj = self.pool.get('product.pricelist')
if account.pricelist_id:
pl = account.pricelist_id.id
price = pro_price_obj.price_get(cr,uid,[pl], product_id, qty or 1.0, account.partner_id.id, context=context)[pl]
price = pro_price_obj.price_get(cr, uid, [pl], product_id, qty or 1.0, account.partner_id.id, context=context)[pl]
else:
price = 0.0
return price
@@ -337,24 +308,22 @@ class account_analytic_line(osv.osv):
invoice_obj.button_reset_taxes(cr, uid, [last_invoice], context)
return invoices
class hr_analytic_timesheet(osv.osv):
_inherit = "hr.analytic.timesheet"
def on_change_account_id(self, cr, uid, ids, account_id, user_id=False):
res = {}
if not account_id:
return res
res.setdefault('value',{})
acc = self.pool.get('account.analytic.account').browse(cr, uid, account_id)
st = acc.to_invoice.id
res['value']['to_invoice'] = st or False
if acc.state=='pending':
res['warning'] = {
'title': 'Warning',
'message': 'The analytic account is in pending state.\nYou should not work on this account !'
}
def on_change_account_id(self, cr, uid, ids, account_id, user_id=False, is_timesheet=False, context=None):
res = {'value': {}}
if is_timesheet and account_id:
acc = self.pool.get('account.analytic.account').browse(cr, uid, account_id, context=context)
st = acc.to_invoice.id
res['value']['to_invoice'] = st or False
if acc.state == 'pending':
res['warning'] = {
'title': 'Warning',
'message': 'The analytic account is in pending state.\nYou should not work on this account !'
}
elif acc.state == 'close' or acc.state == 'cancelled':
raise osv.except_osv(_('Invalid Analytic Account!'), _('You cannot select a Analytic Account which is in Close or Cancelled state.'))
return res
class account_invoice(osv.osv):
_inherit = "account.invoice"
@@ -366,23 +335,22 @@ class account_invoice(osv.osv):
obj_analytic_account = self.pool.get('account.analytic.account')
for il in iml:
if il['account_analytic_id']:
# *-* browse (or refactor to avoid read inside the loop)
# *-* browse (or refactor to avoid read inside the loop)
to_invoice = obj_analytic_account.read(cr, uid, [il['account_analytic_id']], ['to_invoice'], context=context)[0]['to_invoice']
if to_invoice:
il['analytic_lines'][0][2]['to_invoice'] = to_invoice[0]
return iml
class account_move_line(osv.osv):
_inherit = "account.move.line"
def create_analytic_lines(self, cr, uid, ids, context=None):
res = super(account_move_line, self).create_analytic_lines(cr, uid, ids,context=context)
res = super(account_move_line, self).create_analytic_lines(cr, uid, ids, context=context)
analytic_line_obj = self.pool.get('account.analytic.line')
for move_line in self.browse(cr, uid, ids, context=context):
#For customer invoice, link analytic line to the invoice so it is not proposed for invoicing in Bill Tasks Work
invoice_id = move_line.invoice and move_line.invoice.type in ('out_invoice','out_refund') and move_line.invoice.id or False
# For customer invoice, link analytic line to the invoice so it is not proposed for invoicing in Bill Tasks Work
invoice_id = move_line.invoice and move_line.invoice.type in ('out_invoice', 'out_refund') and move_line.invoice.id or False
for line in move_line.analytic_lines:
analytic_line_obj.write(cr, uid, line.id, {
'invoice_id': invoice_id,
@@ -12,7 +12,7 @@
<field name="pricelist_id" groups="product.group_sale_pricelist"/>
<field name="to_invoice"/>
</group>
</xpath>
</xpath>
<xpath expr="/form/sheet" position='before'>
<header>
<button name="set_pending" string="To Renew" type="object" states="open" />
@@ -21,111 +21,59 @@
<button name="set_cancel" string="Cancel Contract" type="object" states="open,pending"/>
<field name="state" readonly="1" widget="statusbar"
statusbar_visible="open,pending,close" statusbar_colors='{"pending":"red", "template":"blue"}'/>
</header>
</xpath>
</field>
</record>
</header>
</xpath>
</field>
</record>
<record id="hr_timesheet_line_form" model="ir.ui.view">
<field name="name">hr.analytic.timesheet.form</field>
<field name="model">hr.analytic.timesheet</field>
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_form"/>
<field name="arch" type="xml">
<xpath expr="//group[@string='Accounting']" position='after'>
<group string="Invoicing">
<record id="hr_timesheet_line_form" model="ir.ui.view">
<field name="name">account.analytic.line.form</field>
<field name="model">account.analytic.line</field>
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_form"/>
<field name="arch" type="xml">
<xpath expr="//group[@string='Accounting']" position='after'>
<group string="Invoicing">
<field name="to_invoice"/>
<field name="invoice_id" readonly="1"/>
</group>
</xpath>
<field name="account_id" position="attributes">
<attribute name="domain">[('type','in',['normal','contract']),('state', '!=', 'close'),('invoice_on_timesheets','=',1)]</attribute>
<attribute name="on_change">on_change_account_id(account_id, user_id, is_timesheet, context)</attribute>
<attribute name="context">{'default_invoice_on_timesheets': 1}</attribute>
</field>
</field>
</record>
<record id="hr_timesheet_line_tree" model="ir.ui.view">
<field name="name">account.analytic.line.tree</field>
<field name="model">account.analytic.line</field>
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_tree"/>
<field name="arch" type="xml">
<field name="amount" position="after">
<field name="to_invoice"/>
<field name="invoice_id"/>
</group>
</xpath>
</field>
</record>
<record id="hr_timesheet_line_form2" model="ir.ui.view">
<field name="name">hr.analytic.timesheet.form2</field>
<field name="model">hr.analytic.timesheet</field>
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_form"/>
<field name="arch" type="xml">
<field name="account_id" position="attributes">
<attribute name="domain">[('type','in',['normal','contract']),('state', '!=', 'close'),('invoice_on_timesheets','=',1)]</attribute>
<attribute name="on_change">on_change_account_id(account_id, user_id)</attribute>
<attribute name="context">{'default_invoice_on_timesheets': 1}</attribute>
<field name="invoice_id" readonly="1"/>
</field>
<field name="account_id" position="attributes">
<attribute name="domain">[('type','in',['normal','contract']),('state', '!=', 'close'),('invoice_on_timesheets','=',1)]</attribute>
<attribute name="on_change">on_change_account_id(account_id, user_id, is_timesheet, context)</attribute>
<attribute name="context">{'default_invoice_on_timesheets': 1}</attribute>
</field>
</field>
</field>
</record>
</record>
<record id="hr_timesheet_line_tree" model="ir.ui.view">
<field name="name">hr.analytic.timesheet.tree</field>
<field name="model">hr.analytic.timesheet</field>
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_tree"/>
<field name="arch" type="xml">
<field name="amount" position="after">
<field name="to_invoice"/>
<record id="account_analytic_line_view_search_to_invoice" model="ir.ui.view">
<field name="name">account.analytic.line.search.to_invoice</field>
<field name="model">account.analytic.line</field>
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_search"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='account_id']" position="after">
<separator/>
<filter name="to_invoice" string="To Invoice" context="{'to_invoice': 1}" domain="[('invoice_id','=',False), ('to_invoice','!=',False)]" icon="terp-dolar"/>
<filter name="invoiced" string="Invoiced" domain="[('invoice_id','!=',False), ('to_invoice','!=',False)]" icon="terp-dolar"/>
</xpath>
</field>
</field>
</record>
<record id="hr_timesheet_line_tree2" model="ir.ui.view">
<field name="name">hr.analytic.timesheet.tree2</field>
<field name="model">hr.analytic.timesheet</field>
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_tree"/>
<field name="arch" type="xml">
<field name="account_id" position="attributes">
<attribute name="domain">[('type','in',['normal','contract']),('state', '!=', 'close'),('invoice_on_timesheets','=',1)]</attribute>
<attribute name="on_change">on_change_account_id(account_id, user_id)</attribute>
<attribute name="context">{'default_invoice_on_timesheets': 1}</attribute>
</field>
</field>
</record>
<!-- Inherits for account analytic lines -->
<record id="view_account_analytic_line_search_inherit" model="ir.ui.view">
<field name="name">account.analytic.line.search.to_invoice</field>
<field name="model">account.analytic.line</field>
<field name="inherit_id" ref="analytic.view_account_analytic_line_filter"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='date']" position="after">
<separator/>
<filter name="to_invoice" string="To Invoice" context="{'to_invoice': 1}" domain="[('invoice_id','=',False), ('to_invoice','&lt;&gt;',False)]" icon="terp-dolar"/>
<filter name="invoiced" string="Invoiced" domain="[('invoice_id','!=',False), ('to_invoice','&lt;&gt;',False)]" icon="terp-dolar"/>
</xpath>
</field>
</record>
<record id="view_hr_timesheet_line_search_to_invoice" model="ir.ui.view">
<field name="name">hr.analytic.timesheet.search.to_invoice</field>
<field name="model">hr.analytic.timesheet</field>
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_search"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='account_id']" position="after">
<separator/>
<filter name="to_invoice" string="To Invoice" context="{'to_invoice': 1}" domain="[('invoice_id','=',False), ('to_invoice','&lt;&gt;',False)]" icon="terp-dolar"/>
<filter name="invoiced" string="Invoiced" domain="[('invoice_id','!=',False), ('to_invoice','&lt;&gt;',False)]" icon="terp-dolar"/>
</xpath>
</field>
</record>
<record id="view_account_analytic_line_tree_inherit" model="ir.ui.view">
<field name="name">account.analytic.line.tree.to_invoice</field>
<field name="model">account.analytic.line</field>
<field name="inherit_id" ref="analytic.view_account_analytic_line_tree"/>
<field name="arch" type="xml">
<field name="account_id" position="after">
<field name="to_invoice"/>
</field>
</field>
</record>
<record id="view_account_analytic_line_form_inherit" model="ir.ui.view">
<field name="name">account.analytic.line.form.to_invoice</field>
<field name="model">account.analytic.line</field>
<field name="inherit_id" ref="analytic.view_account_analytic_line_form"/>
<field name="arch" type="xml">
<field name="amount" position="before">
<field name="to_invoice"/>
<field name="invoice_id"/>
</field>
</field>
</record>
</record>
<record id="view_hr_timesheet_invoice_factor_search" model="ir.ui.view">
<field name="name">hr_timesheet_invoice.factor.search</field>
@@ -163,6 +111,7 @@
<record id="action_hr_timesheet_invoice_factor_form" model="ir.actions.act_window">
<field name="name">Invoicing Types</field>
<field name="type">ir.actions.act_window</field>
<field name="context">{"default_is_timesheet":1}</field>
<field name="res_model">hr_timesheet_invoice.factor</field>
<field name="view_type">form</field>
<field name="view_id" ref="view_hr_timesheet_invoice_factor_tree"/>
@@ -170,19 +119,19 @@
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to add a new type of invoicing.
</p><p>
Odoo allows you to create default invoicing types. You might
have to regularly assign discounts because of a specific
contract or agreement with a customer. From this menu, you can
create additional types of invoicing to speed up your
invoicing.
</p>
</field>
</record>
<menuitem
action="action_hr_timesheet_invoice_factor_form"
id="hr_timesheet_invoice_factor_view"
parent="account.menu_analytic_accounting" sequence="99" groups="base.group_no_one"/>
</p><p>
Odoo allows you to create default invoicing types. You might
have to regularly assign discounts because of a specific
contract or agreement with a customer. From this menu, you can
create additional types of invoicing to speed up your
invoicing.
</p>
</field>
</record>
<menuitem
action="action_hr_timesheet_invoice_factor_form"
id="hr_timesheet_invoice_factor_view"
parent="account.menu_analytic_accounting" sequence="99" groups="base.group_no_one"/>
<record id="hr_timesheet.act_hr_timesheet_accounts_form" model="ir.actions.act_window">
<field name="context">{'invoice_on_timesheets':1, 'default_type': 'contract'}</field>
@@ -28,7 +28,7 @@
-
I assign account on analytic account line.
-
!python {model: hr.analytic.timesheet}: |
!python {model: account.analytic.line}: |
self.on_change_account_id(cr, uid, [ref('account_analytic_line_developyamlforhrmodule0')], ref('analytic.analytic_agrolait'))
-
@@ -28,7 +28,7 @@
-
I assign account on analytic account line.
-
!python {model: hr.analytic.timesheet}: |
!python {model: account.analytic.line}: |
self.on_change_account_id(cr, uid, [ref('account_analytic_line_developyamlforhrmodule1')], ref('analytic.analytic_agrolait'))
-
I create a Tax Code
+42 -62
View File
@@ -139,16 +139,12 @@ class hr_timesheet_sheet(osv.osv):
if sheet.employee_id.id not in employee_ids: employee_ids.append(sheet.employee_id.id)
return hr_employee.attendance_action_change(cr, uid, employee_ids, context=context)
def _count_all(self, cr, uid, ids, field_name, arg, context=None):
Timesheet = self.pool['hr.analytic.timesheet']
Attendance = self.pool['hr.attendance']
return {
sheet_id: {
'timesheet_activity_count': Timesheet.search_count(cr,uid, [('sheet_id','=', sheet_id)], context=context),
'attendance_count': Attendance.search_count(cr,uid, [('sheet_id', '=', sheet_id)], context=context)
}
for sheet_id in ids
}
def _count_attendances(self, cr, uid, ids, field_name, arg, context=None):
res = dict.fromkeys(ids, 0)
attendances_groups = self.pool['hr.attendance'].read_group(cr, uid, [('sheet_id' , 'in' , ids)], ['sheet_id'], 'sheet_id', context=context)
for attendances in attendances_groups:
res[attendances['sheet_id'][0]] = attendances['sheet_id_count']
return res
_columns = {
'name': fields.char('Note', select=1,
@@ -157,7 +153,7 @@ class hr_timesheet_sheet(osv.osv):
'user_id': fields.related('employee_id', 'user_id', type="many2one", relation="res.users", store=True, string="User", required=False, readonly=True),#fields.many2one('res.users', 'User', required=True, select=1, states={'confirm':[('readonly', True)], 'done':[('readonly', True)]}),
'date_from': fields.date('Date from', required=True, select=1, readonly=True, states={'new':[('readonly', False)]}),
'date_to': fields.date('Date to', required=True, select=1, readonly=True, states={'new':[('readonly', False)]}),
'timesheet_ids' : fields.one2many('hr.analytic.timesheet', 'sheet_id',
'timesheet_ids' : fields.one2many('account.analytic.line', 'sheet_id',
'Timesheet lines',
readonly=True, states={
'draft': [('readonly', False)],
@@ -181,8 +177,7 @@ class hr_timesheet_sheet(osv.osv):
'account_ids': fields.one2many('hr_timesheet_sheet.sheet.account', 'sheet_id', 'Analytic accounts', readonly=True),
'company_id': fields.many2one('res.company', 'Company'),
'department_id':fields.many2one('hr.department','Department'),
'timesheet_activity_count': fields.function(_count_all, type='integer', string='Timesheet Activities', multi=True),
'attendance_count': fields.function(_count_all, type='integer', string="Attendances", multi=True),
'attendance_count': fields.function(_count_attendances, type='integer', string="Attendances"),
}
def _default_date_from(self, cr, uid, context=None):
@@ -260,7 +255,7 @@ class hr_timesheet_sheet(osv.osv):
raise UserError(_('You cannot delete a timesheet which have attendance entries.'))
toremove = []
analytic_timesheet = self.pool.get('hr.analytic.timesheet')
analytic_timesheet = self.pool.get('account.analytic.line')
for sheet in self.browse(cr, uid, ids, context=context):
for timesheet in sheet.timesheet_ids:
toremove.append(timesheet.id)
@@ -298,26 +293,9 @@ class hr_timesheet_sheet(osv.osv):
return dom
class account_analytic_line(osv.osv):
_inherit = "account.analytic.line"
def _get_default_date(self, cr, uid, context=None):
if context is None:
context = {}
#get the default date (should be: today)
res = super(account_analytic_line, self)._get_default_date(cr, uid, context=context)
#if we got the dates from and to from the timesheet and if the default date is in between, we use the default
#but if the default isn't included in those dates, we use the date start of the timesheet as default
if context.get('timesheet_date_from') and context.get('timesheet_date_to'):
if context['timesheet_date_from'] <= res <= context['timesheet_date_to']:
return res
return context.get('timesheet_date_from')
#if we don't get the dates from the timesheet, we return the default value from super()
return res
class account_analytic_account(osv.osv):
_inherit = "account.analytic.account"
def name_create(self, cr, uid, name, context=None):
if context is None:
context = {}
@@ -326,9 +304,24 @@ class account_analytic_account(osv.osv):
return super(account_analytic_account, self).name_create(cr, uid, name, context=context)
rec_id = self.create(cr, uid, {self._rec_name: name}, context)
return self.name_get(cr, uid, [rec_id], context)[0]
class hr_timesheet_line(osv.osv):
_inherit = "hr.analytic.timesheet"
class account_analytic_line(osv.osv):
_inherit = "account.analytic.line"
def _get_default_date(self, cr, uid, context=None):
if context is None:
context = {}
# get the default date (should be: today)
res = super(account_analytic_line, self)._get_default_date(cr, uid, context=context)
# if we got the dates from and to from the timesheet and if the default date is in between, we use the default
# but if the default isn't included in those dates, we use the date start of the timesheet as default
if context.get('timesheet_date_from') and context.get('timesheet_date_to'):
if context['timesheet_date_from'] <= res <= context['timesheet_date_to']:
return res
return context.get('timesheet_date_from')
# if we don't get the dates from the timesheet, we return the default value from super()
return res
def _sheet(self, cursor, user, ids, name, args, context=None):
sheet_obj = self.pool.get('hr_timesheet_sheet.sheet')
@@ -349,29 +342,22 @@ class hr_timesheet_line(osv.osv):
for ts in self.browse(cr, uid, ids, context=context):
cr.execute("""
SELECT l.id
FROM hr_analytic_timesheet l
INNER JOIN account_analytic_line al
ON (l.line_id = al.id)
WHERE %(date_to)s >= al.date
AND %(date_from)s <= al.date
AND %(user_id)s = al.user_id
FROM account_analytic_line l
WHERE %(date_to)s >= l.date
AND %(date_from)s <= l.date
AND %(user_id)s = l.user_id
GROUP BY l.id""", {'date_from': ts.date_from,
'date_to': ts.date_to,
'user_id': ts.employee_id.user_id.id,})
ts_line_ids.extend([row[0] for row in cr.fetchall()])
return ts_line_ids
def _get_account_analytic_line(self, cr, uid, ids, context=None):
ts_line_ids = self.pool.get('hr.analytic.timesheet').search(cr, uid, [('line_id', 'in', ids)])
return ts_line_ids
_columns = {
'sheet_id': fields.function(_sheet, string='Sheet', select="1",
type='many2one', relation='hr_timesheet_sheet.sheet', ondelete="cascade",
store={
'hr_timesheet_sheet.sheet': (_get_hr_timesheet_sheet, ['employee_id', 'date_from', 'date_to'], 10),
'account.analytic.line': (_get_account_analytic_line, ['user_id', 'date'], 10),
'hr.analytic.timesheet': (lambda self,cr,uid,ids,context=None: ids, None, 10),
'account.analytic.line': (lambda self,cr,uid,ids,context=None: ids, ['user_id', 'date'], 10),
},
),
}
@@ -380,13 +366,13 @@ class hr_timesheet_line(osv.osv):
if isinstance(ids, (int, long)):
ids = [ids]
self._check(cr, uid, ids)
return super(hr_timesheet_line, self).write(cr, uid, ids, values, context=context)
return super(account_analytic_line, self).write(cr, uid, ids, values,context=context)
def unlink(self, cr, uid, ids, *args, **kwargs):
if isinstance(ids, (int, long)):
ids = [ids]
self._check(cr, uid, ids)
return super(hr_timesheet_line,self).unlink(cr, uid, ids,*args, **kwargs)
return super(account_analytic_line,self).unlink(cr, uid, ids,*args, **kwargs)
def _check(self, cr, uid, ids):
for att in self.browse(cr, uid, ids):
@@ -395,8 +381,7 @@ class hr_timesheet_line(osv.osv):
return True
def multi_on_change_account_id(self, cr, uid, ids, account_ids, context=None):
return dict([(el, self.on_change_account_id(cr, uid, ids, el, context.get('user_id', uid))) for el in account_ids])
return dict([(account_id, self.on_change_account_id(cr, uid, ids, account_id, user_id=context.get('user_id', uid), is_timesheet=True, context=context)) for account_id in account_ids])
class hr_attendance(osv.osv):
@@ -550,7 +535,6 @@ class hr_timesheet_sheet_sheet_day(osv.osv):
}
_depends = {
'account.analytic.line': ['date', 'unit_amount'],
'hr.analytic.timesheet': ['line_id', 'sheet_id'],
'hr.attendance': ['action', 'name', 'sheet_id'],
}
@@ -583,7 +567,7 @@ class hr_timesheet_sheet_sheet_day(osv.osv):
FROM
((
select
min(hrt.id) as id,
min(l.id) as id,
p.tz as timezone,
l.date::date as name,
s.id as sheet_id,
@@ -591,9 +575,8 @@ class hr_timesheet_sheet_sheet_day(osv.osv):
0 as orphan_attendances,
0.0 as total_attendance
from
hr_analytic_timesheet hrt
JOIN account_analytic_line l ON l.id = hrt.line_id
LEFT JOIN hr_timesheet_sheet_sheet s ON s.id = hrt.sheet_id
account_analytic_line l
LEFT JOIN hr_timesheet_sheet_sheet s ON s.id = l.sheet_id
JOIN hr_employee e ON s.employee_id = e.id
JOIN resource_resource r ON e.resource_id = r.id
LEFT JOIN res_users u ON r.user_id = u.id
@@ -642,26 +625,23 @@ class hr_timesheet_sheet_sheet_account(osv.osv):
_depends = {
'account.analytic.line': ['account_id', 'date', 'to_invoice', 'unit_amount', 'user_id'],
'hr.analytic.timesheet': ['line_id'],
'hr_timesheet_sheet.sheet': ['date_from', 'date_to', 'user_id'],
}
def init(self, cr):
cr.execute("""create or replace view hr_timesheet_sheet_sheet_account as (
select
min(hrt.id) as id,
min(l.id) as id,
l.account_id as name,
s.id as sheet_id,
sum(l.unit_amount) as total,
l.to_invoice as invoice_rate
from
hr_analytic_timesheet hrt
left join (account_analytic_line l
account_analytic_line l
LEFT JOIN hr_timesheet_sheet_sheet s
ON (s.date_to >= l.date
AND s.date_from <= l.date
AND s.user_id = l.user_id))
on (l.id = hrt.line_id)
AND s.user_id = l.user_id)
group by l.account_id, s.id, l.to_invoice
)""")
@@ -42,9 +42,9 @@
<record model="ir.actions.act_window" id="act_hr_timesheet_sheet_sheet_2_hr_analytic_timesheet">
<field name="context">{'search_default_sheet_id': [active_id]}</field>
<field name="context">{'search_default_sheet_id': [active_id], 'default_is_timesheet' : 1}</field>
<field name="name">Timesheet Activities</field>
<field name="res_model">hr.analytic.timesheet</field>
<field name="res_model">account.analytic.line</field>
<field name="src_model">hr_timesheet_sheet.sheet</field>
</record>
@@ -56,6 +56,23 @@
<field name="src_model">hr_timesheet_sheet.sheet</field>
</record>
<record id="hr_timesheet_sheet_tree_simplified" model="ir.ui.view">
<field name="name">hr.timesheet.sheet.tree</field>
<field name="model">hr_timesheet_sheet.sheet</field>
<field name="arch" type="xml">
<tree colors="blue:state == 'draft';black:state in ('confirm','new');gray:state == 'done'" fonts="bold: message_unread == True" string="Timesheets">
<field name="employee_id"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="department_id" invisible="1"/>
<field name="total_attendance" widget="float_time" groups="base.group_hr_attendance"/>
<field name="total_timesheet" widget="float_time"/>
<field name="total_difference" widget="float_time" groups="base.group_hr_attendance"/>
<field name="state"/>
<field name="message_unread" invisible="1"/>
</tree>
</field>
</record>
<record id="hr_timesheet_sheet_form" model="ir.ui.view">
<field name="name">hr.timesheet.sheet.form</field>
@@ -107,10 +124,10 @@
</widget>
</page>
<page string="Details">
<field context="{'employee_id': employee_id, 'user_id':user_id, 'timesheet_date_from': date_from, 'timesheet_date_to': date_to}" name="timesheet_ids" nolabel="1">
<field context="{'employee_id': employee_id, 'user_id':user_id, 'timesheet_date_from': date_from, 'timesheet_date_to': date_to, 'default_is_timesheet' : 1}" name="timesheet_ids" nolabel="1">
<tree editable="top" string="Timesheet Activities">
<field name="date"/>
<field domain="[('type','in',['normal', 'contract']), ('state', '&lt;&gt;', 'close'),('invoice_on_timesheets','=',1)]" name="account_id" on_change="on_change_account_id(account_id, user_id)" context="{'default_use_timesheets': 1}"/>
<field domain="[('type','in',['normal', 'contract']), ('state', '!=', 'close'),('invoice_on_timesheets','=',1)]" name="account_id" on_change="on_change_account_id(account_id, user_id)" options="{'no_open': True, 'no_create': True}" context="{'default_use_timesheets': 1}"/>
<field name="name"/>
<field name="unit_amount" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)" widget="float_time" string="Hours" sum="Hours"/>
<field name="to_invoice" widget="selection"/>
@@ -120,10 +137,11 @@
<field invisible="1" name="amount"/>
<field invisible="1" name="general_account_id"/>
<field invisible="1" name="user_id" required="1"/>
<field name="is_timesheet" invisible="1"/>
</tree>
<form string="Timesheet Activities">
<field name="date"/>
<field domain="[('type','=','normal'), ('state', '&lt;&gt;', 'close')]" name="account_id" on_change="on_change_account_id(account_id, user_id)"/>
<field domain="[('type','=','normal'), ('state', '!=', 'close')]" name="account_id" on_change="on_change_account_id(account_id, user_id)"/>
<field name="name"/>
<field name="unit_amount" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)" widget="float_time"/>
<field name="to_invoice"/>
@@ -133,6 +151,7 @@
<field name="amount"/>
<field name="general_account_id"/>
<field name="user_id" required="1"/>
<field name="is_timesheet" invisible="1"/>
</form>
</field>
</page>
@@ -198,7 +217,7 @@
<field name="res_model">hr_timesheet_sheet.sheet</field>
<field name="view_type">form</field>
<field name="view_id" eval="False"/>
<field name="context">{'search_default_to_approve':1}</field>
<field name="context">{'search_default_to_approve':1, 'default_is_timesheet':1}</field>
<field name="search_view_id" ref="view_hr_timesheet_sheet_filter"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
@@ -214,9 +233,15 @@
</field>
</record>
<menuitem id="menu_timesheet_approvals" name="Approvals" parent="hr_attendance.timesheet_menu_root" sequence="6" groups="base.group_hr_user"/>
<menuitem action="act_hr_timesheet_sheet_form" id="menu_act_hr_timesheet_sheet_form" parent="menu_timesheet_approvals"
sequence="11" />
<record model="ir.actions.act_window.view" id="action_open_timesheet_sheet_holidays_tree">
<field name="sequence" eval="2"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="hr_timesheet_sheet_tree_simplified"/>
<field name="act_window_id" ref="act_hr_timesheet_sheet_form"/>
</record>
<menuitem action="act_hr_timesheet_sheet_form" id="menu_act_hr_timesheet_sheet_form" parent="hr_attendance.menu_hr_time_tracking"
sequence="11" groups="base.group_hr_user"/>
<!--
Company inheritancy
@@ -237,12 +262,12 @@
</record>
<!--
hr.analytic.timesheet inheritancy
account.analytic.line inheritancy
-->
<record id="hr_timesheet_line_form" model="ir.ui.view">
<field name="name">hr.analytic.timesheet.form</field>
<field name="model">hr.analytic.timesheet</field>
<field name="name">account.analytic.line.form</field>
<field name="model">account.analytic.line</field>
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_form"/>
<field name="arch" type="xml">
<field name="date" position="after">
@@ -252,8 +277,8 @@
</record>
<record id="hr_timesheet_line_search" model="ir.ui.view">
<field name="name">hr.analytic.timesheet.search</field>
<field name="model">hr.analytic.timesheet</field>
<field name="name">account.analytic.line.search</field>
<field name="model">account.analytic.line</field>
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_search"/>
<field name="arch" type="xml">
<field name="date" position="before">
@@ -324,24 +349,6 @@
res_model="hr_timesheet_sheet.sheet.day"
src_model="hr_timesheet_sheet.sheet"/>
<record id="hr_timesheet_sheet_tree_simplified" model="ir.ui.view">
<field name="name">hr.timesheet.sheet.tree</field>
<field name="model">hr_timesheet_sheet.sheet</field>
<field eval="10" name="priority"/>
<field name="arch" type="xml">
<tree colors="blue:state == 'draft';black:state in ('confirm','new');gray:state == 'done'" fonts="bold: message_unread == True" string="Timesheets">
<field name="employee_id"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="department_id" invisible="1"/>
<field name="total_attendance" widget="float_time" groups="base.group_hr_attendance"/>
<field name="total_timesheet" widget="float_time"/>
<field name="total_difference" widget="float_time" groups="base.group_hr_attendance"/>
<field name="state"/>
<field name="message_unread" invisible="1"/>
</tree>
</field>
</record>
<record id="hr_timesheet_day_tree" model="ir.ui.view">
<field name="name">hr.timesheet.day.tree</field>
@@ -363,7 +370,7 @@
<field name="view_type">form</field>
<field name="name">Timesheets</field>
<field name="view_mode">tree,form</field>
<field name="context">{'search_default_employee_id': [active_id], 'default_employee_id': active_id}</field>
<field name="context">{'search_default_employee_id': [active_id], 'default_employee_id': active_id , 'default_is_timesheet' : 1}</field>
</record>
<record id="hr_timesheet_sheet_employee_extd_form" model="ir.ui.view">
@@ -52,11 +52,11 @@ class hr_timesheet_report(osv.osv):
sum(total_attendance) / coalesce(sum(j.count),1) as total_attendance
FROM hr_timesheet_sheet_sheet_day d left join (
SELECT
h.sheet_id,
a.sheet_id,
a.date,
count(*)
FROM account_analytic_line a inner join hr_analytic_timesheet h ON (h.line_id=a.id)
GROUP BY h.sheet_id, a.date
FROM account_analytic_line a
GROUP BY a.sheet_id, a.date
) j ON (d.sheet_id = j.sheet_id AND d.name = j.date)
GROUP BY d.sheet_id, d.name
)
@@ -73,7 +73,7 @@ class hr_timesheet_report(osv.osv):
htss.state"""
def _from(self):
return super(hr_timesheet_report, self)._from() + "left join hr_timesheet_sheet_sheet as htss ON (hat.sheet_id=htss.id) join totals as t on (t.sheet_id = hat.sheet_id and t.date = aal.date)"
return super(hr_timesheet_report, self)._from() + "left join hr_timesheet_sheet_sheet as htss ON (aal.sheet_id=htss.id) join totals as t on (t.sheet_id = aal.sheet_id and t.date = aal.date)"
def _group_by(self):
return super(hr_timesheet_report, self)._group_by() + """,
@@ -3,5 +3,4 @@ access_hr_timesheet_sheet_sheet_user,hr_timesheet_sheet.sheet.user,model_hr_time
access_hr_timesheet_sheet_sheet_system_employee,hr_timesheet_sheet.sheet.system.employee,model_hr_timesheet_sheet_sheet,base.group_user,1,1,1,0
access_hr_timesheet_sheet_sheet_day,hr_timesheet_sheet.sheet.day,model_hr_timesheet_sheet_sheet_day,base.group_hr_user,1,1,1,1
access_hr_timesheet_sheet_sheet_account,hr_timesheet_sheet.sheet.account,model_hr_timesheet_sheet_sheet_account,base.group_hr_user,1,1,1,1
access_hr_analytic_timesheet_system_user,hr.analytic.timesheet.system.user,model_hr_analytic_timesheet,base.group_user,1,0,0,0
access_hr_timesheet_sheet_sheet_day,hr.timesheet.sheet.sheet.day.user,model_hr_timesheet_sheet_sheet_day,base.group_user,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
3 access_hr_timesheet_sheet_sheet_system_employee hr_timesheet_sheet.sheet.system.employee model_hr_timesheet_sheet_sheet base.group_user 1 1 1 0
4 access_hr_timesheet_sheet_sheet_day hr_timesheet_sheet.sheet.day model_hr_timesheet_sheet_sheet_day base.group_hr_user 1 1 1 1
5 access_hr_timesheet_sheet_sheet_account hr_timesheet_sheet.sheet.account model_hr_timesheet_sheet_sheet_account base.group_hr_user 1 1 1 1
access_hr_analytic_timesheet_system_user hr.analytic.timesheet.system.user model_hr_analytic_timesheet base.group_user 1 0 0 0
6 access_hr_timesheet_sheet_sheet_day hr.timesheet.sheet.sheet.day.user model_hr_timesheet_sheet_sheet_day base.group_user 1 1 1 0
@@ -107,9 +107,9 @@ var WeeklyTimesheet = form_common.FormWidget.extend(form_common.ReinitializeWidg
var accounts;
var account_names;
var default_get;
return this.render_drop.add(new Model("hr.analytic.timesheet").call("default_get", [
['account_id','general_account_id', 'journal_id','date','name','user_id','product_id','product_uom_id','to_invoice','amount','unit_amount'],
new data.CompoundContext({'user_id': self.get('user_id')})]).then(function(result) {
return this.render_drop.add(new Model("account.analytic.line").call("default_get", [
['account_id','general_account_id', 'journal_id','date','name','user_id','product_id','product_uom_id','to_invoice','amount','unit_amount', 'is_timesheet'],
new data.CompoundContext({'user_id': self.get('user_id'), 'default_is_timesheet':true})]).then(function(result) {
default_get = result;
// calculating dates
dates = [];
@@ -132,7 +132,7 @@ var WeeklyTimesheet = form_common.FormWidget.extend(form_common.ReinitializeWidg
var account_ids = _.map(_.keys(accounts), function(el) { return el === "false" ? false : Number(el); });
return new Model("hr.analytic.timesheet").call("multi_on_change_account_id", [[], account_ids,
return new Model("account.analytic.line").call("multi_on_change_account_id", [[], account_ids,
new data.CompoundContext({'user_id': self.get('user_id')})]).then(function(accounts_defaults) {
accounts = _(accounts).chain().map(function(lines, account_id) {
var account_defaults = _.extend({}, default_get, (accounts_defaults[account_id] || {}).value || {});
@@ -216,7 +216,7 @@ var WeeklyTimesheet = form_common.FormWidget.extend(form_common.ReinitializeWidg
account.days[day_count].lines[0].unit_amount += num - self.sum_box(account, day_count);
var product = (account.days[day_count].lines[0].product_id instanceof Array) ? account.days[day_count].lines[0].product_id[0] : account.days[day_count].lines[0].product_id;
var journal = (account.days[day_count].lines[0].journal_id instanceof Array) ? account.days[day_count].lines[0].journal_id[0] : account.days[day_count].lines[0].journal_id;
self.defs.push(new Model("hr.analytic.timesheet").call("on_change_unit_amount", [[], product, account.days[day_count].lines[0].unit_amount, false, false, journal]).then(function(res) {
self.defs.push(new Model("account.analytic.line").call("on_change_unit_amount", [[], product, account.days[day_count].lines[0].unit_amount, false, false, journal]).then(function(res) {
account.days[day_count].lines[0]['amount'] = res.value.amount || 0;
self.display_totals();
self.sync();
@@ -271,7 +271,7 @@ var WeeklyTimesheet = form_common.FormWidget.extend(form_common.ReinitializeWidg
return;
}
var ops = self.generate_o2m_value();
new Model("hr.analytic.timesheet").call("multi_on_change_account_id", [[], [id],
new Model("account.analytic.line").call("multi_on_change_account_id", [[], [id],
new data.CompoundContext({'user_id': self.get('user_id')})]).then(function(res) {
res = res[id];
var def = _.extend({}, self.default_get, res.value, {
@@ -2,12 +2,10 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_project,project,project.model_project_project,base.group_portal,1,0,0,0
access_task,task,project.model_project_task,base.group_portal,1,0,0,0
access_task_type,task_type,project.model_project_task_type,base.group_portal,1,0,0,0
access_task_work,task_work,project.model_project_task_work,base.group_portal,1,0,0,0
access_project_category,project_category,project.model_project_category,base.group_portal,1,0,0,0
access_account_analytic_account,account_analytic_account,analytic.model_account_analytic_account,base.group_portal,1,0,0,0
access_project_public,project,project.model_project_project,base.group_public,1,0,0,0
access_task_public,task,project.model_project_task,base.group_public,1,0,0,0
access_task_type_public,task_type,project.model_project_task_type,base.group_public,1,0,0,0
access_task_work_public,task_work,project.model_project_task_work,base.group_public,1,0,0,0
access_project_category_public,project_category,project.model_project_category,base.group_public,1,0,0,0
access_account_analytic_account_public,account_analytic_account,analytic.model_account_analytic_account,base.group_public,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_project project project.model_project_project base.group_portal 1 0 0 0
3 access_task task project.model_project_task base.group_portal 1 0 0 0
4 access_task_type task_type project.model_project_task_type base.group_portal 1 0 0 0
access_task_work task_work project.model_project_task_work base.group_portal 1 0 0 0
5 access_project_category project_category project.model_project_category base.group_portal 1 0 0 0
6 access_account_analytic_account account_analytic_account analytic.model_account_analytic_account base.group_portal 1 0 0 0
7 access_project_public project project.model_project_project base.group_public 1 0 0 0
8 access_task_public task project.model_project_task base.group_public 1 0 0 0
9 access_task_type_public task_type project.model_project_task_type base.group_public 1 0 0 0
access_task_work_public task_work project.model_project_task_work base.group_public 1 0 0 0
10 access_project_category_public project_category project.model_project_category base.group_public 1 0 0 0
11 access_account_analytic_account_public account_analytic_account analytic.model_account_analytic_account base.group_public 1 0 0 0
+46 -174
View File
@@ -112,73 +112,6 @@ class project(osv.osv):
val['pricelist_id'] = pricelist_id
return {'value': val}
def _get_projects_from_tasks(self, cr, uid, task_ids, context=None):
tasks = self.pool.get('project.task').browse(cr, uid, task_ids, context=context)
project_ids = [task.project_id.id for task in tasks if task.project_id]
return self.pool.get('project.project')._get_project_and_parents(cr, uid, project_ids, context)
def _get_project_and_parents(self, cr, uid, ids, context=None):
""" return the project ids and all their parent projects """
res = set(ids)
while ids:
cr.execute("""
SELECT DISTINCT parent.id
FROM project_project project, project_project parent, account_analytic_account account
WHERE project.analytic_account_id = account.id
AND parent.analytic_account_id = account.parent_id
AND project.id IN %s
""", (tuple(ids),))
ids = [t[0] for t in cr.fetchall()]
res.update(ids)
return list(res)
def _get_project_and_children(self, cr, uid, ids, context=None):
""" retrieve all children projects of project ids;
return a dictionary mapping each project to its parent project (or None)
"""
res = dict.fromkeys(ids, None)
while ids:
cr.execute("""
SELECT project.id, parent.id
FROM project_project project, project_project parent, account_analytic_account account
WHERE project.analytic_account_id = account.id
AND parent.analytic_account_id = account.parent_id
AND parent.id IN %s
""", (tuple(ids),))
dic = dict(cr.fetchall())
res.update(dic)
ids = dic.keys()
return res
def _progress_rate(self, cr, uid, ids, names, arg, context=None):
child_parent = self._get_project_and_children(cr, uid, ids, context)
# compute planned_hours, total_hours, effective_hours specific to each project
cr.execute("""
SELECT project_id, COALESCE(SUM(planned_hours), 0.0),
COALESCE(SUM(total_hours), 0.0), COALESCE(SUM(effective_hours), 0.0)
FROM project_task
LEFT JOIN project_task_type ON project_task.stage_id = project_task_type.id
WHERE project_task.project_id IN %s AND project_task_type.fold = False
GROUP BY project_id
""", (tuple(child_parent.keys()),))
# aggregate results into res
res = dict([(id, {'planned_hours':0.0, 'total_hours':0.0, 'effective_hours':0.0}) for id in ids])
for id, planned, total, effective in cr.fetchall():
# add the values specific to id to all parent projects of id in the result
while id:
if id in ids:
res[id]['planned_hours'] += planned
res[id]['total_hours'] += total
res[id]['effective_hours'] += effective
id = child_parent[id]
# compute progress rates
for id in ids:
if res[id]['total_hours']:
res[id]['progress_rate'] = round(100.0 * res[id]['effective_hours'] / res[id]['total_hours'], 2)
else:
res[id]['progress_rate'] = 0.0
return res
def unlink(self, cr, uid, ids, context=None):
alias_ids = []
mail_alias = self.pool.get('mail.alias')
@@ -284,26 +217,6 @@ class project(osv.osv):
'members': fields.many2many('res.users', 'project_user_rel', 'project_id', 'uid', 'Project Members',
help="Project's members are users who can have an access to the tasks related to this project.", states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}),
'tasks': fields.one2many('project.task', 'project_id', "Task Activities"),
'planned_hours': fields.function(_progress_rate, multi="progress", string='Planned Time', help="Sum of planned hours of all tasks related to this project and its child projects.",
store = {
'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10),
'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'stage_id'], 20),
}),
'effective_hours': fields.function(_progress_rate, multi="progress", string='Time Spent', help="Sum of spent hours of all tasks related to this project and its child projects.",
store = {
'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10),
'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'stage_id'], 20),
}),
'total_hours': fields.function(_progress_rate, multi="progress", string='Total Time', help="Sum of total hours of all tasks related to this project and its child projects.",
store = {
'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10),
'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'stage_id'], 20),
}),
'progress_rate': fields.function(_progress_rate, multi="progress", string='Progress', type='float', group_operator="avg", help="Percent of tasks closed according to the total of tasks todo.",
store = {
'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10),
'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'stage_id'], 20),
}),
'resource_calendar_id': fields.many2one('resource.calendar', 'Working Time', help="Timetable working hours to adjust the gantt diagram report", states={'close':[('readonly',True)]} ),
'type_ids': fields.many2many('project.task.type', 'project_task_type_rel', 'project_id', 'type_id', 'Tasks Stages', states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}),
'task_count': fields.function(_task_count, type='integer', string="Tasks",),
@@ -759,12 +672,6 @@ class task(osv.osv):
res[task.id] = False
return res
def _get_task(self, cr, uid, ids, context=None):
result = {}
for work in self.pool.get('project.task.work').browse(cr, uid, ids, context=context):
if work.task_id: result[work.task_id.id] = True
return result.keys()
_columns = {
'active': fields.function(_is_template, store=True, string='Not a Template Task', type='boolean', help="This field is computed automatically and have the same behavior than the boolean 'active' field: if the task is linked to a template or unactivated project, it will be hidden unless specifically asked."),
'name': fields.char('Task Summary', track_visibility='onchange', size=128, required=True, select=True),
@@ -792,31 +699,10 @@ class task(osv.osv):
'child_ids': fields.many2many('project.task', 'project_task_parent_rel', 'parent_id', 'task_id', 'Delegated Tasks'),
'notes': fields.text('Notes'),
'planned_hours': fields.float('Initially Planned Hours', help='Estimated time to do the task, usually set by the project manager when the task is in draft state.'),
'effective_hours': fields.function(_hours_get, string='Hours Spent', multi='hours', help="Computed using the sum of the task work done.",
store = {
'project.task': (lambda self, cr, uid, ids, c={}: ids, ['work_ids', 'remaining_hours', 'planned_hours'], 10),
'project.task.work': (_get_task, ['hours'], 10),
}),
'remaining_hours': fields.float('Remaining Hours', digits=(16,2), help="Total remaining time, can be re-estimated periodically by the assignee of the task."),
'total_hours': fields.function(_hours_get, string='Total', multi='hours', help="Computed as: Time Spent + Remaining Time.",
store = {
'project.task': (lambda self, cr, uid, ids, c={}: ids, ['work_ids', 'remaining_hours', 'planned_hours'], 10),
'project.task.work': (_get_task, ['hours'], 10),
}),
'progress': fields.function(_hours_get, string='Working Time Progress (%)', multi='hours', group_operator="avg", help="If the task has a progress of 99.99% you should close the task if it's finished or reevaluate the time",
store = {
'project.task': (lambda self, cr, uid, ids, c={}: ids, ['work_ids', 'remaining_hours', 'planned_hours', 'state', 'stage_id'], 10),
'project.task.work': (_get_task, ['hours'], 10),
}),
'delay_hours': fields.function(_hours_get, string='Delay Hours', multi='hours', help="Computed as difference between planned hours by the project manager and the total hours of the task.",
store = {
'project.task': (lambda self, cr, uid, ids, c={}: ids, ['work_ids', 'remaining_hours', 'planned_hours'], 10),
'project.task.work': (_get_task, ['hours'], 10),
}),
'user_id': fields.many2one('res.users', 'Assigned to', select=True, track_visibility='onchange'),
'delegated_user_id': fields.related('child_ids', 'user_id', type='many2one', relation='res.users', string='Delegated To'),
'partner_id': fields.many2one('res.partner', 'Customer'),
'work_ids': fields.one2many('project.task.work', 'task_id', 'Work done'),
'manager_id': fields.related('project_id', 'analytic_account_id', 'user_id', type='many2one', relation='res.users', string='Project Manager'),
'company_id': fields.many2one('res.company', 'Company'),
'id': fields.integer('ID', readonly=True),
@@ -829,7 +715,6 @@ class task(osv.osv):
'date_last_stage_update': fields.datetime.now,
'kanban_state': 'normal',
'priority': '0',
'progress': 0,
'sequence': 10,
'active': True,
'user_id': lambda obj, cr, uid, ctx=None: uid,
@@ -889,6 +774,10 @@ class task(osv.osv):
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
users_obj = self.pool.get('res.users')
if context is None: context = {}
# read uom as admin to avoid access rights issues, e.g. for portal/share users,
# this should be safe (no context passed to avoid side-effects)
obj_tm = users_obj.browse(cr, SUPERUSER_ID, uid, context=context).company_id.project_time_mode_id
tm = obj_tm and obj_tm.name or 'Hours'
res = super(task, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu=submenu)
@@ -984,16 +873,14 @@ class task(osv.osv):
new_attachment_ids.append(attachment.copy(cr, uid, attachment_id, default={'res_id': delegated_task_id}, context=context))
return new_attachment_ids
def do_delegate(self, cr, uid, ids, delegate_data=None, context=None):
"""
Delegate Task to another users.
"""
if delegate_data is None:
delegate_data = {}
assert delegate_data['user_id'], _("Delegated User should be specified")
delegated_tasks = {}
def _get_effective_hours(self, task):
# this method is override in project_timesheet modules.
return 0.0
def _prepare_delegate_values(self, cr, uid, ids, delegate_data, context=None):
delegate_values = {}
for task in self.browse(cr, uid, ids, context=context):
delegated_task_id = self.copy(cr, uid, task.id, {
delegate_values[task.id] = {
'name': delegate_data['name'],
'project_id': delegate_data['project_id'] and delegate_data['project_id'][0] or False,
'stage_id': delegate_data.get('stage_id') and delegate_data.get('stage_id')[0] or False,
@@ -1002,14 +889,40 @@ class task(osv.osv):
'parent_ids': [(6, 0, [task.id])],
'description': delegate_data['new_task_description'] or '',
'child_ids': [],
'work_ids': []
'remaining_hours': delegate_data['planned_hours_me'],
# The values below will be written on the old task.
'task_planned_hours': delegate_data['planned_hours_me'],
'task_name': delegate_data['prefix'] or ''
}
return delegate_values
def do_delegate(self, cr, uid, ids, delegate_data=None, context=None):
"""
Delegate Task to another users.
"""
if delegate_data is None:
delegate_data = {}
assert delegate_data['user_id'], _("Delegated User should be specified")
delegated_tasks = {}
vals = self._prepare_delegate_values(cr, uid, ids, delegate_data, context)
for task in self.browse(cr, uid, ids, context=context):
delegated_task_id = self.copy(cr, uid, task.id, {
'name': vals[task.id]['name'],
'project_id': vals[task.id]['project_id'],
'stage_id': vals[task.id]['stage_id'],
'user_id': vals[task.id]['user_id'],
'planned_hours': vals[task.id]['planned_hours'],
'parent_ids': vals[task.id]['parent_ids'],
'description': vals[task.id]['description'],
'child_ids': vals[task.id]['child_ids'],
}, context=context)
self._delegate_task_attachments(cr, uid, task.id, delegated_task_id, context=context)
newname = delegate_data['prefix'] or ''
task.write({
'remaining_hours': delegate_data['planned_hours_me'],
'planned_hours': delegate_data['planned_hours_me'] + (task.effective_hours or 0.0),
'name': newname,
'remaining_hours': vals[task.id]['remaining_hours'],
'planned_hours': vals[task.id]['task_planned_hours'],
'name': vals[task.id]['task_name']
}, context=context)
delegated_tasks[task.id] = delegated_task_id
return delegated_tasks
@@ -1100,6 +1013,9 @@ class task(osv.osv):
res = super(task, self).unlink(cr, uid, ids, context)
return res
def _get_total_hours(self, task):
return self._get_effective_hours(task) + task.remaining_hours
def _generate_task(self, cr, uid, tasks, ident=4, context=None):
context = context or {}
result = ""
@@ -1110,7 +1026,7 @@ class task(osv.osv):
result += '''
%sdef Task_%s():
%s todo = \"%.2fH\"
%s effort = \"%.2fH\"''' % (ident,task.id, ident,task.remaining_hours, ident,task.total_hours)
%s effort = \"%.2fH\"''' % (ident,task.id, ident,task.remaining_hours, ident, self._get_total_hours(task))
start = []
for t2 in task.parent_ids:
start.append("up.Task_%s.end" % (t2.id,))
@@ -1192,50 +1108,6 @@ class task(osv.osv):
pass
return super(task, self).message_update(cr, uid, ids, msg, update_vals=update_vals, context=context)
class project_work(osv.osv):
_name = "project.task.work"
_description = "Project Task Work"
_columns = {
'name': fields.char('Work summary'),
'date': fields.datetime('Date', select="1"),
'task_id': fields.many2one('project.task', 'Task', ondelete='cascade', required=True, select="1"),
'hours': fields.float('Time Spent'),
'user_id': fields.many2one('res.users', 'Done by', required=True, select="1"),
'company_id': fields.related('task_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True)
}
_defaults = {
'user_id': lambda obj, cr, uid, context: uid,
'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S')
}
_order = "date desc"
def create(self, cr, uid, vals, context=None):
if 'hours' in vals and (not vals['hours']):
vals['hours'] = 0.00
if 'task_id' in vals:
cr.execute('update project_task set remaining_hours=remaining_hours - %s where id=%s', (vals.get('hours',0.0), vals['task_id']))
self.pool.get('project.task').invalidate_cache(cr, uid, ['remaining_hours'], [vals['task_id']], context=context)
return super(project_work,self).create(cr, uid, vals, context=context)
def write(self, cr, uid, ids, vals, context=None):
if 'hours' in vals and (not vals['hours']):
vals['hours'] = 0.00
if 'hours' in vals:
task_obj = self.pool.get('project.task')
for work in self.browse(cr, uid, ids, context=context):
cr.execute('update project_task set remaining_hours=remaining_hours - %s + (%s) where id=%s', (vals.get('hours',0.0), work.hours, work.task_id.id))
task_obj.invalidate_cache(cr, uid, ['remaining_hours'], [work.task_id.id], context=context)
return super(project_work,self).write(cr, uid, ids, vals, context)
def unlink(self, cr, uid, ids, context=None):
task_obj = self.pool.get('project.task')
for work in self.browse(cr, uid, ids):
cr.execute('update project_task set remaining_hours=remaining_hours + %s where id=%s', (work.hours, work.task_id.id))
task_obj.invalidate_cache(cr, uid, ['remaining_hours'], [work.task_id.id], context=context)
return super(project_work,self).unlink(cr, uid, ids, context=context)
class account_analytic_account(osv.osv):
_inherit = 'account.analytic.account'
_description = 'Analytic Account'
+3 -77
View File
@@ -158,10 +158,6 @@
attrs="{'readonly':[('state','in',['close', 'cancelled'])]}"
context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'project.group_project_manager']}"/>
<field name="privacy_visibility" widget="radio"/>
<field name="planned_hours" widget="float_time"
groups="project.group_time_work_estimation_tasks"/>
<field name="effective_hours" widget="float_time"
groups="project.group_time_work_estimation_tasks"/>
<field name="resource_calendar_id"
groups="project.group_time_work_estimation_tasks"/>
</group>
@@ -230,10 +226,6 @@
<field name="user_id" string="Project Manager"/>
<field name="partner_id" string="Contact"/>
<field name="parent_id" string="Parent" invisible="1"/>
<field name="planned_hours" widget="float_time"/>
<field name="total_hours" widget="float_time"/>
<field name="effective_hours" widget="float_time"/>
<field name="progress_rate" widget="progressbar"/>
<field name="state"/>
</tree>
</field>
@@ -244,8 +236,6 @@
<field name="model">project.project</field>
<field name="arch" type="xml">
<kanban class="oe_background_grey">
<field name="effective_hours"/>
<field name="planned_hours"/>
<field name="name"/>
<field name="members"/>
<field name="use_tasks"/>
@@ -348,35 +338,6 @@
<field name="view_id" ref="view_project"/>
</record>
<record id="view_task_work_form" model="ir.ui.view">
<field name="name">project.task.work.form</field>
<field name="model">project.task.work</field>
<field name="arch" type="xml">
<form string="Task Work">
<group col="4">
<field colspan="4" name="name"/>
<field name="hours" widget="float_time"/>
<field name="date"/>
<field name="user_id" options='{"no_open": True}' context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'base.group_partner_manager', 'project.group_project_user']}"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
</group>
</form>
</field>
</record>
<record id="view_task_work_tree" model="ir.ui.view">
<field name="name">project.task.work.tree</field>
<field name="model">project.task.work</field>
<field name="arch" type="xml">
<tree editable="top" string="Task Work">
<field name="date"/>
<field name="name"/>
<field name="hours" widget="float_time"/>
<field name="user_id" context="{'default_groups_ref': ['base.group_user', 'project.group_project_user']}"/>
</tree>
</field>
</record>
<!-- Task -->
<record id="view_task_form2" model="ir.ui.view">
@@ -410,36 +371,16 @@
context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'project.group_project_user']}"/>
<field name="planned_hours" widget="float_time"
groups="project.group_time_work_estimation_tasks"
on_change="onchange_planned(planned_hours, effective_hours)"/>
on_change="onchange_planned(planned_hours)"/>
</group>
<group>
<field name="date_deadline"/>
<field name="categ_ids" widget="many2many_tags"/>
<field name="progress" widget="progressbar"
groups="project.group_time_work_estimation_tasks"/>
</group>
</group>
<notebook>
<page string="Description">
<page name="description_page" string="Description">
<field name="description" type="html"/>
<field name="work_ids" groups="project.group_tasks_work_on_tasks">
<tree string="Task Work" editable="top">
<field name="name"/>
<field name="hours" widget="float_time" sum="Spent Hours"/>
<field name="date"/>
<field name="user_id" context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'project.group_project_user']}"/>
</tree>
</field>
<group>
<group class="oe_subtotal_footer oe_right" name="project_hours" groups="project.group_time_work_estimation_tasks">
<field name="effective_hours" widget="float_time"/>
<label for="remaining_hours" string="Remaining" groups="project.group_time_work_estimation_tasks"/>
<div>
<field name="remaining_hours" widget="float_time" groups="project.group_time_work_estimation_tasks"/>
</div>
<field name="total_hours" widget="float_time" class="oe_subtotal_footer_separator"/>
</group>
</group>
<div class="oe_clear"/>
</page>
<page string="Delegation" groups="project.group_delegate_task">
@@ -449,13 +390,10 @@
<field name="parent_ids"/>
<separator string="Delegated tasks"/>
<field name="child_ids">
<tree string="Delegated tasks">
<tree name="delegated_task" string="Delegated tasks">
<field name="name"/>
<field name="user_id"/>
<field name="stage_id"/>
<field name="effective_hours" widget="float_time"/>
<field name="progress" widget="progressbar"/>
<field name="remaining_hours" widget="float_time"/>
<field name="date_deadline"/>
</tree>
</field>
@@ -512,14 +450,6 @@
<ul class="oe_dropdown_menu">
<t t-if="widget.view.is_action_enabled('edit')"><li><a type="edit">Edit Task</a></li></t>
<t t-if="widget.view.is_action_enabled('delete')"><li><a type="delete">Delete</a></li></t>
<li>
<ul class="oe_kanban_project_times" groups="project.group_time_work_estimation_tasks">
<li><a name="set_remaining_time_1" type="object" class="oe_kanban_button">1</a></li>
<li><a name="set_remaining_time_2" type="object" class="oe_kanban_button">2</a></li>
<li><a name="set_remaining_time_5" type="object" class="oe_kanban_button">5</a></li>
<li><a name="set_remaining_time_10" type="object" class="oe_kanban_button">10</a></li>
</ul>
</li>
<br/>
<li><ul class="oe_kanban_colorpicker" data-field="color"/></li>
</ul>
@@ -567,15 +497,11 @@
<field name="project_id" invisible="context.get('user_invisible', False)"/>
<field name="user_id" invisible="context.get('user_invisible', False)"/>
<field name="delegated_user_id" invisible="context.get('show_delegated', True)"/>
<field name="total_hours" invisible="1"/>
<field name="planned_hours" invisible="context.get('set_visible',False)" groups="project.group_time_work_estimation_tasks"/>
<field name="effective_hours" widget="float_time" sum="Spent Hours" invisible="1"/>
<field name="remaining_hours" widget="float_time" sum="Remaining Hours" on_change="onchange_remaining(remaining_hours,planned_hours)" invisible="context.get('set_visible',False)" groups="project.group_time_work_estimation_tasks"/>
<field name="date_deadline" invisible="context.get('deadline_visible',True)"/>
<field name="stage_id" invisible="context.get('set_visible',False)"/>
<field name="date_start" groups="base.group_no_one"/>
<field name="date_end" groups="base.group_no_one"/>
<field name="progress" widget="progressbar" invisible="context.get('set_visible',False)"/>
</tree>
</field>
</record>
+20 -26
View File
@@ -36,12 +36,6 @@ class report_project_task_user(osv.osv):
'date_deadline': fields.date('Deadline', readonly=True),
'date_last_stage_update': fields.datetime('Last Stage Update', readonly=True),
'project_id': fields.many2one('project.project', 'Project', readonly=True),
'hours_planned': fields.float('Planned Hours', readonly=True),
'hours_effective': fields.float('Effective Hours', readonly=True),
'hours_delay': fields.float('Avg. Plan.-Eff.', readonly=True),
'remaining_hours': fields.float('Remaining Hours', readonly=True),
'progress': fields.float('Progress', readonly=True, group_operator='avg'),
'total_hours': fields.float('Total Hours', readonly=True),
'closing_days': fields.float('Days to Close', digits=(16,2), readonly=True, group_operator="avg",
help="Number of Days to close the task"),
'opening_days': fields.float('Days to Assign', digits=(16,2), readonly=True, group_operator="avg",
@@ -57,11 +51,9 @@ class report_project_task_user(osv.osv):
}
_order = 'name desc, project_id'
def init(self, cr):
tools.sql.drop_view_if_exists(cr, 'report_project_task_user')
cr.execute("""
CREATE view report_project_task_user as
SELECT
def _select(self):
select_str = """
SELECT
(select 1 ) AS nbr,
t.id as id,
t.date_start as date_start,
@@ -70,32 +62,23 @@ class report_project_task_user(osv.osv):
t.date_deadline as date_deadline,
abs((extract('epoch' from (t.write_date-t.date_start)))/(3600*24)) as no_of_days,
t.user_id,
progress as progress,
t.project_id,
t.effective_hours as hours_effective,
t.priority,
t.name as name,
t.company_id,
t.partner_id,
t.stage_id as stage_id,
t.kanban_state as state,
remaining_hours as remaining_hours,
total_hours as total_hours,
t.delay_hours as hours_delay,
planned_hours as hours_planned,
(extract('epoch' from (t.write_date-t.create_date)))/(3600*24) as closing_days,
(extract('epoch' from (t.date_start-t.create_date)))/(3600*24) as opening_days,
(extract('epoch' from (t.date_deadline-(now() at time zone 'UTC'))))/(3600*24) as delay_endings_days
FROM project_task t
WHERE t.active = 'true'
"""
return select_str
def _group_by(self):
group_by_str = """
GROUP BY
t.id,
remaining_hours,
t.effective_hours,
progress,
total_hours,
planned_hours,
hours_delay,
create_date,
write_date,
date_start,
@@ -109,4 +92,15 @@ class report_project_task_user(osv.osv):
t.company_id,
t.partner_id,
stage_id
""")
"""
return group_by_str
def init(self, cr):
tools.sql.drop_view_if_exists(cr, 'report_project_task_user')
cr.execute("""
CREATE view report_project_task_user as
%s
FROM project_task t
WHERE t.active = 'true'
%s
"""% (self._select(), self._group_by()))
+1 -1
View File
@@ -57,7 +57,7 @@
<label for="time_unit"/>
<field name="time_unit" class="oe_inline" domain="[('category_id.name','=','Working Time')]"/>
</div>
<div>
<div attrs="{'invisible': [('module_project_timesheet','=',False)]}">
<field name="group_tasks_work_on_tasks" class="oe_inline"/>
<label for="group_tasks_work_on_tasks" />
</div>
@@ -6,7 +6,6 @@ access_account_analytic_account,account.analytic.account,analytic.model_account_
access_project_task_type_user,project.task.type user,model_project_task_type,project.group_project_user,1,0,0,0
access_project_task_type_manager,project.task.type manager,model_project_task_type,project.group_project_manager,1,1,1,1
access_project_task,project.task,model_project_task,project.group_project_user,1,1,1,1
access_project_task_work,project.task.work,model_project_task_work,project.group_project_user,1,1,1,1
access_report_project_task_user,report.project.task.user,model_report_project_task_user,project.group_project_manager,1,1,1,1
access_partner_task user,base.res.partner user,base.model_res_partner,project.group_project_user,1,0,0,0
access_task_on_partner,project.task on partners,model_project_task,base.group_user,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
6 access_project_task_type_user project.task.type user model_project_task_type project.group_project_user 1 0 0 0
7 access_project_task_type_manager project.task.type manager model_project_task_type project.group_project_manager 1 1 1 1
8 access_project_task project.task model_project_task project.group_project_user 1 1 1 1
access_project_task_work project.task.work model_project_task_work project.group_project_user 1 1 1 1
9 access_report_project_task_user report.project.task.user model_report_project_task_user project.group_project_manager 1 1 1 1
10 access_partner_task user base.res.partner user base.model_res_partner project.group_project_user 1 0 0 0
11 access_task_on_partner project.task on partners model_project_task base.group_user 1 0 0 0
-31
View File
@@ -158,16 +158,6 @@ class project_issue(osv.Model):
return res
def _hours_get(self, cr, uid, ids, field_names, args, context=None):
task_pool = self.pool.get('project.task')
res = {}
for issue in self.browse(cr, uid, ids, context=context):
progress = 0.0
if issue.task_id:
progress = task_pool._hours_get(cr, uid, [issue.task_id.id], field_names, args, context=context)[issue.task_id.id]['progress']
res[issue.id] = {'progress' : progress}
return res
def _can_escalate(self, cr, uid, ids, field_name, arg, context=None):
res = {}
for issue in self.browse(cr, uid, ids, context=context):
@@ -183,21 +173,6 @@ class project_issue(osv.Model):
return {'value': {'partner_id': project.partner_id.id}}
return {}
def _get_issue_task(self, cr, uid, ids, context=None):
issues = []
issue_pool = self.pool.get('project.issue')
for task in self.pool.get('project.task').browse(cr, uid, ids, context=context):
issues += issue_pool.search(cr, uid, [('task_id','=',task.id)])
return issues
def _get_issue_work(self, cr, uid, ids, context=None):
issues = []
issue_pool = self.pool.get('project.issue')
for work in self.pool.get('project.task.work').browse(cr, uid, ids, context=context):
if work.task_id:
issues += issue_pool.search(cr, uid, [('task_id','=',work.task_id.id)])
return issues
_columns = {
'id': fields.integer('ID', readonly=True),
'name': fields.char('Issue', required=True),
@@ -257,12 +232,6 @@ class project_issue(osv.Model):
'date_action_last': fields.datetime('Last Action', readonly=1),
'date_action_next': fields.datetime('Next Action', readonly=1),
'can_escalate': fields.function(_can_escalate, type='boolean', string='Can Escalate'),
'progress': fields.function(_hours_get, string='Progress (%)', multi='hours', group_operator="avg", help="Computed as: Time Spent / Total Time.",
store = {
'project.issue': (lambda self, cr, uid, ids, c={}: ids, ['task_id'], 10),
'project.task': (_get_issue_task, ['work_ids', 'remaining_hours', 'planned_hours', 'state', 'stage_id'], 10),
'project.task.work': (_get_issue_work, ['hours'], 10),
}),
}
_defaults = {
@@ -44,7 +44,6 @@
<label for="task_id" groups="base.group_user"/>
<div groups="base.group_user">
<field name="task_id" on_change="onchange_task_id(task_id)" class="oe_inline" context="{'default_project_id':project_id}"/>
<field name="progress" widget="progressbar" attrs="{'invisible':[('task_id','=',False)]}" class="oe_inline"/>
</div>
</group>
</group>
@@ -91,7 +90,6 @@
<field name="priority" string="Priority" groups="base.group_user"/>
<field name="create_date"/>
<field name="user_id"/>
<field name="progress" widget="progressbar" attrs="{'invisible':[('task_id','=',False)]}"/>
<field name="stage_id" widget="selection" readonly="1"/>
<field name="categ_ids" invisible="1"/>
<field name="task_id" invisible="1"/>
@@ -35,6 +35,7 @@ Worklogs can be maintained to signify number of hours spent by users to handle a
'depends': [
'project_issue',
'hr_timesheet_sheet',
'project_timesheet',
],
'data': [
'project_issue_sheet_view.xml',
@@ -25,8 +25,23 @@ from openerp.tools.translate import _
class project_issue(osv.osv):
_inherit = 'project.issue'
_description = 'project issue'
def _hours_get(self, cr, uid, ids, field_names, args, context=None):
res = {}
for issue in self.browse(cr, uid, ids, context=context):
res[issue.id] = { 'progress' : issue.task_id.progress or 0.0 }
return res
def _get_issue_task(self, cr, uid, task_ids, context=None):
return self.pool['project.issue'].search(cr, uid, [('task_id', 'in', task_ids)], context=context)
_columns = {
'timesheet_ids': fields.one2many('hr.analytic.timesheet', 'issue_id', 'Timesheets'),
'progress': fields.function(_hours_get, string='Progress (%)', multi='line_id', group_operator="avg", help="Computed as: Time Spent / Total Time.",
store = {
'project.issue': (lambda self, cr, uid, ids, c={}: ids, ['task_id'], 10),
'project.task': (_get_issue_task, ['progress'], 10),
}),
'timesheet_ids': fields.one2many('account.analytic.line', 'issue_id', 'Timesheets'),
'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account'),
}
@@ -62,15 +77,6 @@ class project_issue(osv.osv):
class account_analytic_line(osv.osv):
_inherit = 'account.analytic.line'
_description = 'account analytic line'
_columns = {
'create_date' : fields.datetime('Create Date', readonly=True),
}
class hr_analytic_issue(osv.osv):
_inherit = 'hr.analytic.timesheet'
_description = 'hr analytic timesheet'
_columns = {
'issue_id' : fields.many2one('project.issue', 'Issue'),
}
@@ -6,6 +6,9 @@
<field name="model">project.issue</field>
<field name="inherit_id" ref="project_issue.project_issue_form_view" />
<field name="arch" type="xml">
<field name="task_id" position="after">
<field name="progress" widget="progressbar" attrs="{'invisible':[('task_id','=',False)]}" class="oe_inline"/>
</field>
<field name="project_id" position="attributes">
<attribute name="on_change">on_change_project(project_id)</attribute>
</field>
@@ -15,26 +18,39 @@
on_change='on_change_account_id(analytic_account_id)'/>
</field>
<xpath expr="//notebook/page[@name='extra_info']" position="before">
<page string="Worklogs">
<field name="timesheet_ids" colspan="4" nolabel="1" context="{'default_user_id' : uid, 'default_account_id' : analytic_account_id}"
<page string="Timesheets">
<field name="timesheet_ids" colspan="4" nolabel="1" context="{'default_user_id' : uid, 'default_account_id' : analytic_account_id, 'default_is_timesheet':1}"
groups="base.group_user">
<tree editable="top" string="Timesheets">
<field name="name"/>
<field name="unit_amount" string="Duration" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)" widget="float_time"/>
<field name="account_id" invisible="0" domain="[('partner_id', '=', parent.partner_id)]" on_change="on_change_account_id(account_id)" groups="analytic.group_analytic_accounting"/>
<tree editable="top" string="Timesheet Activities">
<field name="date"/>
<field name="user_id"/>
<field invisible="1" name="journal_id"/>
<field invisible="1" name="product_id"/>
<field invisible="1" name="product_uom_id" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)"/>
<field invisible="1" name="amount"/>
<field invisible="1" name="general_account_id"/>
<field invisible="1" name="to_invoice"/>
<field name="user_id" required="1"/>
<field name="name"/>
<field name="account_id" invisible="0" domain="[('partner_id', '=', parent.partner_id)]" on_change="on_change_account_id(account_id, context)" groups="analytic.group_analytic_accounting"/>
<field name="unit_amount" string="Duration" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id, context)" widget="float_time"/>
<field name="to_invoice" invisible="1"/>
<field name="product_uom_id" invisible="1" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id, context)"/>
<field name="journal_id" invisible="1"/>
<field name="amount" invisible="1"/>
<field name="general_account_id" invisible="1"/>
<field name="product_id" invisible="1"/>
<field name="is_timesheet" invisible="1"/>
</tree>
</field>
</page>
</xpath>
</field>
</record>
<record id="project_issue_tree_view_inherited" model="ir.ui.view">
<field name="name">Project Issue Tracker Tree Inherited</field>
<field name="model">project.issue</field>
<field name="inherit_id" ref="project_issue.project_issue_tree_view" />
<field name="arch" type="xml">
<field name="user_id" position="after">
<field name="progress" widget="progressbar" invisible="context.get('set_visible',False)"/>
</field>
</field>
</record>
</data>
</openerp>
@@ -1,9 +1,9 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_hr_timesheet_sheet_sheet_project_user,hr_timesheet_sheet.sheet user,hr_timesheet_sheet.model_hr_timesheet_sheet_sheet,project.group_project_user,1,1,1,0
access_hr_analytic_timesheet_project_user,hr_analytic_timesheet user,hr_timesheet_sheet.model_hr_analytic_timesheet,project.group_project_user,1,1,1,0
access_hr_analytic_timesheet_project_manager,hr_analytic_timesheet manager,hr_timesheet_sheet.model_hr_analytic_timesheet,project.group_project_manager,1,0,0,0
access_account_analytic_line_project_user,account_analytic_line user,hr_timesheet_sheet.model_account_analytic_line,project.group_project_user,1,1,1,0
access_account_analytic_line_project_manager,account_analytic_line manager,hr_timesheet_sheet.model_account_analytic_line,project.group_project_manager,1,0,0,0
access_hr_timesheet_sheet_sheet_project_manager,hr_timesheet_sheet.sheet manager,hr_timesheet_sheet.model_hr_timesheet_sheet_sheet,project.group_project_manager,1,0,0,0
access_hr_analytic_timesheet_project_manager,account.analytic.line.timesheet.project.manager,account.model_account_analytic_line,project.group_project_manager,1,1,1,1
access_account_analytic_line_project_manager,account.analytic.line.timesheet.project.manager,account.model_account_analytic_line,project.group_project_manager,1,1,1,1
access_account_analytic_journal_project_manager,account.analytic.journal.project.manager,analytic.model_account_analytic_journal,project.group_project_manager,1,1,1,1
access_account_fiscalyear_project_manager,account.fiscalyear.project.manager,account.model_account_fiscalyear,project.group_project_manager,1,1,1,1
access_hr_timesheet_invoice_factor_project_manager,hr_timesheet_invoice.factor.project.manager,hr_timesheet_invoice.model_hr_timesheet_invoice_factor,project.group_project_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_hr_timesheet_sheet_sheet_project_user hr_timesheet_sheet.sheet user hr_timesheet_sheet.model_hr_timesheet_sheet_sheet project.group_project_user 1 1 1 0
3 access_hr_analytic_timesheet_project_user access_account_analytic_line_project_user hr_analytic_timesheet user account_analytic_line user hr_timesheet_sheet.model_hr_analytic_timesheet hr_timesheet_sheet.model_account_analytic_line project.group_project_user 1 1 1 0
4 access_hr_analytic_timesheet_project_manager access_account_analytic_line_project_manager hr_analytic_timesheet manager account_analytic_line manager hr_timesheet_sheet.model_hr_analytic_timesheet hr_timesheet_sheet.model_account_analytic_line project.group_project_manager 1 0 0 0
5 access_hr_timesheet_sheet_sheet_project_manager hr_timesheet_sheet.sheet manager hr_timesheet_sheet.model_hr_timesheet_sheet_sheet project.group_project_manager 1 0 0 0
6 access_hr_analytic_timesheet_project_manager access_account_analytic_line_project_manager account.analytic.line.timesheet.project.manager account.model_account_analytic_line project.group_project_manager 1 1 1 1
7 access_account_analytic_journal_project_manager account.analytic.journal.project.manager analytic.model_account_analytic_journal project.group_project_manager 1 1 1 1
8 access_account_fiscalyear_project_manager account.fiscalyear.project.manager account.model_account_fiscalyear project.group_project_manager 1 1 1 1
9 access_hr_timesheet_invoice_factor_project_manager hr_timesheet_invoice.factor.project.manager hr_timesheet_invoice.model_hr_timesheet_invoice_factor project.group_project_manager 1 1 1 1
+1
View File
@@ -20,3 +20,4 @@
##############################################################################
import project_timesheet
import report
+4 -3
View File
@@ -33,16 +33,17 @@ with the effect of creating, editing and deleting either ways.
""",
'author': 'OpenERP SA',
'website': 'https://www.odoo.com/page/project-management',
'depends': ['resource', 'project', 'hr_timesheet', 'hr_timesheet_invoice', 'account_analytic_analysis', 'procurement'],
'images': ['images/invoice_task_work.jpeg', 'images/my_timesheet.jpeg', 'images/working_hour.jpeg'],
'depends': ['resource', 'project', 'account_analytic_analysis', 'procurement'],
'data': [
'security/ir.model.access.csv',
'security/project_timesheet_security.xml',
'project_timesheet_view.xml',
'report/project_report_view.xml',
'project_timesheet_view.xml'
],
'demo': ['project_timesheet_demo.xml'],
'test': [
'test/worktask_entry_to_timesheetline_entry.yml',
'test/work_timesheet.yml',
],
'installable': True,
'auto_install': True,
+159 -234
View File
@@ -20,6 +20,7 @@
##############################################################################
import time
import datetime
from dateutil.relativedelta import relativedelta
from openerp.osv import fields, osv
from openerp import tools
@@ -29,6 +30,73 @@ from openerp.exceptions import UserError
class project_project(osv.osv):
_inherit = 'project.project'
def _get_project_and_children(self, cr, uid, ids, context=None):
""" retrieve all children projects of project ids;
return a dictionary mapping each project to its parent project (or None)
"""
res = dict.fromkeys(ids, None)
while ids:
cr.execute("""
SELECT project.id, parent.id
FROM project_project project, project_project parent, account_analytic_account account
WHERE project.analytic_account_id = account.id
AND parent.analytic_account_id = account.parent_id
AND parent.id IN %s
""", (tuple(ids),))
dic = dict(cr.fetchall())
res.update(dic)
ids = dic.keys()
return res
def _progress_rate(self, cr, uid, ids, names, arg, context=None):
child_parent = self._get_project_and_children(cr, uid, ids, context)
# compute planned_hours, total_hours, effective_hours specific to each project
cr.execute("""
SELECT project_id, COALESCE(SUM(planned_hours), 0.0),
COALESCE(SUM(total_hours), 0.0), COALESCE(SUM(effective_hours), 0.0)
FROM project_task
LEFT JOIN project_task_type ON project_task.stage_id = project_task_type.id
WHERE project_task.project_id IN %s AND project_task_type.fold = False
GROUP BY project_id
""", (tuple(child_parent.keys()),))
# aggregate results into res
res = dict([(id, {'planned_hours': 0.0, 'total_hours': 0.0, 'effective_hours': 0.0}) for id in ids])
for id, planned, total, effective in cr.fetchall():
# add the values specific to id to all parent projects of id in the result
while id:
if id in ids:
res[id]['planned_hours'] += planned
res[id]['total_hours'] += total
res[id]['effective_hours'] += effective
id = child_parent[id]
# compute progress rates
for id in ids:
if res[id]['total_hours']:
res[id]['progress_rate'] = round(100.0 * res[id]['effective_hours'] / res[id]['total_hours'], 2)
else:
res[id]['progress_rate'] = 0.0
return res
def _get_projects_from_tasks(self, cr, uid, task_ids, context=None):
tasks = self.pool.get('project.task').browse(cr, uid, task_ids, context=context)
project_ids = [task.project_id.id for task in tasks if task.project_id]
return self.pool.get('project.project')._get_project_and_parents(cr, uid, project_ids, context)
def _get_project_and_parents(self, cr, uid, ids, context=None):
""" return the project ids and all their parent projects """
res = set(ids)
while ids:
cr.execute("""
SELECT DISTINCT parent.id
FROM project_project project, project_project parent, account_analytic_account account
WHERE project.analytic_account_id = account.id
AND parent.analytic_account_id = account.parent_id
AND project.id IN %s
""", (tuple(ids),))
ids = [t[0] for t in cr.fetchall()]
res.update(ids)
return list(res)
def onchange_partner_id(self, cr, uid, ids, part=False, context=None):
res = super(project_project, self).onchange_partner_id(cr, uid, ids, part, context)
if part and res and ('value' in res):
@@ -39,7 +107,30 @@ class project_project(osv.osv):
factor_id = data_obj.browse(cr, uid, data_id).res_id
res['value'].update({'to_invoice': factor_id})
return res
_columns = {
'planned_hours': fields.function(_progress_rate, multi="progress", string='Planned Time', help="Sum of planned hours of all tasks related to this project and its child projects.",
store = {
'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10),
'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'timesheet_ids', 'stage_id'], 20),
}),
'effective_hours': fields.function(_progress_rate, multi="progress", string='Time Spent', help="Sum of spent hours of all tasks related to this project and its child projects.",
store = {
'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10),
'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'timesheet_ids', 'stage_id'], 20),
}),
'total_hours': fields.function(_progress_rate, multi="progress", string='Total Time', help="Sum of total hours of all tasks related to this project and its child projects.",
store = {
'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10),
'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'timesheet_ids', 'stage_id'], 20),
}),
'progress_rate': fields.function(_progress_rate, multi="progress", string='Progress', type='float', group_operator="avg", help="Percent of tasks closed according to the total of tasks todo.",
store = {
'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10),
'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'timesheet_ids', 'stage_id'], 20),
}),
}
_defaults = {
'invoice_on_timesheets': True,
}
@@ -53,6 +144,7 @@ class project_project(osv.osv):
view_context = {
'search_default_account_id': [project.analytic_account_id.id],
'default_account_id': project.analytic_account_id.id,
'default_is_timesheet':True
}
help = _("""<p class="oe_view_nocontent_create">Record your timesheets for the project '%s'.</p>""") % (project.name,)
try:
@@ -70,220 +162,73 @@ class project_project(osv.osv):
result['help'] = help
return result
class project_work(osv.osv):
_inherit = "project.task.work"
def get_user_related_details(self, cr, uid, user_id):
res = {}
emp_obj = self.pool.get('hr.employee')
emp_id = emp_obj.search(cr, uid, [('user_id', '=', user_id)])
if not emp_id:
user_name = self.pool.get('res.users').read(cr, uid, [user_id], ['name'])[0]['name']
raise UserError(_('Please define employee for user "%s". You must create one.')% (user_name,))
emp = emp_obj.browse(cr, uid, emp_id[0])
if not emp.product_id:
raise UserError(_('Please define product and product category property account on the related employee.\nFill in the HR Settings tab of the employee form.'))
if not emp.journal_id:
raise UserError(_('Please define journal on the related employee.\nFill in the HR Settings tab of the employee form.'))
acc_id = emp.product_id.property_account_expense.id
if not acc_id:
acc_id = emp.product_id.categ_id.property_account_expense_categ.id
if not acc_id:
raise UserError(_('Please define product and product category property account on the related employee.\nFill in the HR Settings of the employee form.'))
res['product_id'] = emp.product_id.id
res['journal_id'] = emp.journal_id.id
res['general_account_id'] = acc_id
res['product_uom_id'] = emp.product_id.uom_id.id
return res
def _create_analytic_entries(self, cr, uid, vals, context):
"""Create the hr analytic timesheet from project task work"""
timesheet_obj = self.pool['hr.analytic.timesheet']
task_obj = self.pool['project.task']
vals_line = {}
timeline_id = False
acc_id = False
task_obj = task_obj.browse(cr, uid, vals['task_id'], context=context)
result = self.get_user_related_details(cr, uid, vals.get('user_id', uid))
vals_line['name'] = '%s: %s' % (tools.ustr(task_obj.name), tools.ustr(vals['name'] or '/'))
vals_line['user_id'] = vals['user_id']
vals_line['product_id'] = result['product_id']
if vals.get('date'):
if len(vals['date']) > 10:
timestamp = datetime.datetime.strptime(vals['date'], tools.DEFAULT_SERVER_DATETIME_FORMAT)
ts = fields.datetime.context_timestamp(cr, uid, timestamp, context)
vals_line['date'] = ts.strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
else:
vals_line['date'] = vals['date']
# Calculate quantity based on employee's product's uom
vals_line['unit_amount'] = vals['hours']
default_uom = self.pool['res.users'].browse(cr, uid, uid, context=context).company_id.project_time_mode_id.id
if result['product_uom_id'] != default_uom:
vals_line['unit_amount'] = self.pool['product.uom']._compute_qty(cr, uid, default_uom, vals['hours'], result['product_uom_id'])
acc_id = task_obj.project_id and task_obj.project_id.analytic_account_id.id or acc_id
if acc_id:
vals_line['account_id'] = acc_id
res = timesheet_obj.on_change_account_id(cr, uid, False, acc_id)
if res.get('value'):
vals_line.update(res['value'])
vals_line['general_account_id'] = result['general_account_id']
vals_line['journal_id'] = result['journal_id']
vals_line['amount'] = 0.0
vals_line['product_uom_id'] = result['product_uom_id']
amount = vals_line['unit_amount']
prod_id = vals_line['product_id']
unit = False
timeline_id = timesheet_obj.create(cr, uid, vals=vals_line, context=context)
# Compute based on pricetype
amount_unit = timesheet_obj.on_change_unit_amount(cr, uid, timeline_id,
prod_id, amount, False, unit, vals_line['journal_id'], context=context)
if amount_unit and 'amount' in amount_unit.get('value',{}):
updv = { 'amount': amount_unit['value']['amount'] }
timesheet_obj.write(cr, uid, [timeline_id], updv, context=context)
return timeline_id
def create(self, cr, uid, vals, *args, **kwargs):
context = kwargs.get('context', {})
if not context.get('no_analytic_entry',False):
vals['hr_analytic_timesheet_id'] = self._create_analytic_entries(cr, uid, vals, context=context)
return super(project_work,self).create(cr, uid, vals, *args, **kwargs)
def write(self, cr, uid, ids, vals, context=None):
"""
When a project task work gets updated, handle its hr analytic timesheet.
"""
if context is None:
context = {}
timesheet_obj = self.pool.get('hr.analytic.timesheet')
uom_obj = self.pool.get('product.uom')
result = {}
if isinstance(ids, (long, int)):
ids = [ids]
for task in self.browse(cr, uid, ids, context=context):
line_id = task.hr_analytic_timesheet_id
if not line_id:
# if a record is deleted from timesheet, the line_id will become
# null because of the foreign key on-delete=set null
continue
vals_line = {}
if 'name' in vals:
vals_line['name'] = '%s: %s' % (tools.ustr(task.task_id.name), tools.ustr(vals['name'] or '/'))
if 'user_id' in vals:
vals_line['user_id'] = vals['user_id']
if 'date' in vals:
vals_line['date'] = vals['date'][:10]
if 'hours' in vals:
vals_line['unit_amount'] = vals['hours']
prod_id = vals_line.get('product_id', line_id.product_id.id) # False may be set
# Put user related details in analytic timesheet values
details = self.get_user_related_details(cr, uid, vals.get('user_id', task.user_id.id))
for field in ('product_id', 'general_account_id', 'journal_id', 'product_uom_id'):
if details.get(field, False):
vals_line[field] = details[field]
# Check if user's default UOM differs from product's UOM
user_default_uom_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.project_time_mode_id.id
if details.get('product_uom_id', False) and details['product_uom_id'] != user_default_uom_id:
vals_line['unit_amount'] = uom_obj._compute_qty(cr, uid, user_default_uom_id, vals['hours'], details['product_uom_id'])
# Compute based on pricetype
amount_unit = timesheet_obj.on_change_unit_amount(cr, uid, line_id.id,
prod_id=prod_id, company_id=False,
unit_amount=vals_line['unit_amount'], unit=False, journal_id=vals_line['journal_id'], context=context)
if amount_unit and 'amount' in amount_unit.get('value',{}):
vals_line['amount'] = amount_unit['value']['amount']
if vals_line:
self.pool.get('hr.analytic.timesheet').write(cr, uid, [line_id.id], vals_line, context=context)
return super(project_work,self).write(cr, uid, ids, vals, context)
def unlink(self, cr, uid, ids, *args, **kwargs):
hat_obj = self.pool.get('hr.analytic.timesheet')
hat_ids = []
for task in self.browse(cr, uid, ids):
if task.hr_analytic_timesheet_id:
hat_ids.append(task.hr_analytic_timesheet_id.id)
# Delete entry from timesheet too while deleting entry to task.
if hat_ids:
hat_obj.unlink(cr, uid, hat_ids, *args, **kwargs)
return super(project_work,self).unlink(cr, uid, ids, *args, **kwargs)
_columns={
'hr_analytic_timesheet_id':fields.many2one('hr.analytic.timesheet','Related Timeline Id', ondelete='set null'),
}
class task(osv.osv):
_inherit = "project.task"
def unlink(self, cr, uid, ids, *args, **kwargs):
for task_obj in self.browse(cr, uid, ids, *args, **kwargs):
if task_obj.work_ids:
work_ids = [x.id for x in task_obj.work_ids]
self.pool.get('project.task.work').unlink(cr, uid, work_ids, *args, **kwargs)
return super(task,self).unlink(cr, uid, ids, *args, **kwargs)
def write(self, cr, uid, ids, vals, context=None):
if context is None:
context = {}
task_work_obj = self.pool['project.task.work']
acc_id = False
missing_analytic_entries = {}
if vals.get('project_id',False) or vals.get('name',False):
vals_line = {}
hr_anlytic_timesheet = self.pool.get('hr.analytic.timesheet')
if vals.get('project_id',False):
project_obj = self.pool.get('project.project').browse(cr, uid, vals['project_id'], context=context)
acc_id = project_obj.analytic_account_id.id
for task_obj in self.browse(cr, uid, ids, context=context):
if len(task_obj.work_ids):
for task_work in task_obj.work_ids:
if not task_work.hr_analytic_timesheet_id:
if acc_id :
# missing timesheet activities to generate
missing_analytic_entries[task_work.id] = {
'name' : task_work.name,
'user_id' : task_work.user_id.id,
'date' : task_work.date,
'account_id': acc_id,
'hours' : task_work.hours,
'task_id' : task_obj.id
}
continue
line_id = task_work.hr_analytic_timesheet_id.id
if vals.get('project_id',False):
vals_line['account_id'] = acc_id
if vals.get('name',False):
vals_line['name'] = '%s: %s' % (tools.ustr(vals['name']), tools.ustr(task_work.name) or '/')
hr_anlytic_timesheet.write(cr, uid, [line_id], vals_line, {})
res = super(task,self).write(cr, uid, ids, vals, context)
for task_work_id, analytic_entry in missing_analytic_entries.items():
timeline_id = task_work_obj._create_analytic_entries(cr, uid, analytic_entry, context=context)
task_work_obj.write(cr, uid, task_work_id, {'hr_analytic_timesheet_id' : timeline_id}, context=context)
# Compute: effective_hours, total_hours, progress
def _hours_get(self, cr, uid, ids, field_names, args, context=None):
res = {}
tasks_data = self.pool['account.analytic.line'].read_group(cr, uid, [('task_id', 'in', ids)], ['task_id','unit_amount'], ['task_id'], context=context)
for data in tasks_data:
task = self.browse(cr, uid, data['task_id'][0], context=context)
res[data['task_id'][0]] = {'effective_hours': data.get('unit_amount', 0.0), 'remaining_hours': task.planned_hours - data.get('unit_amount', 0.0)}
res[data['task_id'][0]]['total_hours'] = res[data['task_id'][0]]['remaining_hours'] + data.get('unit_amount', 0.0)
res[data['task_id'][0]]['delay_hours'] = res[data['task_id'][0]]['total_hours'] - task.planned_hours
res[data['task_id'][0]]['progress'] = 0.0
if (task.planned_hours > 0.0 and data.get('unit_amount', 0.0)):
res[data['task_id'][0]]['progress'] = round(min(100.0 * data.get('unit_amount', 0.0) / task.planned_hours, 99.99),2)
# TDE CHECK: if task.state in ('done','cancelled'):
if task.stage_id and task.stage_id.fold:
res[data['task_id'][0]]['progress'] = 100.0
return res
def _get_task(self, cr, uid, id, context=None):
res = []
for line in self.pool.get('account.analytic.line').search_read(cr,uid,[('task_id', '!=', False),('id','in',id)], context=context):
res.append(line['task_id'][0])
return res
_columns = {
'remaining_hours': fields.function(_hours_get, string='Remaining Hours', multi='line_id', help="Total remaining time, can be re-estimated periodically by the assignee of the task.",
store = {
'project.task': (lambda self, cr, uid, ids, c={}: ids, ['timesheet_ids', 'remaining_hours', 'planned_hours'], 10),
'account.analytic.line': (_get_task, ['task_id', 'unit_amount'], 10),
}),
'effective_hours': fields.function(_hours_get, string='Hours Spent', multi='line_id', help="Computed using the sum of the task work done.",
store = {
'project.task': (lambda self, cr, uid, ids, c={}: ids, ['timesheet_ids', 'remaining_hours', 'planned_hours'], 10),
'account.analytic.line': (_get_task, ['task_id', 'unit_amount'], 10),
}),
'total_hours': fields.function(_hours_get, string='Total', multi='line_id', help="Computed as: Time Spent + Remaining Time.",
store = {
'project.task': (lambda self, cr, uid, ids, c={}: ids, ['timesheet_ids', 'remaining_hours', 'planned_hours'], 10),
'account.analytic.line': (_get_task, ['task_id', 'unit_amount'], 10),
}),
'progress': fields.function(_hours_get, string='Working Time Progress (%)', multi='line_id', group_operator="avg", help="If the task has a progress of 99.99% you should close the task if it's finished or reevaluate the time",
store = {
'project.task': (lambda self, cr, uid, ids, c={}: ids, ['timesheet_ids', 'remaining_hours', 'planned_hours', 'state', 'stage_id'], 10),
'account.analytic.line': (_get_task, ['task_id', 'unit_amount'], 10),
}),
'delay_hours': fields.function(_hours_get, string='Delay Hours', multi='line_id', help="Computed as difference between planned hours by the project manager and the total hours of the task.",
store = {
'project.task': (lambda self, cr, uid, ids, c={}: ids, ['timesheet_ids', 'remaining_hours', 'planned_hours'], 10),
'account.analytic.line': (_get_task, ['task_id', 'unit_amount'], 10),
}),
'timesheet_ids': fields.one2many('account.analytic.line', 'task_id', 'Timesheets'),
'analytic_account_id': fields.related('project_id', 'analytic_account_id',
type='many2one', relation='account.analytic.account', string='Analytic Account', store=True),
}
_defaults = {
'progress': 0,
}
def _prepare_delegate_values(self, cr, uid, ids, delegate_data, context=None):
vals = super(task, self)._prepare_delegate_values(cr, uid, ids, delegate_data, context)
for task in self.browse(cr, uid, ids, context=context):
vals[task.id]['planned_hours'] += task.effective_hours
return vals
class res_partner(osv.osv):
_inherit = 'res.partner'
@@ -295,28 +240,8 @@ class res_partner(osv.osv):
return super(res_partner,self).unlink(cursor, user, ids,
context=context)
class account_analytic_line(osv.osv):
_inherit = "account.analytic.line"
def get_product(self, cr, uid, context=None):
emp_obj = self.pool.get('hr.employee')
emp_ids = emp_obj.search(cr, uid, [('user_id', '=', uid)], context=context)
if emp_ids:
employee = emp_obj.browse(cr, uid, emp_ids, context=context)[0]
if employee.product_id:return employee.product_id.id
return False
_defaults = {'product_id': get_product,}
def on_change_account_id(self, cr, uid, ids, account_id):
res = {}
if not account_id:
return res
res.setdefault('value',{})
acc = self.pool.get('account.analytic.account').browse(cr, uid, account_id)
st = acc.to_invoice.id
res['value']['to_invoice'] = st or False
if acc.state == 'close' or acc.state == 'cancelled':
raise UserError(_('You cannot select a Analytic Account which is in Close or Cancelled state.'))
return res
_inherit = "account.analytic.line"
_columns = {
'task_id' : fields.many2one('project.task', 'Task'),
}
@@ -2,6 +2,20 @@
<openerp>
<data>
<record id="view_project_tree_inherited" model="ir.ui.view">
<field name="name">project.project.tree.inherited</field>
<field name="model">project.project</field>
<field name="inherit_id" ref="project.view_project"/>
<field name="arch" type="xml">
<field name="partner_id" position="after">
<field name="planned_hours" widget="float_time"/>
<field name="total_hours" widget="float_time"/>
<field name="effective_hours" widget="float_time"/>
<field name="progress_rate" widget="progressbar"/>
</field>
</field>
</record>
<record id="project_invoice_form" model="ir.ui.view">
<field name="name">Inherit project form : Invoicing Data</field>
<field name="model">project.project</field>
@@ -20,6 +34,12 @@
<field name="analytic_account_id" readonly="1" required="0" class="oe_inline" nolabel="1"/>.
</p>
</xpath>
<field name="privacy_visibility" position="after">
<field name="planned_hours" widget="float_time"
groups="project.group_time_work_estimation_tasks"/>
<field name="effective_hours" widget="float_time"
groups="project.group_time_work_estimation_tasks"/>
</field>
<xpath expr='//div[@name="buttons"]' position="inside">
<button class="oe_inline oe_stat_button" name="open_timesheets" type="object" attrs="{'invisible':[('invoice_on_timesheets','=',False)]}"
icon="fa-calendar" string="Timesheets"/>
@@ -40,6 +60,7 @@
</xpath>
</field>
</record>
<record id="view_account_analytic_line_search_account_inherit" model="ir.ui.view">
<field name="name">account.analytic.line.search.account_id</field>
<field name="model">account.analytic.line</field>
@@ -48,6 +69,9 @@
<field name="account_id" position="replace">
<field name="account_id" string="Analytic account/project"/>
</field>
<field name="account_id" position="after">
<field name="task_id"/>
</field>
</field>
</record>
<record id="view_account_analytic_line_form_inherit_account_id" model="ir.ui.view">
@@ -56,17 +80,20 @@
<field name="inherit_id" ref="analytic.view_account_analytic_line_form"/>
<field name="arch" type="xml">
<field name="account_id" position="replace">
<field name="account_id" string="Analytic Account/Project" on_change="on_change_account_id(account_id)"/>
<field name="account_id" string="Analytic Account/Project"/>
</field>
</field>
</record>
<record id="view_account_analytic_line_tree_inherit_account_id" model="ir.ui.view">
<field name="name">account.analytic.line.tree.account_id</field>
<field name="model">account.analytic.line</field>
<field name="inherit_id" ref="analytic.view_account_analytic_line_tree"/>
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_tree"/>
<field name="arch" type="xml">
<field name="account_id" position="replace">
<field name="account_id" string="Analytic account/project" on_change="on_change_account_id(account_id)"/>
<field name="account_id" position="attributes">
<attribute name="string">Analytic account/project</attribute>
</field>
<field name="account_id" position="after">
<field name="task_id" domain="[('project_id.analytic_account_id', '=' , account_id)]"/>
</field>
</field>
</record>
@@ -118,5 +145,65 @@
<menuitem id="menu_invoicing_contracts" parent="menu_project_billing" sequence="4"
action="account_analytic_analysis.action_account_analytic_overdue"/>
<record model="ir.ui.view" id="view_task_form2_inherited">
<field name="name">project.task.form.inherited</field>
<field name="model">project.task</field>
<field name="inherit_id" ref="project.view_task_form2" />
<field name="arch" type="xml">
<field name="categ_ids" position="after">
<field name="analytic_account_id" invisible="1"/>
<field name="progress" widget="progressbar"
groups="project.group_time_work_estimation_tasks"/>
</field>
<xpath expr="//notebook/page[@name='description_page']" position="after">
<page string="Timesheets">
<field name="timesheet_ids" groups="project.group_tasks_work_on_tasks" context="{'default_account_id' : analytic_account_id, 'default_is_timesheet' : 1}">
<tree editable="top" string="Timesheet Activities">
<field name="date"/>
<field name="user_id" required="1"/>
<field name="name"/>
<field name="account_id" on_change="on_change_account_id(account_id, user_id, unit_amount, is_timesheet, context)"/>
<field name="unit_amount" string="Duration" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id, context)" sum="Total time" widget="float_time"/>
<field name="to_invoice"/>
<field name="product_uom_id" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id, context)" invisible="1"/>
<field name="journal_id" invisible="1"/>
<field name="amount" sum="Total cost" invisible="1"/>
<field name="general_account_id" invisible="1"/>
<field name="product_id" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id, context)" required="1" domain="[('type','=','service')]" invisible="1"/>
<field name="is_timesheet" invisible="1"/>
</tree>
</field>
<group>
<group class="oe_subtotal_footer oe_right" name="project_hours" groups="project.group_time_work_estimation_tasks">
<field name="effective_hours" widget="float_time" groups="project.group_time_work_estimation_tasks"/>
<field name="remaining_hours" widget="float_time" class="oe_subtotal_footer_separator" groups="project.group_time_work_estimation_tasks"/>
</group>
</group>
</page>
</xpath>
<xpath expr="//tree[@name='delegated_task']" position="inside">
<field name="effective_hours" widget="float_time"/>
<field name="progress" widget="progressbar"/>
<field name="remaining_hours" widget="float_time"/>
</xpath>
</field>
</record>
<record id="view_task_tree2_inherited" model="ir.ui.view">
<field name="name">project.task.tree.inherited</field>
<field name="model">project.task</field>
<field name="inherit_id" ref="project.view_task_tree2" />
<field eval="2" name="priority"/>
<field name="arch" type="xml">
<field name="planned_hours" position="after">
<field name="remaining_hours" widget="float_time" sum="Remaining Hours" on_change="onchange_remaining(remaining_hours,planned_hours)" invisible="context.get('set_visible',False)" groups="project.group_time_work_estimation_tasks"/>
<field name="total_hours" invisible="1"/>
<field name="effective_hours" widget="float_time" sum="Spent Hours" invisible="1"/>
</field>
<field name="date_end" position="after">
<field name="progress" widget="progressbar" invisible="context.get('set_visible',False)"/>
</field>
</field>
</record>
</data>
</openerp>
@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
import project_report
@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
from datetime import datetime
from dateutil.relativedelta import relativedelta
from openerp.osv import fields,osv
from openerp import tools
class report_project_task_user(osv.Model):
_inherit = "report.project.task.user"
_columns = {
'hours_planned': fields.float('Planned Hours', readonly=True),
'hours_effective': fields.float('Effective Hours', readonly=True),
'hours_delay': fields.float('Avg. Plan.-Eff.', readonly=True),
'remaining_hours': fields.float('Remaining Hours', readonly=True),
'progress': fields.float('Progress', readonly=True, group_operator='avg'),
'total_hours': fields.float('Total Hours', readonly=True),
}
def _select(self):
return super(report_project_task_user, self)._select() + ", progress as progress, t.effective_hours as hours_effective, remaining_hours as remaining_hours, total_hours as total_hours, t.delay_hours as hours_delay, planned_hours as hours_planned"
def _group_by(self):
return super(report_project_task_user, self)._group_by() + ", remaining_hours, t.effective_hours, progress, total_hours, planned_hours, hours_delay"
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_task_project_user_graph_inherited" model="ir.ui.view">
<field name="name">report.project.task.user.graph.inherited</field>
<field name="model">report.project.task.user</field>
<field name="inherit_id" ref="project.view_task_project_user_graph" />
<field name="arch" type="xml">
<graph string="Tasks Analysis" type="pivot">
<field name="project_id" position="after">
<field name="total_hours" type="measure"/>
<field name="hours_planned" type="measure"/>
<field name="remaining_hours" type="measure"/>
</field>
</graph>
</field>
</record>
</data>
</openerp>
@@ -1,4 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_hr_analytic_timesheet_user,hr.analytic.timesheet.user,hr_timesheet.model_hr_analytic_timesheet,project.group_project_user,1,1,1,0
access_sale_order_manager,sale.order,sale.model_sale_order,project.group_project_manager,1,0,0,0
access_sale_order_user,sale.order,sale.model_sale_order,project.group_project_user,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
access_hr_analytic_timesheet_user hr.analytic.timesheet.user hr_timesheet.model_hr_analytic_timesheet project.group_project_user 1 1 1 0
2 access_sale_order_manager sale.order sale.model_sale_order project.group_project_manager 1 0 0 0
3 access_sale_order_user sale.order sale.model_sale_order project.group_project_user 1 0 0 0
@@ -1,22 +0,0 @@
-
I set task work entry for Employee
-
!record {model: project.task.work, id: project_task_work1}:
date: !eval time.strftime('%Y-%m-%d %H:%M:%S')
name: Test Timesheet records
task_id: project.project_task_10
hours: 20.0
user_id: base.user_demo
company_id: base.main_company
-
I check Timesheet line for employee in current Timesheet
-
!python {model: hr.analytic.timesheet}: |
from openerp.osv import fields
start = fields.date.context_today(self, cr, uid, context=context)
task_work = self.search(cr, uid, [("name","=","Social network integration: Test Timesheet records")],context)[0]
task_ids = self.browse(cr, uid, task_work, context)
assert task_ids.user_id.id == ref("base.user_demo"), 'Error, The User in Timesheet is not Correct'
assert task_ids.date == start, 'Error, The Date in Timesheet is not ok'
assert task_ids.product_uom_id.name == "Hour(s)", 'Error, The Hour in Timesheet is not ok'
@@ -30,6 +30,19 @@
company_id: base.main_company
name: HR Timesheet
type: general
-
Create a user type
-
!record {model: account.account.type, id: user_type0}:
name: Other
code: 990099
-
Create a financial account
-
!record {model: account.account, id: financial_account0}:
name: expenses
code: 9999999
user_type: user_type0
-
Create an employee 'HR Manager' for user 'HR Manager'
-
@@ -58,8 +71,11 @@
Make a work task entry 'Get work calendar of all employees' of 10 hours done by HR manager
-
!record {model: project.task, id: project_task_getalltimesheetrecords0}:
work_ids:
timesheet_ids:
- date: !eval time.strftime('%Y-05-%d %H:%M:%S')
hours: 10.0
unit_amount: 10.0
name: Get work calendar of all employees
user_id: res_users_hrmanager0
account_id: analytic.analytic_administratif
journal_id: account_analytic_journal_hrtimesheet0
general_account_id: financial_account0