[IMP] hr_timesheet: Timesheet entries are now related to projects

- Hide account_id field and add a required project_id field on timesheet entries
- Remove is_timesheet field: as project_id is only required for timesheet entries and not for other analytic lines, it can be used to determine wether a line is a timeheet entry or not.
- Remove project_timesheet module as all its functionalities are moved to hr_timesheet
- Replace timesheets on contract option on products by timesheets on project
- Small fixes and improvements in timesheets and projects views and groups
This commit is contained in:
Tanguy Charlier
2016-02-18 01:25:55 +01:00
committed by Antony Lesuisse
parent f3a9f83ff1
commit 16bf40f7c4
42 changed files with 248 additions and 503 deletions
@@ -120,10 +120,6 @@
<field name="view_id"></field> <!-- force empty -->
</record>
<menuitem id="timesheet_menu_root" name="Timesheets" sequence="55" groups="base.group_user,base.group_hr_user,base.group_hr_manager" web_icon="hr_attendance,static/description/icon_timesheet.png"/>
<menuitem id="menu_hr_time_tracking" name="My Timesheet" parent="timesheet_menu_root" sequence="5" />
<menuitem id="menu_hr_to_approve" name="To Approve" parent="timesheet_menu_root" sequence="7"/>
<menuitem id="menu_hr_attendance_root" name="Attendances" sequence="90" groups="base.group_hr_attendance" web_icon="hr_attendance,static/description/icon.png"/>
<menuitem action="open_view_attendance" id="menu_open_view_attendance" parent="menu_hr_attendance_root" sequence="20" groups="base.group_hr_attendance"/>
+1
View File
@@ -2,4 +2,5 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import hr_timesheet
import project_timesheet
import report
+3 -3
View File
@@ -12,8 +12,6 @@ This module implements a timesheet system.
==========================================
Each employee can encode and track their time spent on the different projects.
A project is an analytic account and the time spent on a project generates costs on
the analytic account.
Lots of reporting on time and employee tracking are provided.
@@ -21,10 +19,11 @@ It is completely integrated with the cost accounting module. It allows you to se
up a management by affair.
""",
'website': 'https://www.odoo.com/page/employees',
'depends': ['account', 'hr', 'base', 'hr_attendance'],
'depends': ['account', 'hr', 'project'],
'data': [
'security/ir.model.access.csv',
'hr_timesheet_view.xml',
'project_timesheet_view.xml',
'report/hr_timesheet_report_view.xml',
'hr_timesheet_installer.xml',
'hr_dashboard.xml',
@@ -35,6 +34,7 @@ up a management by affair.
'test': [
'../account/test/account_minimal_test.xml',
'test/hr_timesheet_users.yml',
'test/worktask_entry_to_timesheetline_entry.yml',
],
'installable': True,
'auto_install': False,
+22 -8
View File
@@ -1,15 +1,29 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp import models, fields
from openerp import models, fields, api
class Company(models.Model):
_inherit = 'res.company'
class account_analytic_line(models.Model):
class AccountAnalyticLine(models.Model):
_inherit = 'account.analytic.line'
is_timesheet = fields.Boolean(string="Is a Timesheet")
class account_analytic_account(models.Model):
_inherit = 'account.analytic.account'
use_timesheets = fields.Boolean('Timesheets', help="Check this field if this project manages timesheets", deprecated=True)
task_id = fields.Many2one('project.task', 'Task')
project_id = fields.Many2one('project.project', 'Project')
@api.onchange('project_id')
def onchange_project_id(self):
self.task_id = False
@api.model
def create(self, vals):
if vals.get('project_id'):
project = self.env['project.project'].browse(vals.get('project_id'))
vals['account_id'] = project.analytic_account_id.id
return super(AccountAnalyticLine, self).create(vals)
@api.multi
def write(self, vals):
if vals.get('project_id'):
project = self.env['project.project'].browse(vals.get('project_id'))
vals['account_id'] = project.analytic_account_id.id
return super(AccountAnalyticLine, self).write(vals)
+5 -10
View File
@@ -10,9 +10,8 @@
'unit_amount': 2.00,
'product_id': ref('product.product_product_1'),
'product_uom_id': ref('product.product_uom_hour'),
'account_id': ref('analytic.analytic_administratif'),
'project_id': ref('project.project_project_2'),
'amount': -60.00,
'is_timesheet': True,
}
self._update(cr, uid, 'account.analytic.line', 'hr_timesheet', vals, 'working_hours_requirements')
vals = {
@@ -22,9 +21,8 @@
'unit_amount': 1.00,
'product_id': ref('product.product_product_1'),
'product_uom_id': ref('product.product_uom_hour'),
'account_id': ref('analytic.analytic_administratif'),
'project_id': ref('project.project_project_2'),
'amount': -30.00,
'is_timesheet': True,
}
self._update(cr, uid, 'account.analytic.line', 'hr_timesheet', vals, 'working_hours_design')
vals = {
@@ -34,9 +32,8 @@
'unit_amount': 3.00,
'product_id': ref('product.product_product_1'),
'product_uom_id': ref('product.product_uom_hour'),
'account_id': ref('analytic.analytic_administratif'),
'project_id': ref('project.project_project_2'),
'amount': -90.00,
'is_timesheet': True,
}
self._update(cr, uid, 'account.analytic.line', 'hr_timesheet', vals, 'working_hours_coding')
vals = {
@@ -46,9 +43,8 @@
'unit_amount': 1.00,
'product_id': ref('product.product_product_1'),
'product_uom_id': ref('product.product_uom_hour'),
'account_id': ref('analytic.analytic_administratif'),
'project_id': ref('project.project_project_2'),
'amount': -30.00,
'is_timesheet': True,
}
self._update(cr, uid, 'account.analytic.line', 'hr_timesheet', vals, 'working_hours_testing')
vals = {
@@ -58,8 +54,7 @@
'unit_amount': 1.00,
'product_id': ref('product.product_product_1'),
'product_uom_id': ref('product.product_uom_hour'),
'account_id': ref('analytic.analytic_administratif'),
'project_id': ref('project.project_project_2'),
'amount': -30.00,
'is_timesheet': True,
}
self._update(cr, uid, 'account.analytic.line', 'hr_timesheet', vals, 'working_hours_maintenance')
+23 -58
View File
@@ -1,57 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<menuitem id="timesheet_menu_root" name="Timesheets" sequence="55" groups="base.group_user" web_icon="hr_timesheet,static/description/icon_timesheet.png"/>
<menuitem id="menu_hr_time_tracking" name="My Timesheet" parent="timesheet_menu_root" sequence="5" />
<record id="hr_timesheet_line_tree" model="ir.ui.view">
<field name="name">account.analytic.line.tree</field>
<field name="name">account.analytic.line.tree.hr_timesheet</field>
<field name="model">account.analytic.line</field>
<field name="arch" type="xml">
<tree editable="top" string="Timesheet Activities">
<field name="date"/>
<field name="user_id" required="1" options='{"no_open": True}'/>
<field name="name"/>
<field name="account_id" context="{'default_type': 'contract'}" options="{'no_open': True, 'no_create': True}"/>
<field name="project_id" options="{'no_open': True, 'no_create': True}" required="1"/>
<field name="task_id" options="{'no_open': True, 'no_create': True}" domain="[('project_id', '=', project_id)]"/>
<field name="unit_amount" string="Duration" sum="Total time" widget="float_time"/>
<field name="is_timesheet" invisible="1"/>
</tree>
</field>
</record>
<record id="hr_timesheet_line_form" model="ir.ui.view">
<field name="name">account.analytic.line.form</field>
<field name="name">account.analytic.line.form.inherit.hr_timesheet</field>
<field name="model">account.analytic.line</field>
<field name="inherit_id" ref="analytic.view_account_analytic_line_form"/>
<field name="mode">primary</field>
<field name="arch" type="xml">
<form string="Timesheet Activities">
<sheet>
<group>
<group>
<field name="name"/>
<field name="user_id" required="1"/>
<field name="is_timesheet" invisible="1"/>
</group>
<group>
<field name="date"/>
</group>
</group>
<notebook>
<page string="Information">
<group>
<group string="Product">
<field name="product_id" domain="[('type','=','service')]"/>
<label string="Duration" for="unit_amount"/>
<div>
<field name="unit_amount" class="oe_inline"/>
<field name="product_uom_id" class="oe_inline"/>
</div>
</group>
<group name="accounting" string="Accounting">
<field domain="[('account_type', '=', 'normal')]" name="account_id" select="1" options="{'no_open': True, 'no_create': True}"/>
<field name="amount"/>
</group>
</group>
</page>
</notebook>
</sheet>
</form>
<field name="account_id" position="after">
<field name="project_id" required="1"/>
<field name="task_id" domain="[('project_id', '=', project_id)]"/>
</field>
</field>
</record>
<record id="hr_timesheet_line_search" model="ir.ui.view">
@@ -61,10 +36,13 @@
<search string="Timesheet">
<field name="date"/>
<field name="user_id"/>
<field name="account_id"/>
<field name="project_id"/>
<field name="task_id"/>
<field name="name"/>
<group expand="0" string="Group By">
<filter string="Users" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Analytic account" domain="[]" context="{'group_by':'account_id'}"/>
<filter string="Project" domain="[]" context="{'group_by':'project_id'}"/>
<filter string="Task" domain="[]" context="{'group_by':'task_id'}"/>
<filter string="Product" domain="[]" context="{'group_by':'product_id'}"/>
<filter string="Timesheet Month" domain="[]" context="{'group_by':'date'}" help="Timesheet by Month"/>
</group>
@@ -72,32 +50,20 @@
</field>
</record>
<record id="act_hr_timesheet_accounts_form" model="ir.actions.act_window">
<field name="name">Timesheet Accounts</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">account.analytic.account</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem name="Configuration" id="menu_hr_timesheet_config" sequence="100"
parent="hr_attendance.timesheet_menu_root" groups="base.group_configuration" action="act_hr_timesheet_accounts_form"/>
<record id="act_hr_timesheet_line_evry1_all_form" model="ir.actions.act_window">
<record id="act_hr_timesheet_line" model="ir.actions.act_window">
<field name="name">Activities</field>
<field name="res_model">account.analytic.line</field>
<field name="view_type">form</field>
<field name="view_id" ref="hr_timesheet_line_form"/>
<field name="domain">[('is_timesheet', '=', True)]</field>
<field name="context">{"search_default_today":1, "default_is_timesheet":1}</field>
<field name="domain">[('project_id', '!=', False)]</field>
<field name="context">{"search_default_today":1}</field>
<field name="search_view_id" ref="hr_timesheet_line_search"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to record activities.
</p><p>
You can register and track your workings hours by project every
day. Every time spent on a project will become a cost in the
analytic accounting/contract and can be re-invoiced to
day. Every time spent on a project will become a cost and can be re-invoiced to
customers if required.
</p>
</field>
@@ -107,12 +73,11 @@
<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"/>
<field name="act_window_id" ref="act_hr_timesheet_line"/>
</record>
<menuitem id="menu_hr_working_hours" parent="hr_attendance.menu_hr_time_tracking" action="act_hr_timesheet_line_evry1_all_form"
<menuitem id="menu_hr_working_hours" parent="menu_hr_time_tracking" action="act_hr_timesheet_line"
name="Detailed Activities"/>
</data>
</openerp>
-74
View File
@@ -1,47 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import time
import datetime
from dateutil.relativedelta import relativedelta
from openerp.addons.analytic.models import analytic
from openerp.osv import fields, osv
from openerp import tools
from openerp.tools.translate import _
from openerp.exceptions import UserError
class project_project(osv.osv):
_inherit = 'project.project'
def open_timesheets(self, cr, uid, ids, context=None):
""" open Timesheets view """
mod_obj = self.pool.get('ir.model.data')
act_obj = self.pool.get('ir.actions.act_window')
project = self.browse(cr, uid, ids[0], context)
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,)
res = mod_obj.get_object_reference(cr, uid, 'hr_timesheet', 'act_hr_timesheet_line_evry1_all_form')
id = res and res[1] or False
result = act_obj.read(cr, uid, [id], context=context)[0]
result['name'] = _('Timesheets')
result['context'] = view_context
result['help'] = help
return result
def open_contract(self, cr, uid, ids, context=None):
""" open Contract view """
res = self.pool['ir.actions.act_window'].for_xml_id(cr, uid, 'project_timesheet', 'action_project_analytic_account', context=context)
contract_ids = self.browse(cr, uid, ids, context=context)
account_ids = [x.analytic_account_id.id for x in contract_ids]
res['res_id'] = account_ids and account_ids[0] or None
return res
class task(osv.osv):
@@ -102,42 +62,8 @@ class task(osv.osv):
'account.analytic.line': (_get_task, ['task_id', 'unit_amount'], 10),
}),
'timesheet_ids': fields.one2many('account.analytic.line', 'task_id', 'Timesheets'),
'analytic_account_id': fields.related('project_id', 'analytic_account_id',
type='many2one', relation='account.analytic.account', string='Analytic Account', store=True),
}
_defaults = {
'progress': 0,
}
def _prepare_delegate_values(self, cr, uid, ids, delegate_data, context=None):
vals = super(task, self)._prepare_delegate_values(cr, uid, ids, delegate_data, context)
for task in self.browse(cr, uid, ids, context=context):
vals[task.id]['planned_hours'] += task.effective_hours
return vals
def onchange_project(self, cr, uid, ids, project_id, context=None):
result = super(task, self).onchange_project(cr, uid, ids, project_id, context=context)
if not project_id:
return result
if 'value' not in result:
result['value'] = {}
project = self.pool['project.project'].browse(cr, uid, project_id, context=context)
return result
class res_partner(osv.osv):
_inherit = 'res.partner'
def unlink(self, cursor, user, ids, context=None):
parnter_id=self.pool.get('project.project').search(cursor, user, [('partner_id', 'in', ids)])
if parnter_id:
raise UserError(_('You cannot delete a partner which is assigned to project, but you can uncheck the active box.'))
return super(res_partner,self).unlink(cursor, user, ids,
context=context)
class account_analytic_line(osv.osv):
_inherit = "account.analytic.line"
_columns = {
'task_id' : fields.many2one('project.task', 'Task'),
}
@@ -9,7 +9,7 @@
<field name="category_ids" eval="[(6, 0, [ref('hr.employee_category_4')])]"/>
<field name="work_location">Building 1, Second Floor</field>
<field name="work_phone">+3282623500</field>
<field name="image" type="base64" file="project_timesheet/static/img/employee_phu-image.jpg"/>
<field name="image" type="base64" file="hr_timesheet/static/img/employee_phu-image.jpg"/>
</record>
<record id="hr.employee_fpi" model="hr.employee">
@@ -31,7 +31,7 @@
<field name="work_email">alice@openerp.com</field>
<field name="department_id" ref="hr.dep_rd"/>
<field name="job_id" ref="hr.job_developer"/>
<field name="image" type="base64" file="project_timesheet/static/img/employee7-image.jpg"/>
<field name="image" type="base64" file="hr_timesheet/static/img/employee7-image.jpg"/>
</record>
<record id="hr.employee_fp" model="hr.employee">
<field name="calendar_id" ref="resource.timesheet_group1"/>
+35 -55
View File
@@ -1,84 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="act_hr_timesheet_line_by_project" model="ir.actions.act_window">
<field name="name">Activities</field>
<field name="res_model">account.analytic.line</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="hr_timesheet_line_tree"/>
<field name="domain">[('project_id', '!=', False)]</field>
<field name="context">{"default_project_id": active_id, "search_default_project_id": [active_id]}</field>
<field name="search_view_id" ref="hr_timesheet_line_search"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to record activities.
</p><p>
You can register and track your workings hours by project every
day. Every time spent on a project will become a cost and can be re-invoiced to
customers if required.
</p>
</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>
<field name="inherit_id" ref="project.edit_project"/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button class="oe_stat_button" name="open_timesheets" type="object" icon="fa-calendar" string="Timesheets"/>
<button class="oe_stat_button" name="%(act_hr_timesheet_line_by_project)d" type="action" icon="fa-calendar" string="Timesheets"/>
</div>
</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>
<field name="inherit_id" ref="analytic.view_account_analytic_line_filter"/>
<field name="arch" type="xml">
<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">
<field name="name">account.analytic.line.form.account_id</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="account_id" position="attributes">
<attribute name="string">Analytic Account/Project</attribute>
</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="hr_timesheet.hr_timesheet_line_tree"/>
<field name="arch" type="xml">
<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>
<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="groups_id" eval="[(4, ref('base.group_user'))]"/>
<field name="arch" type="xml">
<field name="project_id" position="attributes">
<attribute name="on_change">onchange_project(project_id)</attribute>
</field>
<field name="tag_ids" position="after">
<field name="analytic_account_id" invisible="1"/>
<field name="progress" widget="progressbar"
groups="project.group_time_work_estimation_tasks"/>
<field name="progress" widget="progressbar"/>
</field>
<field name="user_id" position="after">
<field name="planned_hours" widget="float_time" on_change="onchange_planned(planned_hours)"/>
</field>
<xpath expr="//notebook/page[@name='description_page']" position="after">
<page string="Timesheets" groups="project.group_tasks_work_on_tasks,project.group_time_work_estimation_tasks">
<field name="timesheet_ids" groups="project.group_tasks_work_on_tasks" context="{'default_account_id' : analytic_account_id, 'default_is_timesheet' : 1}">
<page string="Timesheets">
<field name="timesheet_ids" context="{'default_project_id': project_id}">
<tree editable="bottom" string="Timesheet Activities" default_order="date">
<field name="date"/>
<field name="user_id" required="1"/>
<field name="name"/>
<field name="account_id"/>
<field name="unit_amount" string="Duration" widget="float_time"/>
<field name="is_timesheet" invisible="1"/>
<field name="project_id" 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 class="oe_subtotal_footer oe_right" name="project_hours">
<field name="effective_hours" widget="float_time" />
<field name="remaining_hours" widget="float_time" class="oe_subtotal_footer_separator" />
</group>
</group>
</page>
@@ -92,8 +71,9 @@
<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="user_id" position="after">
<field name="planned_hours"/>
<field name="remaining_hours" widget="float_time" sum="Remaining Hours" on_change="onchange_remaining(remaining_hours,planned_hours)" invisible="context.get('set_visible',False)"/>
<field name="total_hours" invisible="1"/>
<field name="effective_hours" widget="float_time" sum="Spent Hours" invisible="1"/>
</field>
@@ -110,7 +90,7 @@
<field name="arch" type="xml">
<xpath expr="//div[contains(@class, 'o_kanban_manage_reports')]" position="inside">
<div>
<a name="open_timesheets" type="object">Timesheets</a>
<a name="%(act_hr_timesheet_line_by_project)d" type="action">Timesheets</a>
</div>
</xpath>
+1
View File
@@ -1 +1,2 @@
import hr_timesheet_report
import project_report
@@ -12,7 +12,7 @@ class hr_timesheet_report(models.Model):
date = fields.Date('Date', readonly=True)
product_id = fields.Many2one('product.product', 'Product', readonly=True)
user_id = fields.Many2one('res.users', 'User', readonly=True)
account_id = fields.Many2one('account.analytic.account', 'Analytic Account', readonly=True)
project_id = fields.Many2one('project.project', 'Project', readonly=True)
company_id = fields.Many2one('res.company', 'Company', readonly=True)
cost = fields.Float('Cost', readonly=True, digits=dp.get_precision('Account'))
quantity = fields.Float('Time', readonly=True)
@@ -23,7 +23,7 @@ class hr_timesheet_report(models.Model):
aal.date as date,
sum(aal.amount) as cost,
sum(aal.unit_amount) as quantity,
aal.account_id as account_id,
aal.project_id as project_id,
aal.product_id as product_id,
aal.user_id as user_id,
aal.company_id as company_id,
@@ -40,8 +40,8 @@ class hr_timesheet_report(models.Model):
def _group_by(self):
group_by_str = """
GROUP BY aal.date,
aal.account_id,
aal.product_id,
aal.project_id,
aal.user_id,
aal.company_id,
aal.currency_id
@@ -50,7 +50,7 @@ class hr_timesheet_report(models.Model):
def _where(self):
where_str = """
WHERE aal.is_timesheet IS TRUE
WHERE aal.project_id IS NOT NULL
"""
return where_str
@@ -31,7 +31,7 @@
<field name="model">hr.timesheet.report</field>
<field name="arch" type="xml">
<search string="Timesheet">
<field name="account_id"/>
<field name="project_id"/>
<field name="user_id"/>
<filter string="This Month" name="month" domain="[('date','&lt;=', time.strftime('%Y-%m-%d')),('date','&gt;=',time.strftime('%Y-%m-01'))]" help="month"/>
<group expand="0" string="Extended Filters...">
@@ -69,7 +69,7 @@
</record>
<menuitem id="menu_timesheets_reports" name="Reports"
parent="hr_attendance.timesheet_menu_root" sequence="99" groups="base.group_hr_manager"/>
parent="timesheet_menu_root" sequence="99" groups="base.group_hr_manager"/>
<menuitem name="Timesheets" action="action_hr_timesheet_report_stat_all" id="menu_hr_timesheet_report_all"
parent="menu_timesheets_reports" sequence="2"/>
@@ -5,4 +5,4 @@ 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_analytic_line_user,account.analytic.line.user,model_account_analytic_line,base.group_user,1,1,1,0
access_hr_timesheet_report,hr.timesheet.report,model_hr_timesheet_report,base.group_hr_manager,1,1,0,0
access_hr_timesheet_report,hr.timesheet.report,model_hr_timesheet_report,base.group_hr_manager,1,1,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
5 access_product_template_hr_timesheet product.template.hr.timesheet product.model_product_template base.group_hr_user 1 1 1 1
6 access_product_uom_hr_timesheet product.uom.hr.timesheet product.model_product_uom base.group_hr_user 1 1 1 1
7 access_account_analytic_line_user account.analytic.line.user model_account_analytic_line base.group_user 1 1 1 0
8 access_hr_timesheet_report hr.timesheet.report model_hr_timesheet_report base.group_hr_manager 1 1 0 0

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

