summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--addons/account_analytic_analysis/account_analytic_analysis.py2
-rw-r--r--addons/account_analytic_analysis/account_analytic_analysis_view.xml10
-rw-r--r--addons/account_analytic_analysis/analytic_account_demo.xml2
-rw-r--r--addons/analytic/models/analytic.py3
-rw-r--r--addons/analytic_user_function/analytic_user_function.py135
-rw-r--r--addons/analytic_user_function/analytic_user_function_view.xml42
-rw-r--r--addons/hr_timesheet/__openerp__.py2
-rw-r--r--addons/hr_timesheet/hr_timesheet.py305
-rw-r--r--addons/hr_timesheet/hr_timesheet_demo.xml15
-rw-r--r--addons/hr_timesheet/hr_timesheet_view.xml48
-rw-r--r--addons/hr_timesheet/report/hr_timesheet_report.py45
-rw-r--r--addons/hr_timesheet/security/hr_timesheet_security.xml20
-rw-r--r--addons/hr_timesheet/security/ir.model.access.csv6
-rw-r--r--addons/hr_timesheet/test/hr_timesheet_demo.yml11
-rw-r--r--addons/hr_timesheet_invoice/__openerp__.py3
-rw-r--r--addons/hr_timesheet_invoice/hr_timesheet_invoice.py94
-rw-r--r--addons/hr_timesheet_invoice/hr_timesheet_invoice_view.xml173
-rw-r--r--addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice.yml2
-rw-r--r--addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice_no_prod_tax.yml2
-rw-r--r--addons/hr_timesheet_sheet/hr_timesheet_sheet.py100
-rw-r--r--addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml73
-rw-r--r--addons/hr_timesheet_sheet/report/hr_timesheet_report.py8
-rw-r--r--addons/hr_timesheet_sheet/security/ir.model.access.csv1
-rw-r--r--addons/hr_timesheet_sheet/static/src/js/timesheet.js12
-rw-r--r--addons/portal_project/security/ir.model.access.csv2
-rw-r--r--addons/project/project.py218
-rw-r--r--addons/project/project_view.xml80
-rw-r--r--addons/project/report/project_report.py46
-rw-r--r--addons/project/res_config_view.xml2
-rw-r--r--addons/project/security/ir.model.access.csv1
-rw-r--r--addons/project_issue/project_issue.py31
-rw-r--r--addons/project_issue/project_issue_view.xml2
-rw-r--r--addons/project_issue_sheet/__openerp__.py1
-rw-r--r--addons/project_issue_sheet/project_issue_sheet.py26
-rw-r--r--addons/project_issue_sheet/project_issue_sheet_view.xml42
-rw-r--r--addons/project_issue_sheet/security/ir.model.access.csv6
-rw-r--r--addons/project_timesheet/__init__.py1
-rw-r--r--addons/project_timesheet/__openerp__.py7
-rw-r--r--addons/project_timesheet/project_timesheet.py385
-rw-r--r--addons/project_timesheet/project_timesheet_view.xml95
-rw-r--r--addons/project_timesheet/report/__init__.py2
-rw-r--r--addons/project_timesheet/report/project_report.py25
-rw-r--r--addons/project_timesheet/report/project_report_view.xml19
-rw-r--r--addons/project_timesheet/security/ir.model.access.csv1
-rw-r--r--addons/project_timesheet/test/work_timesheet.yml22
-rw-r--r--addons/project_timesheet/test/worktask_entry_to_timesheetline_entry.yml20
46 files changed, 895 insertions, 1253 deletions
diff --git a/addons/account_analytic_analysis/account_analytic_analysis.py b/addons/account_analytic_analysis/account_analytic_analysis.py
index 49e1245c408..04b234d6df8 100644
--- a/addons/account_analytic_analysis/account_analytic_analysis.py
+++ b/addons/account_analytic_analysis/account_analytic_analysis.py
@@ -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 {
diff --git a/addons/account_analytic_analysis/account_analytic_analysis_view.xml b/addons/account_analytic_analysis/account_analytic_analysis_view.xml
index c35dce559df..16bf45c1570 100644
--- a/addons/account_analytic_analysis/account_analytic_analysis_view.xml
+++ b/addons/account_analytic_analysis/account_analytic_analysis_view.xml
@@ -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"
diff --git a/addons/account_analytic_analysis/analytic_account_demo.xml b/addons/account_analytic_analysis/analytic_account_demo.xml
index 85f4e821d9a..b4fb3c58168 100644
--- a/addons/account_analytic_analysis/analytic_account_demo.xml
+++ b/addons/account_analytic_analysis/analytic_account_demo.xml
@@ -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>
diff --git a/addons/analytic/models/analytic.py b/addons/analytic/models/analytic.py
index 7ff3a74e33b..88d4e4f56ec 100644
--- a/addons/analytic/models/analytic.py
+++ b/addons/analytic/models/analytic.py
@@ -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'
diff --git a/addons/analytic_user_function/analytic_user_function.py b/addons/analytic_user_function/analytic_user_function.py
index 855ee4ced47..bbcff141a47 100644
--- a/addons/analytic_user_function/analytic_user_function.py
+++ b/addons/analytic_user_function/analytic_user_function.py
@@ -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"
+class account_analytic_line(osv.osv):
+ _inherit = "account.analytic.line"
+
# 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
+ 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:
- if account.parent_id:
- return self._get_related_user_account_recursiv(cr, uid, user_id, account.parent_id.id)
- else:
- return False
+ return False
- def on_change_account_id(self, cr, uid, ids, account_id, user_id=False, unit_amount=0):
+ 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):
- #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)
+ 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
- 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)
+ # 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
- 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
+ # 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, account_id, unit_amount=0):
- res = super(hr_analytic_timesheet, self).on_change_user_id(cr, uid, ids, user_id)
+ 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
- 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
+ # 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
-class account_analytic_line(osv.osv):
- _inherit = "account.analytic.line"
- def _get_invoice_price(self, cr, uid, account, product_id, user_id, qty, context = {}):
+ # 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)
diff --git a/addons/analytic_user_function/analytic_user_function_view.xml b/addons/analytic_user_function/analytic_user_function_view.xml
index 132f44ebef5..6010970b0ce 100644
--- a/addons/analytic_user_function/analytic_user_function_view.xml
+++ b/addons/analytic_user_function/analytic_user_function_view.xml
@@ -66,7 +66,7 @@
<field name="inherit_id" ref="hr_timesheet_sheet.hr_timesheet_sheet_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='timesheet_ids']/tree/field[@name='account_id']" position="attributes">
- <attribute name="on_change">on_change_account_id(account_id, user_id, unit_amount)</attribute>
+ <attribute name="on_change">on_change_account_id(account_id, user_id, unit_amount, is_timesheet, context)</attribute>
</xpath>
</field>
</record>
@@ -79,8 +79,7 @@
<field name="inherit_id" ref="hr_timesheet_sheet.hr_timesheet_sheet_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='timesheet_ids']/form/field[@name='account_id']" position="attributes">
- <attribute name="domain">[('type','=','normal'),('state', '&lt;&gt;', 'close')]</attribute>
- <attribute name="on_change">on_change_account_id(account_id, user_id, unit_amount)</attribute>
+ <attribute name="on_change">on_change_account_id(account_id, user_id, unit_amount, is_timesheet, context)</attribute>
<attribute name="context">{'default_invoice_on_timesheets': 1}</attribute>
</xpath>
</field>
@@ -88,50 +87,39 @@
<!-- hr.analytic.timesheet inherited views -->
<record model="ir.ui.view" id="hr_timesheet_line_form_inherit">
- <field name="name">hr.analytic.timesheet.form</field>
- <field name="model">hr.analytic.timesheet</field>
- <field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_form"/>
+ <field name="name">account.analytic.line.form</field>
+ <field name="model">account.analytic.line</field>
+ <field name="inherit_id" ref="hr_timesheet_invoice.hr_timesheet_line_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='account_id']" position="attributes">
- <attribute name="domain">[('type','=','normal'),('state', '&lt;&gt;', 'close')]</attribute>
- <attribute name="on_change">on_change_account_id(account_id, user_id, unit_amount)</attribute>
+ <attribute name="on_change">on_change_account_id(account_id, user_id, unit_amount, is_timesheet, context)</attribute>
</xpath>
</field>
</record>
<record model="ir.ui.view" id="hr_timesheet_line_form_inherit_2">
- <field name="name">hr.analytic.timesheet.form</field>
- <field name="model">hr.analytic.timesheet</field>
- <field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_form"/>
+ <field name="name">account.analytic.line.form</field>
+ <field name="model">account.analytic.line</field>
+ <field name="inherit_id" ref="hr_timesheet_invoice.hr_timesheet_line_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='user_id']" position="replace">
- <field name="user_id" required="1" on_change="on_change_user_id(user_id, account_id, unit_amount)"/>
+ <field name="user_id" required="1" on_change="on_change_user_id(user_id, unit_amount, account_id, is_timesheet, context)"/>
</xpath>
</field>
</record>
<record model="ir.ui.view" id="hr_timesheet_line_tree_inherit">
- <field name="name">hr.analytic.timesheet.tree</field>
- <field name="model">hr.analytic.timesheet</field>
- <field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_tree"/>
+ <field name="name">account.analytic.line.tree</field>
+ <field name="model">account.analytic.line</field>
+ <field name="inherit_id" ref="hr_timesheet_invoice.hr_timesheet_line_tree"/>
<field name="arch" type="xml">
<xpath expr="/tree/field[@name='account_id']" position="attributes">
- <attribute name="domain">[('type','=','normal'),('state', '&lt;&gt;', 'close')]</attribute>
- <attribute name="on_change">on_change_account_id(account_id, user_id, unit_amount)</attribute>
+ <attribute name="on_change">on_change_account_id(account_id, user_id, unit_amount, is_timesheet, context)</attribute>
</xpath>
- </field>
- </record>
-
- <record model="ir.ui.view" id="hr_timesheet_line_tree_inherit_2">
- <field name="name">hr.analytic.timesheet.tree</field>
- <field name="model">hr.analytic.timesheet</field>
- <field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_tree"/>
- <field name="arch" type="xml">
<xpath expr="/tree/field[@name='user_id']" position="replace">
- <field name="user_id" required="1" on_change="on_change_user_id(user_id, account_id, unit_amount)" context="{'default_groups_ref': ['base.group_user']}"/>
+ <field name="user_id" required="1" on_change="on_change_user_id(user_id, unit_amount, account_id, is_timesheet, context)" context="{'default_groups_ref': ['base.group_user']}"/>
</xpath>
</field>
</record>
-
</data>
</openerp>
diff --git a/addons/hr_timesheet/__openerp__.py b/addons/hr_timesheet/__openerp__.py
index ff9ebb91d93..61e9e13be0a 100644
--- a/addons/hr_timesheet/__openerp__.py
+++ b/addons/hr_timesheet/__openerp__.py
@@ -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,
diff --git a/addons/hr_timesheet/hr_timesheet.py b/addons/hr_timesheet/hr_timesheet.py
index fcd50b50393..3e2aec8b621 100644
--- a/addons/hr_timesheet/hr_timesheet.py
+++ b/addons/hr_timesheet/hr_timesheet.py
@@ -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
- 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
-
- _defaults = {
- 'journal_id': _getAnalyticJournal,
- 'product_id': _getEmployeeProduct
- }
-
-
-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),
- }
-
- 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
+ @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']
+
+ @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']
+
+ 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 account_analytic_line(models.Model):
+ _inherit = 'account.analytic.line'
+
+ 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,129 +145,40 @@ 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,))
-
-
- _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),
- }}
+ @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.'))
-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"),
- }
- 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)
- if template_id and 'value' in res:
- template = self.browse(cr, uid, template_id, context=context)
- res['value']['invoice_on_timesheets'] = template.invoice_on_timesheets
- return res
+ 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")
- def onchange_invoice_on_timesheets(self, cr, uid, ids, invoice_on_timesheets, context=None):
+ @api.onchange('invoice_on_timesheets')
+ def onchange_invoice_on_timesheets(self):
result = {}
- if not invoice_on_timesheets:
+ if not self.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')
+ 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)
+ if template_id and 'value' in res:
+ template = self.browse(cr, uid, template_id, context=context)
+ res['value']['invoice_on_timesheets'] = template.invoice_on_timesheets
+ return res
diff --git a/addons/hr_timesheet/hr_timesheet_demo.xml b/addons/hr_timesheet/hr_timesheet_demo.xml
index 312f118a619..85df3005d15 100644
--- a/addons/hr_timesheet/hr_timesheet_demo.xml
+++ b/addons/hr_timesheet/hr_timesheet_demo.xml
@@ -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">
diff --git a/addons/hr_timesheet/hr_timesheet_view.xml b/addons/hr_timesheet/hr_timesheet_view.xml
index 3ec03627932..ec83e80b2ca 100644
--- a/addons/hr_timesheet/hr_timesheet_view.xml
+++ b/addons/hr_timesheet/hr_timesheet_view.xml
@@ -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>
diff --git a/addons/hr_timesheet/report/hr_timesheet_report.py b/addons/hr_timesheet/report/hr_timesheet_report.py
index 20673770c40..1566f83fd79 100644
--- a/addons/hr_timesheet/report/hr_timesheet_report.py
+++ b/addons/hr_timesheet/report/hr_timesheet_report.py
@@ -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()))
diff --git a/addons/hr_timesheet/security/hr_timesheet_security.xml b/addons/hr_timesheet/security/hr_timesheet_security.xml
deleted file mode 100644
index c5549d1ed27..00000000000
--- a/addons/hr_timesheet/security/hr_timesheet_security.xml
+++ /dev/null
@@ -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>
diff --git a/addons/hr_timesheet/security/ir.model.access.csv b/addons/hr_timesheet/security/ir.model.access.csv
index 757cd84f4b5..c6317873ac8 100644
--- a/addons/hr_timesheet/security/ir.model.access.csv
+++ b/addons/hr_timesheet/security/ir.model.access.csv
@@ -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 \ No newline at end of file
diff --git a/addons/hr_timesheet/test/hr_timesheet_demo.yml b/addons/hr_timesheet/test/hr_timesheet_demo.yml
deleted file mode 100644
index 2c81912d130..00000000000
--- a/addons/hr_timesheet/test/hr_timesheet_demo.yml
+++ /dev/null
@@ -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
diff --git a/addons/hr_timesheet_invoice/__openerp__.py b/addons/hr_timesheet_invoice/__openerp__.py
index 1eb003b1e4f..35630b0b3df 100644
--- a/addons/hr_timesheet_invoice/__openerp__.py
+++ b/addons/hr_timesheet_invoice/__openerp__.py
@@ -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',
diff --git a/addons/hr_timesheet_invoice/hr_timesheet_invoice.py b/addons/hr_timesheet_invoice/hr_timesheet_invoice.py
index 05e772e30e9..3e26dec67a9 100644
--- a/addons/hr_timesheet_invoice/hr_timesheet_invoice.py
+++ b/addons/hr_timesheet_invoice/hr_timesheet_invoice.py
@@ -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,
diff --git a/addons/hr_timesheet_invoice/hr_timesheet_invoice_view.xml b/addons/hr_timesheet_invoice/hr_timesheet_invoice_view.xml
index 55aaa0d50ae..967da3c3f17 100644
--- a/addons/hr_timesheet_invoice/hr_timesheet_invoice_view.xml
+++ b/addons/hr_timesheet_invoice/hr_timesheet_invoice_view.xml
@@ -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>
-
- <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">
- <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>
+ </header>
+ </xpath>
</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="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>
- </field>
- </record>
+ </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>
+ <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" 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>
-
- <!-- Inherits for account analytic lines -->
- <record id="view_account_analytic_line_search_inherit" model="ir.ui.view">
- <field name="name">account.analytic.line.search.to_invoice</field>
- <field name="model">account.analytic.line</field>
- <field name="inherit_id" ref="analytic.view_account_analytic_line_filter"/>
- <field name="arch" type="xml">
- <xpath expr="//field[@name='date']" position="after">
- <separator/>
- <filter name="to_invoice" string="To Invoice" context="{'to_invoice': 1}" domain="[('invoice_id','=',False), ('to_invoice','&lt;&gt;',False)]" icon="terp-dolar"/>
- <filter name="invoiced" string="Invoiced" domain="[('invoice_id','!=',False), ('to_invoice','&lt;&gt;',False)]" icon="terp-dolar"/>
- </xpath>
- </field>
- </record>
-
- <record id="view_hr_timesheet_line_search_to_invoice" model="ir.ui.view">
- <field name="name">hr.analytic.timesheet.search.to_invoice</field>
- <field name="model">hr.analytic.timesheet</field>
- <field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_search"/>
- <field name="arch" type="xml">
- <xpath expr="//field[@name='account_id']" position="after">
- <separator/>
- <filter name="to_invoice" string="To Invoice" context="{'to_invoice': 1}" domain="[('invoice_id','=',False), ('to_invoice','&lt;&gt;',False)]" icon="terp-dolar"/>
- <filter name="invoiced" string="Invoiced" domain="[('invoice_id','!=',False), ('to_invoice','&lt;&gt;',False)]" icon="terp-dolar"/>
- </xpath>
- </field>
- </record>
+ </record>
-
- <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"/>
+ <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>
<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>
diff --git a/addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice.yml b/addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice.yml
index 3d852a53540..29bb6363ebe 100644
--- a/addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice.yml
+++ b/addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice.yml
@@ -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'))
-
diff --git a/addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice_no_prod_tax.yml b/addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice_no_prod_tax.yml
index 15b243d0ac8..6c2adc1862a 100644
--- a/addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice_no_prod_tax.yml
+++ b/addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice_no_prod_tax.yml
@@ -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
diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py
index 953cb1942aa..90a9c8550bf 100644
--- a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py
+++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py
@@ -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,38 +293,36 @@ class hr_timesheet_sheet(osv.osv):
return dom
+class account_analytic_account(osv.osv):
+ _inherit = "account.analytic.account"
+
+ def name_create(self, cr, uid, name, context=None):
+ if context is None:
+ context = {}
+ group_template_required = self.pool['res.users'].has_group(cr, uid, 'account_analytic_analysis.group_template_required')
+ if not context.get('default_invoice_on_timesheets') or group_template_required:
+ 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 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)
+ # 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 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()
+ # 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 = {}
- group_template_required = self.pool['res.users'].has_group(cr, uid, 'account_analytic_analysis.group_template_required')
- if not context.get('default_invoice_on_timesheets') or group_template_required:
- 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"
-
def _sheet(self, cursor, user, ids, name, args, context=None):
sheet_obj = self.pool.get('hr_timesheet_sheet.sheet')
res = {}.fromkeys(ids, False)
@@ -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
)""")
diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml b/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml
index aa5685d3d4a..daedf0686f0 100644
--- a/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml
+++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml
@@ -42,9 +42,9 @@
<record model="ir.actions.act_window" id="act_hr_timesheet_sheet_sheet_2_hr_analytic_timesheet">
- <field name="context">{'search_default_sheet_id': [active_id]}</field>
+ <field name="context">{'search_default_sheet_id': [active_id], 'default_is_timesheet' : 1}</field>
<field name="name">Timesheet Activities</field>
- <field name="res_model">hr.analytic.timesheet</field>
+ <field name="res_model">account.analytic.line</field>
<field name="src_model">hr_timesheet_sheet.sheet</field>
</record>
@@ -56,6 +56,23 @@
<field name="src_model">hr_timesheet_sheet.sheet</field>
</record>
+ <record id="hr_timesheet_sheet_tree_simplified" model="ir.ui.view">
+ <field name="name">hr.timesheet.sheet.tree</field>
+ <field name="model">hr_timesheet_sheet.sheet</field>
+ <field name="arch" type="xml">
+ <tree colors="blue:state == 'draft';black:state in ('confirm','new');gray:state == 'done'" fonts="bold: message_unread == True" string="Timesheets">
+ <field name="employee_id"/>
+ <field name="date_from"/>
+ <field name="date_to"/>
+ <field name="department_id" invisible="1"/>
+ <field name="total_attendance" widget="float_time" groups="base.group_hr_attendance"/>
+ <field name="total_timesheet" widget="float_time"/>
+ <field name="total_difference" widget="float_time" groups="base.group_hr_attendance"/>
+ <field name="state"/>
+ <field name="message_unread" invisible="1"/>
+ </tree>
+ </field>
+ </record>
<record id="hr_timesheet_sheet_form" model="ir.ui.view">
<field name="name">hr.timesheet.sheet.form</field>
@@ -107,10 +124,10 @@
</widget>
</page>
<page string="Details">
- <field context="{'employee_id': employee_id, 'user_id':user_id, 'timesheet_date_from': date_from, 'timesheet_date_to': date_to}" name="timesheet_ids" nolabel="1">
+ <field context="{'employee_id': employee_id, 'user_id':user_id, 'timesheet_date_from': date_from, 'timesheet_date_to': date_to, 'default_is_timesheet' : 1}" name="timesheet_ids" nolabel="1">
<tree editable="top" string="Timesheet Activities">
<field name="date"/>
- <field domain="[('type','in',['normal', 'contract']), ('state', '&lt;&gt;', 'close'),('invoice_on_timesheets','=',1)]" name="account_id" on_change="on_change_account_id(account_id, user_id)" context="{'default_use_timesheets': 1}"/>
+ <field domain="[('type','in',['normal', 'contract']), ('state', '!=', 'close'),('invoice_on_timesheets','=',1)]" name="account_id" on_change="on_change_account_id(account_id, user_id)" options="{'no_open': True, 'no_create': True}" context="{'default_use_timesheets': 1}"/>
<field name="name"/>
<field name="unit_amount" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)" widget="float_time" string="Hours" sum="Hours"/>
<field name="to_invoice" widget="selection"/>
@@ -120,10 +137,11 @@
<field invisible="1" name="amount"/>
<field invisible="1" name="general_account_id"/>
<field invisible="1" name="user_id" required="1"/>
+ <field name="is_timesheet" invisible="1"/>
</tree>
<form string="Timesheet Activities">
<field name="date"/>
- <field domain="[('type','=','normal'), ('state', '&lt;&gt;', 'close')]" name="account_id" on_change="on_change_account_id(account_id, user_id)"/>
+ <field domain="[('type','=','normal'), ('state', '!=', 'close')]" name="account_id" on_change="on_change_account_id(account_id, user_id)"/>
<field name="name"/>
<field name="unit_amount" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)" widget="float_time"/>
<field name="to_invoice"/>
@@ -133,6 +151,7 @@
<field name="amount"/>
<field name="general_account_id"/>
<field name="user_id" required="1"/>
+ <field name="is_timesheet" invisible="1"/>
</form>
</field>
</page>
@@ -198,7 +217,7 @@
<field name="res_model">hr_timesheet_sheet.sheet</field>
<field name="view_type">form</field>
<field name="view_id" eval="False"/>
- <field name="context">{'search_default_to_approve':1}</field>
+ <field name="context">{'search_default_to_approve':1, 'default_is_timesheet':1}</field>
<field name="search_view_id" ref="view_hr_timesheet_sheet_filter"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
@@ -214,9 +233,15 @@
</field>
</record>
- <menuitem id="menu_timesheet_approvals" name="Approvals" parent="hr_attendance.timesheet_menu_root" sequence="6" groups="base.group_hr_user"/>
- <menuitem action="act_hr_timesheet_sheet_form" id="menu_act_hr_timesheet_sheet_form" parent="menu_timesheet_approvals"
- sequence="11" />
+ <record model="ir.actions.act_window.view" id="action_open_timesheet_sheet_holidays_tree">
+ <field name="sequence" eval="2"/>
+ <field name="view_mode">tree</field>
+ <field name="view_id" ref="hr_timesheet_sheet_tree_simplified"/>
+ <field name="act_window_id" ref="act_hr_timesheet_sheet_form"/>
+ </record>
+
+ <menuitem action="act_hr_timesheet_sheet_form" id="menu_act_hr_timesheet_sheet_form" parent="hr_attendance.menu_hr_time_tracking"
+ sequence="11" groups="base.group_hr_user"/>
<!--
Company inheritancy
@@ -237,12 +262,12 @@
</record>
<!--
- hr.analytic.timesheet inheritancy
+ account.analytic.line inheritancy
-->
<record id="hr_timesheet_line_form" model="ir.ui.view">
- <field name="name">hr.analytic.timesheet.form</field>
- <field name="model">hr.analytic.timesheet</field>
+ <field name="name">account.analytic.line.form</field>
+ <field name="model">account.analytic.line</field>
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_form"/>
<field name="arch" type="xml">
<field name="date" position="after">
@@ -252,8 +277,8 @@
</record>
<record id="hr_timesheet_line_search" model="ir.ui.view">
- <field name="name">hr.analytic.timesheet.search</field>
- <field name="model">hr.analytic.timesheet</field>
+ <field name="name">account.analytic.line.search</field>
+ <field name="model">account.analytic.line</field>
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_search"/>
<field name="arch" type="xml">
<field name="date" position="before">
@@ -324,24 +349,6 @@
res_model="hr_timesheet_sheet.sheet.day"
src_model="hr_timesheet_sheet.sheet"/>
- <record id="hr_timesheet_sheet_tree_simplified" model="ir.ui.view">
- <field name="name">hr.timesheet.sheet.tree</field>
- <field name="model">hr_timesheet_sheet.sheet</field>
- <field eval="10" name="priority"/>
- <field name="arch" type="xml">
- <tree colors="blue:state == 'draft';black:state in ('confirm','new');gray:state == 'done'" fonts="bold: message_unread == True" string="Timesheets">
- <field name="employee_id"/>
- <field name="date_from"/>
- <field name="date_to"/>
- <field name="department_id" invisible="1"/>
- <field name="total_attendance" widget="float_time" groups="base.group_hr_attendance"/>
- <field name="total_timesheet" widget="float_time"/>
- <field name="total_difference" widget="float_time" groups="base.group_hr_attendance"/>
- <field name="state"/>
- <field name="message_unread" invisible="1"/>
- </tree>
- </field>
- </record>
<record id="hr_timesheet_day_tree" model="ir.ui.view">
<field name="name">hr.timesheet.day.tree</field>
@@ -363,7 +370,7 @@
<field name="view_type">form</field>
<field name="name">Timesheets</field>
<field name="view_mode">tree,form</field>
- <field name="context">{'search_default_employee_id': [active_id], 'default_employee_id': active_id}</field>
+ <field name="context">{'search_default_employee_id': [active_id], 'default_employee_id': active_id , 'default_is_timesheet' : 1}</field>
</record>
<record id="hr_timesheet_sheet_employee_extd_form" model="ir.ui.view">
diff --git a/addons/hr_timesheet_sheet/report/hr_timesheet_report.py b/addons/hr_timesheet_sheet/report/hr_timesheet_report.py
index 6d41cc6443d..8aaea5d2606 100644
--- a/addons/hr_timesheet_sheet/report/hr_timesheet_report.py
+++ b/addons/hr_timesheet_sheet/report/hr_timesheet_report.py
@@ -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() + """,
diff --git a/addons/hr_timesheet_sheet/security/ir.model.access.csv b/addons/hr_timesheet_sheet/security/ir.model.access.csv
index 2ad3c7d5ca2..d3185141612 100644
--- a/addons/hr_timesheet_sheet/security/ir.model.access.csv
+++ b/addons/hr_timesheet_sheet/security/ir.model.access.csv
@@ -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
diff --git a/addons/hr_timesheet_sheet/static/src/js/timesheet.js b/addons/hr_timesheet_sheet/static/src/js/timesheet.js
index 805333795e4..c99a3593d7e 100644
--- a/addons/hr_timesheet_sheet/static/src/js/timesheet.js
+++ b/addons/hr_timesheet_sheet/static/src/js/timesheet.js
@@ -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, {
diff --git a/addons/portal_project/security/ir.model.access.csv b/addons/portal_project/security/ir.model.access.csv
index d2a7287e355..45998a38841 100644
--- a/addons/portal_project/security/ir.model.access.csv
+++ b/addons/portal_project/security/ir.model.access.csv
@@ -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 \ No newline at end of file
diff --git a/addons/project/project.py b/addons/project/project.py
index af4789fd0b5..a02947c7387 100644
--- a/addons/project/project.py
+++ b/addons/project/project.py
@@ -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,6 +873,29 @@ 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 _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):
+ 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,
+ 'user_id': delegate_data['user_id'] and delegate_data['user_id'][0] or False,
+ 'planned_hours': delegate_data['planned_hours'] or 0.0,
+ 'parent_ids': [(6, 0, [task.id])],
+ 'description': delegate_data['new_task_description'] or '',
+ 'child_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.
@@ -992,24 +904,25 @@ class task(osv.osv):
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': 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,
- 'user_id': delegate_data['user_id'] and delegate_data['user_id'][0] or False,
- 'planned_hours': delegate_data['planned_hours'] or 0.0,
- 'parent_ids': [(6, 0, [task.id])],
- 'description': delegate_data['new_task_description'] or '',
- 'child_ids': [],
- 'work_ids': []
+ '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'
diff --git a/addons/project/project_view.xml b/addons/project/project_view.xml
index 09f17030249..8e78ee03a18 100644
--- a/addons/project/project_view.xml
+++ b/addons/project/project_view.xml
@@ -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>
diff --git a/addons/project/report/project_report.py b/addons/project/report/project_report.py
index dd7a558feba..e607b9729b4 100644
--- a/addons/project/report/project_report.py
+++ b/addons/project/report/project_report.py
@@ -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()))
diff --git a/addons/project/res_config_view.xml b/addons/project/res_config_view.xml
index 684ee49cfca..05c5e4bc42b 100644
--- a/addons/project/res_config_view.xml
+++ b/addons/project/res_config_view.xml
@@ -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>
diff --git a/addons/project/security/ir.model.access.csv b/addons/project/security/ir.model.access.csv
index 28ac60f87bd..40541c94a77 100644
--- a/addons/project/security/ir.model.access.csv
+++ b/addons/project/security/ir.model.access.csv
@@ -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
diff --git a/addons/project_issue/project_issue.py b/addons/project_issue/project_issue.py
index fa22f9cd888..0f32f60a1f1 100644
--- a/addons/project_issue/project_issue.py
+++ b/addons/project_issue/project_issue.py
@@ -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 = {
diff --git a/addons/project_issue/project_issue_view.xml b/addons/project_issue/project_issue_view.xml
index 78d49bee9ad..d09319bcf3c 100644
--- a/addons/project_issue/project_issue_view.xml
+++ b/addons/project_issue/project_issue_view.xml
@@ -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"/>
diff --git a/addons/project_issue_sheet/__openerp__.py b/addons/project_issue_sheet/__openerp__.py
index eb264d865b3..38003c358f0 100644
--- a/addons/project_issue_sheet/__openerp__.py
+++ b/addons/project_issue_sheet/__openerp__.py
@@ -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',
diff --git a/addons/project_issue_sheet/project_issue_sheet.py b/addons/project_issue_sheet/project_issue_sheet.py
index ef810db095e..7b298ded6d3 100644
--- a/addons/project_issue_sheet/project_issue_sheet.py
+++ b/addons/project_issue_sheet/project_issue_sheet.py
@@ -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'),
}
@@ -63,14 +78,5 @@ 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'),
}
diff --git a/addons/project_issue_sheet/project_issue_sheet_view.xml b/addons/project_issue_sheet/project_issue_sheet_view.xml
index 69dc2b41776..7d2eddb6361 100644
--- a/addons/project_issue_sheet/project_issue_sheet_view.xml
+++ b/addons/project_issue_sheet/project_issue_sheet_view.xml
@@ -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>
diff --git a/addons/project_issue_sheet/security/ir.model.access.csv b/addons/project_issue_sheet/security/ir.model.access.csv
index 3273f1f4247..d2b3808f0ae 100644
--- a/addons/project_issue_sheet/security/ir.model.access.csv
+++ b/addons/project_issue_sheet/security/ir.model.access.csv
@@ -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
diff --git a/addons/project_timesheet/__init__.py b/addons/project_timesheet/__init__.py
index 53a5e438263..709a7fd9da2 100644
--- a/addons/project_timesheet/__init__.py
+++ b/addons/project_timesheet/__init__.py
@@ -20,3 +20,4 @@
##############################################################################
import project_timesheet
+import report
diff --git a/addons/project_timesheet/__openerp__.py b/addons/project_timesheet/__openerp__.py
index 4327d7a1871..87dd97ab532 100644
--- a/addons/project_timesheet/__openerp__.py
+++ b/addons/project_timesheet/__openerp__.py
@@ -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,
diff --git a/addons/project_timesheet/project_timesheet.py b/addons/project_timesheet/project_timesheet.py
index c036f4887d1..dada9b17f33 100644
--- a/addons/project_timesheet/project_timesheet.py
+++ b/addons/project_timesheet/project_timesheet.py
@@ -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,219 +162,72 @@ class project_project(osv.osv):
result['help'] = help
return result
+class task(osv.osv):
+ _inherit = "project.task"
-class project_work(osv.osv):
- _inherit = "project.task.work"
-
- def get_user_related_details(self, cr, uid, user_id):
+ # Compute: effective_hours, total_hours, progress
+ def _hours_get(self, cr, uid, ids, field_names, args, context=None):
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
+ 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 _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)
+ 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={
- 'hr_analytic_timesheet_id':fields.many2one('hr.analytic.timesheet','Related Timeline Id', ondelete='set null'),
+ _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,
+ }
-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)
-
- return res
+ 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):
@@ -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'),
+ }
diff --git a/addons/project_timesheet/project_timesheet_view.xml b/addons/project_timesheet/project_timesheet_view.xml
index e725a4296dd..19624bb5870 100644
--- a/addons/project_timesheet/project_timesheet_view.xml
+++ b/addons/project_timesheet/project_timesheet_view.xml
@@ -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>
diff --git a/addons/project_timesheet/report/__init__.py b/addons/project_timesheet/report/__init__.py
new file mode 100644
index 00000000000..334f01258a0
--- /dev/null
+++ b/addons/project_timesheet/report/__init__.py
@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+import project_report \ No newline at end of file
diff --git a/addons/project_timesheet/report/project_report.py b/addons/project_timesheet/report/project_report.py
new file mode 100644
index 00000000000..f3cee3e1cfd
--- /dev/null
+++ b/addons/project_timesheet/report/project_report.py
@@ -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: \ No newline at end of file
diff --git a/addons/project_timesheet/report/project_report_view.xml b/addons/project_timesheet/report/project_report_view.xml
new file mode 100644
index 00000000000..35287e1f14f
--- /dev/null
+++ b/addons/project_timesheet/report/project_report_view.xml
@@ -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> \ No newline at end of file
diff --git a/addons/project_timesheet/security/ir.model.access.csv b/addons/project_timesheet/security/ir.model.access.csv
index e8f7badf40a..ffd4aa2094e 100644
--- a/addons/project_timesheet/security/ir.model.access.csv
+++ b/addons/project_timesheet/security/ir.model.access.csv
@@ -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
diff --git a/addons/project_timesheet/test/work_timesheet.yml b/addons/project_timesheet/test/work_timesheet.yml
deleted file mode 100644
index 9c28ec3a76f..00000000000
--- a/addons/project_timesheet/test/work_timesheet.yml
+++ /dev/null
@@ -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'
-
diff --git a/addons/project_timesheet/test/worktask_entry_to_timesheetline_entry.yml b/addons/project_timesheet/test/worktask_entry_to_timesheetline_entry.yml
index c62c5584516..d1dc5a9214a 100644
--- a/addons/project_timesheet/test/worktask_entry_to_timesheetline_entry.yml
+++ b/addons/project_timesheet/test/worktask_entry_to_timesheetline_entry.yml
@@ -31,6 +31,19 @@
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'
-
!record {model: hr.employee, id: hr_employee_hrmanager0}:
@@ -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