summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorodoo-tac <tac@odoo.com>2015-02-24 16:52:20 +0100
committerodoo-tac <tac@odoo.com>2015-04-28 10:31:18 +0200
commit14183ba2be54a10247419f110f512cc6844a41b4 (patch)
tree1a074c4c2c0a3d46a1ff9f5eb060305365745d87 /addons
parenteb91b14837b20601b9d8cba80829883f85263b8e (diff)
[REF] Refactored hr_timesheet: Refactored the "timesheet" model in several modules. hr.analytic.timesheet is removed, and account.analytic.line is used directly to represent timesheets.
Also removed worklogs on tasks and replaced them by generic timesheets, i.e. account.analytic.line(s)
Diffstat (limited to 'addons')
-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