+1 -2
View File
@@ -26,14 +26,13 @@ The validation can be configured in the company:
* Maximal difference between timesheet and attendances
""",
'website': 'https://www.odoo.com/page/employees',
'depends': ['hr_timesheet'],
'depends': ['hr_timesheet', 'hr_attendance'],
'data': [
'security/ir.model.access.csv',
'security/hr_timesheet_sheet_security.xml',
'hr_timesheet_sheet_view.xml',
'hr_timesheet_workflow.xml',
'report/hr_timesheet_report_view.xml',
'wizard/hr_timesheet_current_view.xml',
'hr_timesheet_sheet_data.xml',
'views/hr_timesheet_sheet.xml',
'hr_dashboard.xml',
@@ -297,7 +297,7 @@ class account_analytic_line(osv.osv):
sheet_obj = self.pool.get('hr_timesheet_sheet.sheet')
res = {}.fromkeys(ids, False)
for ts_line in self.browse(cursor, user, ids, context=context):
if not ts_line.is_timesheet:
if not ts_line.project_id:
continue
sheet_ids = sheet_obj.search(cursor, user,
[('date_to', '>=', ts_line.date), ('date_from', '<=', ts_line.date),
@@ -318,7 +318,7 @@ class account_analytic_line(osv.osv):
WHERE %(date_to)s >= l.date
AND %(date_from)s <= l.date
AND %(user_id)s = l.user_id
AND l.is_timesheet = True
AND l.project_id IS NOT NULL
GROUP BY l.id""", {'date_from': ts.date_from,
'date_to': ts.date_to,
'user_id': ts.employee_id.user_id.id,})
@@ -21,7 +21,7 @@
<field name="name">My Timesheet</field>
</record>
<menuitem name="Weekly" id="menu_act_hr_timesheet_sheet_form_my_current" parent="hr_attendance.menu_hr_time_tracking" action="ir_actions_server_timsheet_sheet" sequence="2"/>
<menuitem name="Weekly" id="menu_act_hr_timesheet_sheet_form_my_current" parent="hr_timesheet.menu_hr_time_tracking" action="ir_actions_server_timsheet_sheet" sequence="2"/>
<!-- Timesheet sheet related subtypes for messaging / Chatter -->
<record id="mt_timesheet_confirmed" model="mail.message.subtype">
@@ -39,10 +39,13 @@
<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], 'default_is_timesheet' : 1}</field>
<field name="context">{'search_default_sheet_id': [active_id]}</field>
<field name="name">Timesheet Activities</field>
<field name="res_model">account.analytic.line</field>
<field name="src_model">hr_timesheet_sheet.sheet</field>
<field name="view_ids" eval="[(5, 0, 0),
(0, 0, {'view_mode': 'tree', 'view_id': ref('hr_timesheet.hr_timesheet_line_tree')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('hr_timesheet.hr_timesheet_line_form')})]"/>
</record>
@@ -120,25 +123,21 @@
</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, 'default_is_timesheet' : 1}" 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}" name="timesheet_ids" nolabel="1">
<tree editable="bottom" string="Timesheet Activities">
<field name="date"/>
<field name="account_id" options="{'no_open': True, 'no_create': True}"/>
<field name="name"/>
<field name="project_id" required="1"/>
<field name="unit_amount" widget="float_time" string="Hours" sum="Hours"/>
<field invisible="1" name="user_id" required="1"/>
<field name="is_timesheet" invisible="1"/>
<field name="user_id" invisible="1"/>
</tree>
<form string="Timesheet Activities">
<group>
<field name="date"/>
<field name="account_id"/>
<field name="name"/>
<field name="unit_amount" widget="float_time"/>
<field name="amount"/>
<field name="general_account_id"/>
<field name="user_id" required="1"/>
<field name="is_timesheet" invisible="1"/>
<field name="project_id"/>
<field name="unit_amount" widget="float_time" string="Hours"/>
<field name="user_id" invisible="1"/>
</group>
</form>
</field>
@@ -217,7 +216,7 @@
</field>
</record>
<menuitem action="act_hr_timesheet_sheet_my_timesheets" id="menu_act_hr_timesheet_sheet_my_timesheets" parent="hr_timesheet.menu_timesheets_reports"
<menuitem action="act_hr_timesheet_sheet_my_timesheets" id="menu_act_hr_timesheet_sheet_my_timesheets" parent="hr_timesheet.menu_hr_time_tracking"
sequence="3"/>
<record id="act_hr_timesheet_sheet_form" model="ir.actions.act_window">
@@ -226,7 +225,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, 'default_is_timesheet':1}</field>
<field name="context">{'search_default_to_approve':1}</field>
<field name="search_view_id" ref="view_hr_timesheet_sheet_filter"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
@@ -249,7 +248,9 @@
<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_to_approve"
<menuitem id="menu_hr_to_approve" name="To Approve" parent="hr_timesheet.timesheet_menu_root" sequence="7"/>
<menuitem action="act_hr_timesheet_sheet_form" id="menu_act_hr_timesheet_sheet_form" parent="menu_hr_to_approve"
sequence="11" groups="base.group_hr_user"/>
@@ -386,7 +387,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 , 'default_is_timesheet' : 1}</field>
<field name="context">{'search_default_employee_id': [active_id], 'default_employee_id': active_id}</field>
</record>
<record id="hr_timesheet_sheet_employee_extd_form" model="ir.ui.view">
@@ -35,7 +35,7 @@
<field name="model">hr.timesheet.report</field>
<field name="inherit_id" ref="hr_timesheet.view_hr_timesheet_report_search"/>
<field name="arch" type="xml">
<field name="account_id" position="before">
<field name="project_id" position="before">
<filter string="Draft" domain="[('state','=','draft')]"/>
<filter string="Confirmed" domain="[('state','=','confirm')]"/>
<filter string="Done" domain="[('state','=','done')]"/>
@@ -46,7 +46,7 @@ var WeeklyTimesheet = form_common.FormWidget.extend(form_common.ReinitializeWidg
var id = JSON.parse($(event.target).data("id"));
this.do_action({
type: 'ir.actions.act_window',
res_model: "account.analytic.account",
res_model: "project.project",
res_id: id,
views: [[false, 'form']],
});
@@ -55,13 +55,12 @@ var WeeklyTimesheet = form_common.FormWidget.extend(form_common.ReinitializeWidg
if (this.updating) {
return;
}
this.querying = true;
var commands = this.field_manager.get_field_value("timesheet_ids");
var self = this;
this.res_o2m_drop.add(new Model(this.view.model).call("resolve_2many_commands",
["timesheet_ids", commands, [], new data.CompoundContext()]))
.done(function(result) {
self.querying = true;
self.set({sheets: result});
self.querying = false;
});
@@ -107,13 +106,13 @@ var WeeklyTimesheet = form_common.FormWidget.extend(form_common.ReinitializeWidg
// it's important to use those vars to avoid race conditions
var dates;
var accounts;
var account_names;
var projects;
var project_names;
var default_get;
var self = this;
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','amount','unit_amount','is_timesheet'],
new data.CompoundContext({'user_id': self.get('user_id'), 'default_is_timesheet': true})
['account_id','general_account_id','journal_id','date','name','user_id','product_id','product_uom_id','amount','unit_amount','project_id'],
new data.CompoundContext({'user_id': self.get('user_id')})
]).then(function(result) {
default_get = result;
// calculating dates
@@ -125,23 +124,23 @@ var WeeklyTimesheet = form_common.FormWidget.extend(form_common.ReinitializeWidg
var m_start = moment(start).add(1, 'days');
start = m_start.toDate();
}
// group by account
accounts = _.chain(self.get("sheets"))
// group by project
projects = _.chain(self.get("sheets"))
.map(_.clone)
.each(function(el) {
// much simpler to use only the id in all cases
if (typeof(el.account_id) === "object") {
el.account_id = el.account_id[0];
if (typeof(el.project_id) === "object") {
el.project_id = el.project_id[0];
}
})
.groupBy("account_id").value();
.groupBy("project_id").value();
var account_ids = _.map(_.keys(accounts), function(el) { return el === "false" ? false : Number(el); });
var project_ids = _.map(_.keys(projects), function(el) { return el === "false" ? false : Number(el); });
accounts = _(accounts).chain().map(function(lines, account_id) {
var account_defaults = _.extend({}, default_get, (accounts[account_id] || {}).value || {});
projects = _(projects).chain().map(function(lines, project_id) {
var projects_defaults = _.extend({}, default_get, (projects[project_id] || {}).value || {});
// group by days
account_id = (account_id === "false")? false : Number(account_id);
project_id = (project_id === "false")? false : Number(project_id);
var index = _.groupBy(lines, "date");
var days = _.map(dates, function(date) {
var day = {day: date, lines: index[time.date_to_str(date)] || []};
@@ -151,34 +150,34 @@ var WeeklyTimesheet = form_common.FormWidget.extend(form_common.ReinitializeWidg
day.lines = _.without(day.lines, to_add);
day.lines.unshift(to_add);
} else {
day.lines.unshift(_.extend(_.clone(account_defaults), {
day.lines.unshift(_.extend(_.clone(projects_defaults), {
name: self.description_line,
unit_amount: 0,
date: time.date_to_str(date),
account_id: account_id,
project_id: project_id,
}));
}
return day;
});
return {account: account_id, days: days, account_defaults: account_defaults};
return {project: project_id, days: days, projects_defaults: projects_defaults};
}).value();
// we need the name_get of the analytic accounts
return new Model("account.analytic.account").call("name_get", [_.pluck(accounts, "account"),
// we need the name_get of the projects
return new Model("project.project").call("name_get", [_.pluck(projects, "project"),
new data.CompoundContext()]).then(function(result) {
account_names = {};
project_names = {};
_.each(result, function(el) {
account_names[el[0]] = el[1];
project_names[el[0]] = el[1];
});
accounts = _.sortBy(accounts, function(el) {
return account_names[el.account];
projects = _.sortBy(projects, function(el) {
return project_names[el.project];
});
});
})).then(function(result) {
// we put all the gathered data in self, then we render
self.dates = dates;
self.accounts = accounts;
self.account_names = account_names;
self.projects = projects;
self.project_names = project_names;
self.default_get = default_get;
//real rendering
self.display_data();
@@ -206,23 +205,23 @@ var WeeklyTimesheet = form_common.FormWidget.extend(form_common.ReinitializeWidg
display_data: function() {
var self = this;
self.$el.html(QWeb.render("hr_timesheet_sheet.WeeklyTimesheet", {widget: self}));
_.each(self.accounts, function(account) {
_.each(_.range(account.days.length), function(day_count) {
_.each(self.projects, function(project) {
_.each(_.range(project.days.length), function(day_count) {
if (!self.get('effective_readonly')) {
self.get_box(account, day_count).val(self.sum_box(account, day_count, true)).change(function() {
self.get_box(project, day_count).val(self.sum_box(project, day_count, true)).change(function() {
var num = $(this).val();
if (self.is_valid_value(num) && num !== 0) {
num = Number(self.parse_client(num));
}
if (isNaN(num)) {
$(this).val(self.sum_box(account, day_count, true));
$(this).val(self.sum_box(project, day_count, true));
} else {
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;
project.days[day_count].lines[0].unit_amount += num - self.sum_box(project, day_count);
var product = (project.days[day_count].lines[0].product_id instanceof Array) ? project.days[day_count].lines[0].product_id[0] : project.days[day_count].lines[0].product_id;
var journal = (project.days[day_count].lines[0].journal_id instanceof Array) ? project.days[day_count].lines[0].journal_id[0] : project.days[day_count].lines[0].journal_id;
if(!isNaN($(this).val())){
$(this).val(self.sum_box(account, day_count, true));
$(this).val(self.sum_box(project, day_count, true));
}
self.display_totals();
@@ -230,16 +229,16 @@ var WeeklyTimesheet = form_common.FormWidget.extend(form_common.ReinitializeWidg
}
});
} else {
self.get_box(account, day_count).html(self.sum_box(account, day_count, true));
self.get_box(project, day_count).html(self.sum_box(project, day_count, true));
}
});
});
self.display_totals();
if(!this.get('effective_readonly')) {
this.init_add_account();
this.init_add_project();
}
},
init_add_account: function() {
init_add_project: function() {
if (this.dfm) {
this.dfm.destroy();
}
@@ -248,26 +247,26 @@ var WeeklyTimesheet = form_common.FormWidget.extend(form_common.ReinitializeWidg
this.$(".oe_timesheet_weekly_add_row").show();
this.dfm = new form_common.DefaultFieldManager(this);
this.dfm.extend_field_desc({
account: {
relation: "account.analytic.account",
project: {
relation: "project.project",
},
});
var FieldMany2One = core.form_widget_registry.get('many2one');
this.account_m2o = new FieldMany2One(this.dfm, {
this.project_m2o = new FieldMany2One(this.dfm, {
attrs: {
name: "account",
name: "project",
type: "many2one",
domain: [
['id', 'not in', _.pluck(this.accounts, "account")],
['id', 'not in', _.pluck(this.projects, "project")],
],
modifiers: '{"required": true}',
},
});
this.account_m2o.prependTo(this.$(".o_add_timesheet_line > div")).then(function() {
self.account_m2o.$el.addClass('oe_edit_only');
this.project_m2o.prependTo(this.$(".o_add_timesheet_line > div")).then(function() {
self.project_m2o.$el.addClass('oe_edit_only');
});
this.$(".oe_timesheet_button_add").click(function() {
var id = self.account_m2o.get_value();
var id = self.project_m2o.get_value();
if (id === false) {
self.dfm.set({display_invalid_fields: true});
return;
@@ -278,19 +277,19 @@ var WeeklyTimesheet = form_common.FormWidget.extend(form_common.ReinitializeWidg
name: self.description_line,
unit_amount: 0,
date: time.date_to_str(self.dates[0]),
account_id: id,
project_id: id,
}));
self.set({sheets: ops});
self.destroy_content();
});
},
get_box: function(account, day_count) {
return this.$('[data-account="' + account.account + '"][data-day-count="' + day_count + '"]');
get_box: function(project, day_count) {
return this.$('[data-project="' + project.project + '"][data-day-count="' + day_count + '"]');
},
sum_box: function(account, day_count, show_value_in_hour) {
sum_box: function(project, day_count, show_value_in_hour) {
var line_total = 0;
_.each(account.days[day_count].lines, function(line) {
_.each(project.days[day_count].lines, function(line) {
line_total += line.unit_amount;
});
return (show_value_in_hour && line_total !== 0)?this.format_client(line_total):line_total;
@@ -299,15 +298,15 @@ var WeeklyTimesheet = form_common.FormWidget.extend(form_common.ReinitializeWidg
var self = this;
var day_tots = _.map(_.range(self.dates.length), function() { return 0; });
var super_tot = 0;
_.each(self.accounts, function(account) {
_.each(self.projects, function(project) {
var acc_tot = 0;
_.each(_.range(self.dates.length), function(day_count) {
var sum = self.sum_box(account, day_count);
var sum = self.sum_box(project, day_count);
acc_tot += sum;
day_tots[day_count] += sum;
super_tot += sum;
});
self.$('[data-account-total="' + account.account + '"]').html(self.format_client(acc_tot));
self.$('[data-project-total="' + project.project + '"]').html(self.format_client(acc_tot));
});
_.each(_.range(self.dates.length), function(day_count) {
self.$('[data-day-total="' + day_count + '"]').html(self.format_client(day_tots[day_count]));
@@ -330,8 +329,8 @@ var WeeklyTimesheet = form_common.FormWidget.extend(form_common.ReinitializeWidg
generate_o2m_value: function() {
var ops = [];
var ignored_fields = this.ignore_fields();
_.each(this.accounts, function(account) {
_.each(account.days, function(day) {
_.each(this.projects, function(project) {
_.each(project.days, function(day) {
_.each(day.lines, function(line) {
if (line.unit_amount !== 0) {
var tmp = _.clone(line);
@@ -13,19 +13,19 @@
</t>
<th class="oe_timesheet_weekly_date_head">Total</th>
</tr>
<tr t-foreach="widget.accounts" t-as="account">
<td class="oe_timesheet_weekly_account" colspan="2"><a href="javascript:void(0)" t-att-data-id="JSON.stringify(account.account)"><t t-esc="widget.account_names[account.account]"/></a></td>
<tr t-foreach="widget.projects" t-as="project">
<td class="oe_timesheet_weekly_account" colspan="2"><a href="javascript:void(0)" t-att-data-id="JSON.stringify(project.project)"><t t-esc="widget.project_names[project.project]"/></a></td>
<t t-set="day_count" t-value="0"/>
<t t-foreach="account.days" t-as="day">
<t t-foreach="project.days" t-as="day">
<td t-att-class="moment().format('DD-MM-YYYY') === moment(day.day).format('DD-MM-YYYY') ? 'oe_timesheet_weekly_today' : ''">
<input t-if="!widget.get('effective_readonly')" class="oe_timesheet_weekly_input" t-att-data-account="account.account"
<input t-if="!widget.get('effective_readonly')" class="oe_timesheet_weekly_input" t-att-data-project="project.project"
t-att-data-day-count="day_count" type="text"/>
<span t-if="widget.get('effective_readonly')" t-att-data-account="account.account"
<span t-if="widget.get('effective_readonly')" t-att-data-project="project.project"
t-att-data-day-count="day_count" class="oe_timesheet_weekly_box"/>
<t t-set="day_count" t-value="day_count + 1"/>
</td>
</t>
<td t-att-data-account-total="account.account" class="oe_timesheet_total"/>
<td t-att-data-project-total="project.project" class="oe_timesheet_total"/>
</tr>
<tr>
<td class="o_add_timesheet_line">
@@ -46,7 +46,7 @@
<td class="oe_timesheet_weekly_supertotal oe_timesheet_total"/>
</tr>
</table>
<div t-if="widget.accounts.length == 0">
<div t-if="widget.projects.length == 0">
<div class="oe_view_nocontent oe_edit_only">
<p class="oe_view_nocontent_create">Click to add projects, contracts or analytic accounts.</p>
<p>You will be able to register your working hours and activities.</p>
@@ -39,7 +39,7 @@
-
!record {model: hr_timesheet_sheet.sheet, id: hr_timesheet_sheet_sheet_deddk0}:
timesheet_ids:
- account_id: analytic.analytic_agrolait
- project_id: project.project_project_2
date: !eval time.strftime('%Y-%m-%d')
name: 'Develop yaml for hr module'
user_id: base.user_demo
@@ -47,7 +47,6 @@
amount: -90.00
product_id: product.product_product_1
general_account_id: a_expense
is_timesheet: !eval True
-
I confirm my Timesheet at end of period by click on "Confirm" button,
if the difference between Timesheet hour and attendance hour is more than 1 hour it will give message.
@@ -68,7 +67,7 @@
-
!record {model: hr_timesheet_sheet.sheet, id: hr_timesheet_sheet_sheet_deddk0}:
timesheet_ids:
- account_id: analytic.analytic_agrolait
- project_id: project.project_project_2
date: !eval time.strftime('%Y-%m-%d')
name: 'Develop yaml for hr module'
unit_amount: 2.00
@@ -76,7 +75,6 @@
amount: -90.00
product_id: product.product_product_1
general_account_id: a_expense
is_timesheet: !eval True
-
I set an allowed difference of 1 hour
-
@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_hr_timesheet_current_open" model="ir.ui.view">
<field name="name">hr_timesheet_current_open.form</field>
<field name="model">hr.timesheet.current.open</field>
<field name="arch" type="xml">
<form string="My Timesheet">
<group>
<separator string="It will open your current timesheet"/>
</group>
<footer>
<button name="open_timesheet" string="Open" type="object" class="btn-primary"/>
<button string="Cancel" class="btn-default" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="action_hr_timesheet_current_open" model="ir.actions.act_window">
<field name="name">My Timesheet</field>
<field name="res_model">hr.timesheet.current.open</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="view_hr_timesheet_current_open"/>
<field name="target">new</field>
<field name="help">My Timesheet opens your timesheet so that you can book your activities into the system. From the same form, you can register your attendances (Sign In/Out) and describe the working hours made on the different projects. At the end of the period defined in the company, the timesheet is confirmed by the user and can be validated by his manager. If required, as defined on the project, you can generate the invoices based on the timesheet.</field>
</record>
<menuitem action="action_hr_timesheet_current_open" id="menu_act_hr_timesheet_sheet_form_my_current" name="My Timesheet" parent="hr_attendance.menu_hr_time_tracking" sequence="1"/>
</data>
</openerp>
+1 -1
View File
@@ -9,7 +9,7 @@ class res_company(osv.osv):
_columns = {
'project_time_mode_id': fields.many2one('product.uom', 'Project Time Unit',
help='This will set the unit of measure used in projects and tasks.\n' \
"If you use the timesheet linked to projects (project_timesheet module), don't " \
"If you use the timesheet linked to projects, don't " \
"forget to setup the right unit of measure in your employees.",
),
}
-4
View File
@@ -265,9 +265,6 @@
<field name="user_id"
on_change="onchange_user_id(user_id, context)"
options='{"no_open": True}'/>
<field name="planned_hours" widget="float_time"
groups="project.group_time_work_estimation_tasks"
on_change="onchange_planned(planned_hours)"/>
<field name="legend_blocked" invisible="1"/>
<field name="legend_normal" invisible="1"/>
<field name="legend_done" invisible="1"/>
@@ -391,7 +388,6 @@
<field name="name"/>
<field name="project_id" invisible="context.get('user_invisible', False)"/>
<field name="user_id" invisible="context.get('user_invisible', False)"/>
<field name="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)"/>
</tree>
-13
View File
@@ -24,14 +24,6 @@ class project_configuration(osv.osv_memory):
help='Lets the company customize which Pad installation should be used to link to new pads '
'(for example: http://ietherpad.com/).\n'
'-This installs the module pad.'),
'module_project_timesheet': fields.selection([
(0, "Do not record timesheets on tasks"),
(1, "Record timesheet lines per tasks")
], "Timesheets on Project",
help='This allows you to transfer the entries under tasks defined for Project Management to '
'the timesheet line entries for particular date and user, with the effect of creating, '
'editing and deleting either ways.\n'
'-This installs the module project_timesheet.'),
'module_rating_project': fields.selection([
(0, "No customer rating"),
(1, 'Allow activating customer rating on projects, at issue completion')
@@ -58,11 +50,6 @@ class project_configuration(osv.osv_memory):
'module_project_forecast': fields.boolean("Forecasts, planning and Gantt charts"),
}
def onchange_time_estimation_project_timesheet(self, cr, uid, ids, group_time_work_estimation_tasks):
if group_time_work_estimation_tasks:
return {'value': {'group_tasks_work_on_tasks': True}}
return {}
def set_default_generate_project_alias(self, cr, uid, ids, context=None):
config_value = self.browse(cr, uid, ids, context=context).generate_project_alias
self.pool.get('ir.values').set_default(cr, uid, 'project.config.settings', 'generate_project_alias', config_value)
+1 -1
View File
@@ -14,7 +14,7 @@
<group string="Project Management" name="project_management">
<field name="module_sale_service" widget="radio"/>
<field name="module_pad" widget="radio"/>
<field name="group_time_work_estimation_tasks" on_change="onchange_time_estimation_project_timesheet(group_time_work_estimation_tasks)" widget="radio"/>
<field name="group_time_work_estimation_tasks" widget="radio"/>
<label for="id" string="Forecasts"/>
<div>
<div>
@@ -19,14 +19,8 @@
<field name="groups_id" eval="[(4,ref('project.group_project_manager'))]"/>
</record>
<record id="group_tasks_work_on_tasks" model="res.groups">
<field name="name">Task's Work on Tasks</field>
<field name="category_id" ref="base.module_category_hidden"/>
</record>
<record id="group_time_work_estimation_tasks" model="res.groups">
<field name="name">Time Estimation on Tasks</field>
<field name="implied_ids" eval="[(4, ref('group_tasks_work_on_tasks'))]"/>
<field name="category_id" ref="base.module_category_hidden"/>
</record>
@@ -15,7 +15,6 @@ 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',
@@ -14,19 +14,15 @@
<attribute name="on_change">on_change_project(project_id)</attribute>
</field>
<xpath expr="//notebook/page[@name='extra_info']" position="before">
<page string="Timesheets" groups="project.group_tasks_work_on_tasks,project.group_time_work_estimation_tasks">
<group>
<field name="analytic_account_id" domain="[('account_type', '=', 'normal')]"/>
</group>
<field name="timesheet_ids" colspan="4" nolabel="1" context="{'default_user_id' : uid, 'default_account_id' : analytic_account_id, 'default_is_timesheet':1}"
<page string="Timesheets">
<field name="timesheet_ids" colspan="4" nolabel="1" context="{'default_user_id' : uid, 'default_project_id': project_id}"
groups="base.group_user">
<tree editable="top" string="Timesheet Activities">
<field name="date"/>
<field name="user_id" required="1"/>
<field name="name"/>
<field name="account_id" invisible="0" domain="[('account_type, '=', 'normal'), ('partner_id', '=', parent.partner_id)]" options="{'no_open': True, 'no_create': True}" groups="analytic.group_analytic_accounting"/>
<field name="project_id" invisible="1" required="1"/>
<field name="unit_amount" string="Duration" widget="float_time"/>
<field name="is_timesheet" invisible="1"/>
</tree>
</field>
</page>
-6
View File
@@ -1,6 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import project_timesheet
import report
import res_config
-32
View File
@@ -1,32 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Bill Time on Tasks',
'version': '1.0',
'category': 'Project Management',
'description': """
Synchronization of project task work entries with timesheet entries.
====================================================================
This module lets you transfer the entries under tasks defined for Project
Management to the Timesheet line entries for particular date and particular user
with the effect of creating, editing and deleting either ways.
""",
'website': 'https://www.odoo.com/page/project-management',
'images': ['images/invoice_task_work.jpeg', 'images/my_timesheet.jpeg', 'images/working_hour.jpeg'],
'depends': ['resource', 'project', 'sale_timesheet'],
'data': [
'security/ir.model.access.csv',
'security/project_timesheet_security.xml',
'report/project_report_view.xml',
'project_timesheet_view.xml',
'res_config_view.xml',
],
'demo': ['project_timesheet_demo.xml'],
'test': [
'test/worktask_entry_to_timesheetline_entry.yml',
],
'installable': True,
'auto_install': True,
}
@@ -1,2 +0,0 @@
# -*- coding: utf-8 -*-
import project_report
-18
View File
@@ -1,18 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.osv import fields, osv
from openerp.tools.translate import _
class project_configuration(osv.osv_memory):
_name = 'project.config.settings'
_inherit = 'project.config.settings'
_columns = {
'group_tasks_work_on_tasks': fields.selection([
(0, "Do not log work activities on task"),
(1, "Log work activities on tasks")
], "Activity Log",
implied_group='project.group_tasks_work_on_tasks',
help="Allows you to compute work on tasks."),
}
@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_config_settings" model="ir.ui.view">
<field name="name">project settings</field>
<field name="model">project.config.settings</field>
<field name="inherit_id" ref="project.view_config_settings"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='project_management']" position="inside">
<field name="group_tasks_work_on_tasks" widget="radio"/>
</xpath>
</field>
</record>
</data>
</openerp>
@@ -1,4 +0,0 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
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
access_analytic_line_portal,account.analytic.line,account.model_account_analytic_line,base.group_portal,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_sale_order_manager sale.order sale.model_sale_order project.group_project_manager 1 0 0 0
3 access_sale_order_user sale.order sale.model_sale_order project.group_project_user 1 0 0 0
4 access_analytic_line_portal account.analytic.line account.model_account_analytic_line base.group_portal 1 0 0 0
@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<record model="res.groups" id="base.group_user">
<field name="implied_ids" eval="[(4, ref('project.group_tasks_work_on_tasks'))]"/>
</record>
<record id="portal_task_sheet_rule" model="ir.rule">
<field name="name">Project/Task Timesheets: portal access</field>
<field name="model_id" ref="analytic.model_account_analytic_line"/>
<field name="domain_force">['|',
'&amp;',
('task_id.project_id.privacy_visibility', '=', 'portal'),
('task_id.message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
'&amp;',
('task_id.project_id.privacy_visibility', '=', 'followers'),
('task_id.message_partner_ids', 'in', [user.partner_id.id]),
]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
</data>
</openerp>
+1 -1
View File
@@ -28,7 +28,7 @@ order line, the sale order line will be considered delivered when the task is
completed.
""",
'website': 'https://www.odoo.com/page/crm',
'depends': ['project', 'sale', 'project_timesheet', 'sale_timesheet'],
'depends': ['sale_timesheet'],
'data': ['views/sale_service_view.xml'],
'demo': ['demo/sale_service_demo.xml'],
'installable': True,
@@ -27,11 +27,10 @@ class TestSaleService(TestSale):
# register timesheet on task
self.env['account.analytic.line'].create({
'name': 'Test Line',
'account_id': project.id,
'project_id': project.id,
'task_id': task.id,
'unit_amount': 50,
'user_id': self.manager.id,
'is_timesheet': True,
})
self.assertEqual(so.invoice_status, 'to invoice', 'Sale Service: there should be something to invoice after registering timesheets')
so.action_invoice_create()
+39 -9
View File
@@ -27,7 +27,7 @@ class HrEmployee(models.Model):
class ProductTemplate(models.Model):
_inherit = 'product.template'
track_service = fields.Selection(selection_add=[('timesheet', 'Timesheets on contract')])
track_service = fields.Selection(selection_add=[('timesheet', 'Timesheets on project')])
@api.onchange('type', 'invoice_policy')
def onchange_type_timesheet(self):
@@ -43,12 +43,12 @@ class AccountAnalyticLine(models.Model):
def _get_sale_order_line(self, vals=None):
result = dict(vals or {})
if self.is_timesheet:
if self.project_id:
if result.get('so_line'):
sol = self.env['sale.order.line'].browse([result['so_line']])
else:
sol = self.so_line
if not sol and self.account_id:
if not sol:
sol = self.env['sale.order.line'].search([
('order_id.project_id', '=', self.account_id.id),
('state', '=', 'sale'),
@@ -67,7 +67,7 @@ class AccountAnalyticLine(models.Model):
def _get_timesheet_cost(self, vals=None):
result = dict(vals or {})
if result.get('is_timesheet') or self.is_timesheet:
if result.get('project_id') or self.project_id:
if result.get('amount'):
return result
unit_amount = result.get('unit_amount', 0.0) or self.unit_amount
@@ -99,11 +99,13 @@ class SaleOrder(models.Model):
timesheet_ids = fields.Many2many('account.analytic.line', compute='_compute_timesheet_ids', string='Timesheet activities associated to this sale')
timesheet_count = fields.Float(string='Timesheet activities', compute='_compute_timesheet_ids')
project_project_id = fields.Many2one('project.project', compute='_compute_project_project_id', string='Project associated to this sale')
@api.multi
@api.depends('project_id.line_ids')
def _compute_timesheet_ids(self):
for order in self:
order.timesheet_ids = self.env['account.analytic.line'].search([('is_timesheet', '=', True), ('account_id', '=', order.project_id.id)]) if order.project_id else []
order.timesheet_ids = self.env['account.analytic.line'].search([('project_id', '!=', False), ('account_id', '=', order.project_id.id)]) if order.project_id else []
order.timesheet_count = round(sum([line.unit_amount for line in order.timesheet_ids]), 2)
@api.multi
@@ -118,14 +120,41 @@ class SaleOrder(models.Model):
raise UserError(_("You can use only one product on timesheet within the same sale order. You should split your order to include only one contract based on time and material."))
return {}
@api.multi
@api.depends('project_id.project_ids')
def _compute_project_project_id(self):
for order in self:
order.project_project_id = self.env['project.project'].search([('analytic_account_id', '=', order.project_id.id)])
@api.multi
def action_view_project_project(self):
self.ensure_one()
imd = self.env['ir.model.data']
action = imd.xmlid_to_object('project.open_view_project_all')
form_view_id = imd.xmlid_to_res_id('project.edit_project')
result = {
'name': action.name,
'help': action.help,
'type': action.type,
'views': [(form_view_id, 'form')],
'target': action.target,
'context': action.context,
'res_model': action.res_model,
'res_id': self.project_project_id.id,
}
return result
@api.multi
def action_confirm(self):
result = super(SaleOrder, self).action_confirm()
for order in self:
if not order.project_id:
if not order.project_project_id:
for line in order.order_line:
if line.product_id.track_service == 'timesheet':
order._create_analytic_account(prefix=order.product_id.default_code or None)
if not order.project_id:
order._create_analytic_account(prefix=order.product_id.default_code or None)
order.project_id.project_create({'name': order.project_id.name, 'use_tasks': True})
break
return result
@@ -133,7 +162,7 @@ class SaleOrder(models.Model):
def action_view_timesheet(self):
self.ensure_one()
imd = self.env['ir.model.data']
action = imd.xmlid_to_object('hr_timesheet.act_hr_timesheet_line_evry1_all_form')
action = imd.xmlid_to_object('hr_timesheet.act_hr_timesheet_line')
list_view_id = imd.xmlid_to_res_id('hr_timesheet.hr_timesheet_line_tree')
form_view_id = imd.xmlid_to_res_id('hr_timesheet.hr_timesheet_line_form')
@@ -152,6 +181,7 @@ class SaleOrder(models.Model):
result = {'type': 'ir.actions.act_window_close'}
return result
class SaleOrderLine(models.Model):
_inherit = "sale.order.line"
@@ -159,7 +189,7 @@ class SaleOrderLine(models.Model):
def _compute_analytic(self, domain=None):
if not domain:
# To filter on analyic lines linked to an expense
domain = [('so_line', 'in', self.ids), '|', ('amount', '<=', 0.0), ('is_timesheet', '=', True)]
domain = [('so_line', 'in', self.ids), '|', ('amount', '<=', 0.0), ('project_id', '!=', False)]
return super(SaleOrderLine, self)._compute_analytic(domain=domain)
@api.model
@@ -24,30 +24,27 @@ class TestSaleTimesheet(TestSale):
# let's log some timesheets
self.env['account.analytic.line'].create({
'name': 'Test Line',
'account_id': so.project_id.id,
'project_id': so.project_project_id.id,
'unit_amount': 10.5,
'user_id': self.manager.id,
'is_timesheet': True,
})
self.assertEqual(so.order_line.qty_delivered, 10.5, 'Sale Timesheet: timesheet does not increase delivered quantity on so line')
self.assertEqual(so.invoice_status, 'invoiced', 'Sale Timesheet: "invoice on order" timesheets should not modify the invoice_status of the so')
self.env['account.analytic.line'].create({
'name': 'Test Line',
'account_id': so.project_id.id,
'project_id': so.project_project_id.id,
'unit_amount': 39.5,
'user_id': self.user.id,
'is_timesheet': True,
})
self.assertEqual(so.order_line.qty_delivered, 50, 'Sale Timesheet: timesheet does not increase delivered quantity on so line')
self.assertEqual(so.invoice_status, 'invoiced', 'Sale Timesheet: "invoice on order" timesheets should not modify the invoice_status of the so')
self.env['account.analytic.line'].create({
'name': 'Test Line',
'account_id': so.project_id.id,
'project_id': so.project_project_id.id,
'unit_amount': 10,
'user_id': self.user.id,
'is_timesheet': True,
})
self.assertEqual(so.order_line.qty_delivered, 60, 'Sale Timesheet: timesheet does not increase delivered quantity on so line')
self.assertEqual(so.invoice_status, 'upselling', 'Sale Timesheet: "invoice on order" timesheets should not modify the invoice_status of the so')
@@ -70,10 +67,9 @@ class TestSaleTimesheet(TestSale):
# let's log some timesheets
self.env['account.analytic.line'].create({
'name': 'Test Line',
'account_id': so.project_id.id,
'project_id': so.project_project_id.id,
'unit_amount': 10.5,
'user_id': self.manager.id,
'is_timesheet': True,
})
self.assertEqual(so.invoice_status, 'to invoice', 'Sale Timesheet: "invoice on delivery" timesheets should set the so in "to invoice" status when logged')
inv_id = so.action_invoice_create()
@@ -82,10 +78,9 @@ class TestSaleTimesheet(TestSale):
self.env['account.analytic.line'].create({
'name': 'Test Line',
'account_id': so.project_id.id,
'project_id': so.project_project_id.id,
'unit_amount': 39.5,
'user_id': self.user.id,
'is_timesheet': True,
})
self.assertEqual(so.invoice_status, 'to invoice', 'Sale Timesheet: "invoice on delivery" timesheets should not modify the invoice_status of the so')
so.action_invoice_create()
@@ -93,10 +88,9 @@ class TestSaleTimesheet(TestSale):
self.env['account.analytic.line'].create({
'name': 'Test Line',
'account_id': so.project_id.id,
'project_id': so.project_project_id.id,
'unit_amount': 10,
'user_id': self.user.id,
'is_timesheet': True,
})
self.assertEqual(so.invoice_status, 'to invoice', 'Sale Timesheet: supplementary timesheets do not change the status of the SO')
@@ -117,19 +111,17 @@ class TestSaleTimesheet(TestSale):
# let's log some timesheets
self.env['account.analytic.line'].create({
'name': 'Test Line',
'account_id': so.project_id.id,
'project_id': so.project_project_id.id,
'unit_amount': 16,
'user_id': self.manager.id,
'is_timesheet': True,
})
self.assertEqual(so.order_line.qty_delivered, 2, 'Sale: uom conversion of timesheets is wrong')
self.env['account.analytic.line'].create({
'name': 'Test Line',
'account_id': so.project_id.id,
'project_id': so.project_project_id.id,
'unit_amount': 24,
'user_id': self.user.id,
'is_timesheet': True,
})
so.action_invoice_create()
self.assertEqual(so.invoice_status, 'invoiced', 'Sale Timesheet: "invoice on delivery" timesheets should not modify the invoice_status of the so')
@@ -42,6 +42,16 @@
<field name="timesheet_count" widget="statinfo" string="Timesheet"/>
</button>
</xpath>
<xpath expr="//button[@name='action_view_invoice']" position="before">
<field name="project_project_id" invisible="1"/>
<button type="object"
name="action_view_project_project"
class="oe_stat_button"
icon="fa-tasks"
string="Project"
attrs="{'invisible': [('project_project_id', '=', False)]}" groups="base.group_user">
</button>
</xpath>
</data>
</field>
</record>