[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:
@@ -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>
|
||||
|
||||
@@ -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', '<>', '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', '<>', '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', '<>', '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>
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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,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
|
||||
@@ -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','<>',False)]" icon="terp-dolar"/>
|
||||
<filter name="invoiced" string="Invoiced" domain="[('invoice_id','!=',False), ('to_invoice','<>',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','<>',False)]" icon="terp-dolar"/>
|
||||
<filter name="invoiced" string="Invoiced" domain="[('invoice_id','!=',False), ('to_invoice','<>',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
|
||||
|
||||
@@ -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', '<>', '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', '<>', '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
|
||||
|
||||
|
@@ -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
|
||||
|
+46
-174
@@ -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'
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -20,3 +20,4 @@
|
||||
##############################################################################
|
||||
|
||||
import project_timesheet
|
||||
import report
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,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
|
||||
|
||||
Reference in New Issue
Block a user