[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:
committed by
Antony Lesuisse
parent
f3a9f83ff1
commit
16bf40f7c4
@@ -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"/>
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import hr_timesheet
|
||||
import project_timesheet
|
||||
import report
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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 +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','<=', time.strftime('%Y-%m-%d')),('date','>=',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
|
||||
|
||||
|
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
@@ -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>
|
||||
@@ -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.",
|
||||
),
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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,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">['|',
|
||||
'&',
|
||||
('task_id.project_id.privacy_visibility', '=', 'portal'),
|
||||
('task_id.message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
|
||||
'&',
|
||||
('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>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user