1
##############################################################################
3
# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
4
# Fabien Pinckaers <fp@tiny.Be>
6
# WARNING: This program as such is intended to be used by professional
7
# programmers who take the whole responsability of assessing all potential
8
# consequences resulting from its eventual inadequacies and bugs
9
# End users who are looking for a ready-to-use solution with commercial
10
# garantees and support are strongly adviced to contract a Free Software
13
# This program is Free Software; you can redistribute it and/or
14
# modify it under the terms of the GNU General Public License
15
# as published by the Free Software Foundation; either version 2
16
# of the License, or (at your option) any later version.
18
# This program is distributed in the hope that it will be useful,
19
# but WITHOUT ANY WARRANTY; without even the implied warranty of
20
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
# GNU General Public License for more details.
23
# You should have received a copy of the GNU General Public License
24
# along with this program; if not, write to the Free Software
25
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27
##############################################################################
31
from osv import fields, osv, orm
33
from mx import DateTime
36
class scrum_team(osv.osv):
38
_description = 'Scrum Team'
40
'name' : fields.char('Team Name', size=64),
41
'users_id' : fields.many2many('res.users', 'scrum_team_users_rel', 'team_id','user_id', 'Users'),
45
class scrum_project(osv.osv):
46
_name = 'scrum.project'
47
_inherit = 'project.project'
48
_table = 'project_project'
49
_description = 'Scrum Project'
51
'product_owner_id': fields.many2one('res.users', 'Product Owner'),
52
'tasks': fields.one2many('scrum.task', 'project_id', 'Scrum Tasks'),
53
'sprint_size': fields.integer('Sprint Days'),
54
'scrum': fields.integer('Is Scrum'),
57
'product_owner_id': lambda self,cr,uid,context={}: uid,
58
'warn_manager': lambda *a: 1,
59
'sprint_size': lambda *a: 14,
64
class scrum_sprint(osv.osv):
65
_name = 'scrum.sprint'
66
_description = 'Scrum Sprint'
67
def _calc_progress(self, cr, uid, ids, name, args, context):
69
for sprint in self.browse(cr, uid, ids):
72
for bl in sprint.backlog_ids:
73
tot += bl.planned_hours
74
prog += bl.planned_hours * bl.progress / 100.0
75
res.setdefault(sprint.id, 0.0)
77
res[sprint.id] = round(prog/tot*100)
79
def _calc_effective(self, cr, uid, ids, name, args, context):
81
for sprint in self.browse(cr, uid, ids):
82
res.setdefault(sprint.id, 0.0)
83
for bl in sprint.backlog_ids:
84
res[sprint.id] += bl.effective_hours
86
def _calc_planned(self, cr, uid, ids, name, args, context):
88
for sprint in self.browse(cr, uid, ids):
89
res.setdefault(sprint.id, 0.0)
90
for bl in sprint.backlog_ids:
91
res[sprint.id] += bl.planned_hours
94
'name' : fields.char('Sprint Name', size=64),
95
'date_start': fields.date('Starting Date', required=True),
96
'date_stop': fields.date('Ending Date', required=True),
97
'project_id': fields.many2one('scrum.project', 'Project', required=True, domain=[('scrum','=',1)]),
98
'product_owner_id': fields.many2one('res.users', 'Product Owner', required=True),
99
'scrum_master_id': fields.many2one('res.users', 'Scrum Master', required=True),
100
'meetings_id': fields.one2many('scrum.meeting', 'sprint_id', 'Daily Scrum'),
101
'review': fields.text('Sprint Review'),
102
'retrospective': fields.text('Sprint Retrospective'),
103
'backlog_ids': fields.one2many('scrum.product.backlog', 'sprint_id', 'Sprint Backlog'),
104
'progress': fields.function(_calc_progress, method=True, string='Progress (0-100)'),
105
'effective_hours': fields.function(_calc_effective, method=True, string='Effective hours'),
106
'planned_hours': fields.function(_calc_planned, method=True, string='Planned Hours'),
107
'state': fields.selection([('draft','Draft'),('open','Open'),('done','Done')], 'State', required=True),
110
'state': lambda *a: 'draft',
111
'date_start' : lambda *a:time.strftime('%Y-%m-%d'),
113
def onchange_project_id(self, cr, uid, ids, project_id):
116
proj = self.pool.get('scrum.project').browse(cr, uid, [project_id])[0]
117
v['product_owner_id']= proj.product_owner_id.id
118
v['scrum_master_id']= proj.manager.id
119
v['date_stop'] = (DateTime.now() + DateTime.RelativeDateTime(days=int(proj.sprint_size or 15))).strftime('%Y-%m-%d')
124
class scrum_product_backlog(osv.osv):
125
_name = 'scrum.product.backlog'
126
_description = 'Product Backlog'
128
def name_search(self, cr, uid, name, args=[], operator='ilike', context={}):
129
match = re.match('^S\(([0-9]+)\)$', name)
131
ids = self.search(cr, uid, [('sprint_id','=', int(match.group(1)))])
132
return self.name_get(cr, uid, ids, context=context)
133
return super(scrum_product_backlog, self).name_search(cr, uid, name, args,operator,context)
135
def _calc_progress(self, cr, uid, ids, name, args, context):
137
for bl in self.browse(cr, uid, ids):
140
for task in bl.tasks_id:
141
tot += task.planned_hours
142
prog += task.planned_hours * task.progress / 100.0
143
res.setdefault(bl.id, 0.0)
145
res[bl.id] = round(prog/tot*100)
147
def _calc_effective(self, cr, uid, ids, name, args, context):
149
for bl in self.browse(cr, uid, ids):
150
res.setdefault(bl.id, 0.0)
151
for task in bl.tasks_id:
152
res[bl.id] += task.effective_hours
154
def _calc_planned(self, cr, uid, ids, name, args, context):
156
for bl in self.browse(cr, uid, ids):
157
res.setdefault(bl.id, 0.0)
158
for task in bl.tasks_id:
159
res[bl.id] += task.planned_hours
162
'name' : fields.char('Feature', size=64),
163
'note' : fields.text('Note'),
164
'active' : fields.boolean('Active'),
165
'project_id': fields.many2one('scrum.project', 'Scrum Project', required=True, domain=[('scrum','=',1)]),
166
'user_id': fields.many2one('res.users', 'User'),
167
'sprint_id': fields.many2one('scrum.sprint', 'Sprint'),
168
'sequence' : fields.integer('Sequence'),
169
'priority' : fields.selection([('4','Very Low'), ('3','Low'), ('2','Medium'), ('1','Urgent'), ('0','Very urgent')], 'Priority'),
170
'tasks_id': fields.one2many('scrum.task', 'product_backlog_id', 'Tasks Details'),
171
'state': fields.selection([('draft','Draft'),('open','Open'),('done','Done')], 'State', required=True),
172
'progress': fields.function(_calc_progress, method=True, string='Progress (0-100)'),
173
'effective_hours': fields.function(_calc_effective, method=True, string='Effective hours'),
174
'planned_hours': fields.function(_calc_planned, method=True, string='Planned Hours')
177
'priority': lambda *a: '4',
178
'state': lambda *a: 'draft',
179
'active': lambda *a: 1
181
_order = "priority,sequence"
182
scrum_product_backlog()
184
class scrum_task(osv.osv):
186
_inherit = 'project.task'
187
_table = 'project_task'
188
_description = 'Scrum Task'
190
'product_backlog_id': fields.many2one('scrum.product.backlog', 'Product Backlog'),
191
'scrum': fields.integer('Is Scrum'),
194
'scrum': lambda *a: 1,
196
def onchange_backlog_id(self, cr, uid, backlog_id):
199
project_id = self.pool.get('scrum.product.backlog').browse(cr, uid, backlog_id).project_id.id
200
return {'value': {'project_id': project_id}}
203
class scrum_meeting(osv.osv):
204
_name = 'scrum.meeting'
205
_description = 'Scrum Meeting'
207
'name' : fields.char('Meeting Name', size=64, required=True),
208
'date': fields.date('Meeting Date', required=True),
209
'sprint_id': fields.many2one('scrum.sprint', 'Sprint', required=True),
210
'question_yesterday': fields.text('Tasks since yesterday'),
211
'question_today': fields.text('Tasks for today'),
212
'question_blocks': fields.text('Blocks encountered'),
214
# Should be more formal.
216
'question_backlog': fields.text('Backlog Accurate'),
219
# Find the right sprint thanks to users and date
222
'date' : lambda *a:time.strftime('%Y-%m-%d'),