Improvements on work, project and task:
* work: - added sequence - added One2Many to timesheet lines * project / task: - use type instead of project_type - added children, effort and total_effort - handling of the parent field at the project.work level
This commit is contained in:
1 parent
792edb369a
commit
00e126eef7
2 files changed
+374
-57
No files matched your search
@@ -2,52 +2,246 @@
|
||||
#this repository contains the full copyright notices and license terms.
|
||||
"Project"
|
||||
from trytond.model import ModelView, ModelSQL, fields
|
||||
from trytond.model.modelstorage import OPERATORS
|
||||
import copy
|
||||
|
||||
|
||||
class TimesheetWork(ModelSQL, ModelView):
|
||||
_name = 'timesheet.work'
|
||||
|
||||
timesheet_lines = fields.One2Many('timesheet.line', 'work',
|
||||
'Timesheet Lines',
|
||||
depends=['timesheet_available', 'active'],
|
||||
states={
|
||||
'invisible': 'not bool(timesheet_available)',
|
||||
'readonly': '''not active'''
|
||||
})
|
||||
sequence = fields.Integer('Sequence')
|
||||
|
||||
def __init__(self):
|
||||
super(TimesheetWork, self).__init__()
|
||||
self._order.insert(0, ('sequence', 'ASC'))
|
||||
|
||||
self.parent = copy.copy(self.parent)
|
||||
if not self.parent.context:
|
||||
self.parent.context = "{'project_type': locals().get('project_type')}"
|
||||
elif 'project_type' not in self.parent.context:
|
||||
self.parent.context = "{'type': locals().get('type')}"
|
||||
elif 'type' not in self.parent.context:
|
||||
sep = ''
|
||||
if self.parent.context[-2] != ',':
|
||||
sep = ','
|
||||
self.parent.context = self.parent.context[:-1] + \
|
||||
sep + "'project_type': locals().get('project_type')" + \
|
||||
sep + "'type': locals().get('type')" + \
|
||||
self.parent.context[-1:]
|
||||
|
||||
self._reset_columns()
|
||||
|
||||
TimesheetWork()
|
||||
|
||||
|
||||
class Work(ModelSQL, ModelView):
|
||||
'Work'
|
||||
'Work Effort'
|
||||
_name = 'project.work'
|
||||
_description = __doc__
|
||||
_inherits = {'timesheet.work': 'work'}
|
||||
|
||||
work = fields.Many2One('timesheet.work', 'Work',
|
||||
required=True)
|
||||
project_type = fields.Selection([
|
||||
('project', 'Project'),
|
||||
], 'Type', required=True, select=1)
|
||||
work = fields.Many2One('timesheet.work', 'Work', required=True,
|
||||
ondelete='CASCADE')
|
||||
type = fields.Selection([
|
||||
('project', 'Project'),
|
||||
('task', 'Task')
|
||||
],
|
||||
'Type', required=True, select=1,
|
||||
states={
|
||||
'invisible': 'context.get("type", False)',
|
||||
})
|
||||
party = fields.Many2One('party.party', 'Party',
|
||||
states={
|
||||
'invisible': "project_type != 'project'",
|
||||
})
|
||||
'invisible': "type!= 'project'"
|
||||
}, depends=['type'])
|
||||
party_address = fields.Many2One('party.address', 'Contact Address',
|
||||
domain="[('party', '=', party)]",
|
||||
domain="["\
|
||||
"('party', '=', party)"\
|
||||
"]",
|
||||
states={
|
||||
'invisible': "project_type != 'project'",
|
||||
})
|
||||
'invisible': "type != 'project'"
|
||||
}, depends=['party', 'type'])
|
||||
effort = fields.Float("Effort",
|
||||
states={
|
||||
'invisible': "type != 'task'"
|
||||
}, depends=['type'])
|
||||
total_effort = fields.Function('get_total_effort', type='float',
|
||||
string='Total Effort',
|
||||
states={
|
||||
'invisible': "type != 'project'"
|
||||
}, depends=['type'])
|
||||
comment = fields.Text('Comment')
|
||||
parent = fields.Function('get_parent', fnct_inv='set_parent',
|
||||
fnct_search='search_parent', string='Parent',
|
||||
relation='project.work', type="many2one")
|
||||
children = fields.One2Many('project.work', 'parent', 'Children')
|
||||
state = fields.Selection([
|
||||
('opened', 'Opened'),
|
||||
('done', 'Done'),
|
||||
], 'State',
|
||||
states={
|
||||
'invisible': "type != 'task'",
|
||||
'required': "type == 'task'",
|
||||
}, select=1, depends=['type'])
|
||||
|
||||
def default_type(self, cursor, user, context=None):
|
||||
if context.get('type') == 'project':
|
||||
return 'project'
|
||||
return 'task'
|
||||
|
||||
def default_state(self, cursor, user, context=None):
|
||||
return 'opened'
|
||||
|
||||
def __init__(self):
|
||||
super(Work, self).__init__()
|
||||
self._sql_constraints += [
|
||||
('work_uniq', 'UNIQUE(work)', 'There should be only one '\
|
||||
'timesheet work by task/project!'),
|
||||
]
|
||||
self._order.insert(0, ('sequence', 'ASC'))
|
||||
|
||||
def get_parent(self, cursor, user, ids, name, arg, context=None):
|
||||
res = dict.fromkeys(ids, None)
|
||||
cursor.execute(
|
||||
'SELECT child.id, parent.id '\
|
||||
'FROM project_work child '\
|
||||
'JOIN timesheet_work twc on (twc.id = child.work) '\
|
||||
'JOIN timesheet_work twp on (twc.parent = twp.id) '\
|
||||
'JOIN project_work parent on (twp.id = parent.work) '\
|
||||
'WHERE child.id in (' + ','.join('%s' for i in ids) + ') ',
|
||||
ids)
|
||||
res.update(dict(cursor.fetchall()))
|
||||
return res
|
||||
|
||||
def set_parent(self, cursor, user, id, name, value, arg, context=None):
|
||||
timesheet_work_obj = self.pool.get('timesheet.work')
|
||||
if value:
|
||||
child_project_work, parent_project_work = self.browse(
|
||||
cursor, user, [id, value], context=context)
|
||||
child_timesheet_work_id = child_project_work.work.id
|
||||
parent_timesheet_work_id = parent_project_work.work.id
|
||||
else:
|
||||
child_project_work = self.browse( cursor, user, id, context=context)
|
||||
child_timesheet_work_id = child_project_work.work.id
|
||||
parent_timesheet_work_id = False
|
||||
|
||||
timesheet_work_obj.write(cursor, user, child_timesheet_work_id, {
|
||||
'parent': parent_timesheet_work_id
|
||||
},
|
||||
context=context)
|
||||
|
||||
def search_parent(self, cursor, user, name, domain=None, context=None):
|
||||
ids = domain[0][2]
|
||||
clauses = []
|
||||
args = []
|
||||
for d in domain:
|
||||
if d[1] in ('in', 'not in'):
|
||||
clause = 'parent.id ' + d[1] + \
|
||||
'(' + ','.join('%s' for i in d[2]) + ')'
|
||||
clauses.append(clause)
|
||||
args.extend(d[2])
|
||||
|
||||
elif d[1] in ('child_of', 'not child_of'):
|
||||
raise Exception('Domain not implemented')
|
||||
|
||||
elif d[1] in ('=', '!=') and not d[2]:
|
||||
op = d[1] == '=' and 'is' or 'is not'
|
||||
clauses.append('twc.parent %s null' % op)
|
||||
|
||||
elif d[1] in OPERATORS:
|
||||
clauses.append('parent.id %s %%s' % d[1])
|
||||
args.append(d[2])
|
||||
|
||||
query = \
|
||||
'SELECT child.id '\
|
||||
'FROM project_work child '\
|
||||
'JOIN timesheet_work twc on (twc.id = child.work) '\
|
||||
'LEFT JOIN timesheet_work twp on (twc.parent = twp.id) '\
|
||||
'LEFT JOIN project_work parent on (twp.id = parent.work) '
|
||||
|
||||
if clauses:
|
||||
query = query + 'WHERE ' + ' AND '.join(clauses)
|
||||
|
||||
cursor.execute(query, args)
|
||||
|
||||
res = [('id', 'in', [x[0] for x in cursor.fetchall()])]
|
||||
return res
|
||||
|
||||
def get_total_effort(self, cursor, user, ids, name, arg, context=None):
|
||||
timesheet_work_obj = self.pool.get('timesheet.work')
|
||||
|
||||
projects = self.browse(cursor, user, ids, context=context)
|
||||
w2p = dict((p.work.id, p.id) for p in projects)
|
||||
|
||||
query_ids, args_ids = timesheet_work_obj.search(cursor, user, [
|
||||
('parent', 'child_of', w2p.keys()),
|
||||
], context=context, query_string=True)
|
||||
|
||||
cursor.execute(
|
||||
'SELECT task.work, ' \
|
||||
'SUM(CASE WHEN task.type = \'task\' '\
|
||||
'AND task.effort is not null '\
|
||||
'THEN task.effort '\
|
||||
'ELSE 0 END) '\
|
||||
'FROM project_work task ' \
|
||||
'WHERE task.work IN (' + query_ids + ') ' \
|
||||
'GROUP BY task.work', args_ids)
|
||||
work_effort = dict(cursor.fetchall())
|
||||
|
||||
works = timesheet_work_obj.browse(
|
||||
cursor, user, work_effort.keys(), context=context)
|
||||
id2work = {}
|
||||
leafs = set()
|
||||
for work in works:
|
||||
id2work[work.id] = work
|
||||
if not work.children:
|
||||
leafs.add(work.id)
|
||||
|
||||
while leafs:
|
||||
parents = set()
|
||||
for work_id in leafs:
|
||||
work = id2work[work_id]
|
||||
if work.parent and work.parent.id in work_effort:
|
||||
work_effort[work.parent.id] += work_effort[work_id]
|
||||
parents.add(work.parent.id)
|
||||
leafs = parents
|
||||
|
||||
return dict((w2p[w], e) for w,e in work_effort.iteritems() if w in w2p)
|
||||
|
||||
|
||||
def delete(self, cursor, user, ids, context=None):
|
||||
timesheet_work_obj = self.pool.get('timesheet.work')
|
||||
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
|
||||
# Get the timesheet works linked to the project works
|
||||
project_works = self.browse(cursor, user, ids, context=context)
|
||||
timesheet_work_ids = [pw.work.id for pw in project_works]
|
||||
|
||||
res = super(Work, self).delete(cursor, user, ids, context=context)
|
||||
|
||||
# check if timesheet works are linked to project works
|
||||
project_work_ids = self.search(cursor, user, [
|
||||
('work', 'in', timesheet_work_ids)
|
||||
],
|
||||
context=context)
|
||||
if project_work_ids:
|
||||
project_works = self.browse(
|
||||
cursor, user, project_work_ids, context=context)
|
||||
|
||||
keep_ids = set(pw.work.id for pw in project_works)
|
||||
timesheet_work_ids = [
|
||||
i for i in timesheet_work_ids if i not in keep_ids]
|
||||
|
||||
timesheet_work_obj.delete(
|
||||
cursor, user, timesheet_work_ids, context=context)
|
||||
return res
|
||||
|
||||
def default_project_type(self, cursor, user, context=None):
|
||||
return 'project'
|
||||
|
||||
Work()
|
||||
|
||||
@@ -3,56 +3,119 @@
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<tryton>
|
||||
<data>
|
||||
<!-- default tree view -->
|
||||
<record model="ir.ui.view" id="work_view_tree">
|
||||
<field name="model">project.work</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="field_childs">children</field>
|
||||
<field name="arch" type="xml">
|
||||
<![CDATA[
|
||||
<tree string="Works Efforts">
|
||||
<field name="name" select="1"/>
|
||||
<field name="hours" widget="float_time"/>
|
||||
<field name="total_effort" widget="float_time"/>
|
||||
<field name="type" select="1"/>
|
||||
<field name="state" select="1"/>
|
||||
<field name="active" select="2" tree_invisible="1"/>
|
||||
</tree>
|
||||
]]>
|
||||
</field>
|
||||
</record>
|
||||
<!-- tree view for tasks (wihtout type field) -->
|
||||
<record model="ir.ui.view" id="work_view_tree2">
|
||||
<field name="model">project.work</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<![CDATA[
|
||||
<tree string="Works">
|
||||
<field name="rec_name" select="1"/>
|
||||
<field name="project_type" select="1"/>
|
||||
<tree string="Tasks">
|
||||
<field name="name" select="1"/>
|
||||
<field name="hours" widget="float_time"/>
|
||||
<field name="total_effort" widget="float_time"/>
|
||||
<field name="state" select="1"/>
|
||||
<field name="active" select="2" tree_invisible="1"/>
|
||||
</tree>
|
||||
]]>
|
||||
</field>
|
||||
</record>
|
||||
<!-- tree view for projects (wihtout type and state fields) -->
|
||||
<record model="ir.ui.view" id="work_view_tree3">
|
||||
<field name="model">project.work</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<![CDATA[
|
||||
<tree string="Projects">
|
||||
<field name="name" select="1"/>
|
||||
<field name="hours" widget="float_time"/>
|
||||
<field name="total_effort" widget="float_time"/>
|
||||
<field name="active" select="2" tree_invisible="1"/>
|
||||
</tree>
|
||||
]]>
|
||||
</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="work_view_form">
|
||||
<field name="model">project.work</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<![CDATA[
|
||||
<form string="Work">
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="state"/>
|
||||
<group col="3" colspan="1" id="state_active">
|
||||
<field name="state"/>
|
||||
<form string="Work Efforts" col="8">
|
||||
<label name="name"/>
|
||||
<field name="name" colspan="3"/>
|
||||
<label name="type"/>
|
||||
<field name="type"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
<label name="parent"/>
|
||||
<field name="parent"/>
|
||||
<label name="project_type"/>
|
||||
<field name="project_type"/>
|
||||
<notebook colspan="4">
|
||||
<field name="active" xexpand="0"/>
|
||||
<label name="parent"/>
|
||||
<field name="parent"/>
|
||||
<label name="sequence"/>
|
||||
<field name="sequence"/>
|
||||
<label string="" id="placeholder1"/>
|
||||
<label string="" id="placeholder2"/>
|
||||
<label name="timesheet_available"/>
|
||||
<field name="timesheet_available" xexpand="0"/>
|
||||
<notebook colspan="8">
|
||||
<page string="General" id="general">
|
||||
<label name="party"/>
|
||||
<field name="party"/>
|
||||
<label name="party_address"/>
|
||||
<field name="party_address"/>
|
||||
<label name="hours"/>
|
||||
<field name="hours" widget="float_time"/>
|
||||
<group colspan="2" col="4" id="effort">
|
||||
<label name="effort"/>
|
||||
<field name="effort" widget="float_time"/>
|
||||
<label name="total_effort"/>
|
||||
<field name="total_effort" widget="float_time"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Children" id="children"
|
||||
states="{'invisible': '''type != 'project' '''}">
|
||||
<field name="children" colspan="4">
|
||||
<tree string="Children">
|
||||
<field name="name"/>
|
||||
<field name="hours" widget="float_time"/>
|
||||
<field name="total_effort" widget="float_time"/>
|
||||
<field name="type"/>
|
||||
<field name="state"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Timesheet" id="timesheet_lines"
|
||||
states="{'invisible': '''not bool(timesheet_available) '''}">
|
||||
<field name="timesheet_lines" colspan="4"/>
|
||||
</page>
|
||||
<page string="Other Info" id="other_info">
|
||||
<separator name="comment" colspan="4"/>
|
||||
<field name="comment" colspan="4"/>
|
||||
</page>
|
||||
</notebook>
|
||||
<label name="state"/>
|
||||
<field name="state"/>
|
||||
</form>
|
||||
]]>
|
||||
</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_work_form">
|
||||
<field name="name">Works</field>
|
||||
<field name="name">Works Efforts</field>
|
||||
<field name="res_model">project.work</field>
|
||||
<field name="view_type">form</field>
|
||||
</record>
|
||||
@@ -71,6 +134,90 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<menuitem parent="menu_configuration" action="act_work_form"
|
||||
id="menu_work_form"/>
|
||||
|
||||
<record model="ir.action.act_window" id="act_work_tree">
|
||||
<field name="name">Works</field>
|
||||
<field name="res_model">project.work</field>
|
||||
<field name="view_type">tree</field>
|
||||
<field name="domain">[('parent', '=', False)]</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view"
|
||||
id="act_work_tree_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="work_view_tree"/>
|
||||
<field name="act_window" ref="act_work_tree"/>
|
||||
</record>
|
||||
<menuitem parent="menu_project" action="act_work_tree"
|
||||
id="menu_project_tree" name="Projects"/>
|
||||
|
||||
<record model="ir.action.act_window" id="act_project_form">
|
||||
<field name="name">Projects</field>
|
||||
<field name="res_model">project.work</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="domain">[('type', '=', 'project')]</field>
|
||||
<field name="context">{'type': 'project'}</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view"
|
||||
id="act_project_form_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="work_view_tree3"/>
|
||||
<field name="act_window" ref="act_project_form"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view"
|
||||
id="act_project_form_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="work_view_form"/>
|
||||
<field name="act_window" ref="act_project_form"/>
|
||||
</record>
|
||||
<menuitem parent="menu_project_tree" action="act_project_form"
|
||||
id="menu_project_form"/>
|
||||
|
||||
<record model="ir.action.act_window" id="act_task_form">
|
||||
<field name="name">Tasks</field>
|
||||
<field name="res_model">project.work</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="domain">[('type', '=', 'task')]</field>
|
||||
<field name="context">{'type': 'task'}</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view"
|
||||
id="act_task_form_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="work_view_tree2"/>
|
||||
<field name="act_window" ref="act_task_form"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view"
|
||||
id="act_task_form_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="work_view_form"/>
|
||||
<field name="act_window" ref="act_task_form"/>
|
||||
</record>
|
||||
<menuitem parent="menu_project_tree" action="act_task_form"
|
||||
id="menu_task_form"/>
|
||||
|
||||
<record model="ir.action.act_window" id="act_open_child_work">
|
||||
<field name="name">Tasks</field>
|
||||
<field name="res_model">project.work</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="domain">[('parent', '=', active_id), ('type', '=', 'task')]</field>
|
||||
</record>
|
||||
<record model="ir.action.keyword"
|
||||
id="act_open_project_work_keyword1">
|
||||
<field name="keyword">form_relate</field>
|
||||
<field name="model">project.work,0</field>
|
||||
<field name="action" ref="act_open_child_work"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view"
|
||||
id="act_open_project_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="work_view_tree"/>
|
||||
<field name="act_window" ref="act_open_child_work"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view"
|
||||
id="act_open_project_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="work_view_form"/>
|
||||
<field name="act_window" ref="act_open_child_work"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.model.access" id="access_work">
|
||||
<field name="model" search="[('model', '=', 'project.work')]"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
@@ -87,29 +234,5 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
<menuitem parent="menu_project" action="timesheet.act_work_tree"
|
||||
id="menu_project_tree" name="Projects"/>
|
||||
|
||||
<record model="ir.action.act_window" id="act_project_form">
|
||||
<field name="name">Projects</field>
|
||||
<field name="res_model">project.work</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="domain">[('project_type', '=', 'project')]</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view"
|
||||
id="act_project_form_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="work_view_tree"/>
|
||||
<field name="act_window" ref="act_project_form"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view"
|
||||
id="act_project_form_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="work_view_form"/>
|
||||
<field name="act_window" ref="act_project_form"/>
|
||||
</record>
|
||||
<menuitem parent="menu_project_tree" action="act_project_form"
|
||||
id="menu_project_form"/>
|
||||
|
||||
</data>
|
||||
</tryton>
|
||||
Reference in new issue
Block a user