1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ##############################################################################
25 from osv import fields, osv, orm
27 from mx import DateTime
30 class scrum_team(osv.osv):
32 _description = 'Scrum Team'
34 'name' : fields.char('Team Name', size=64),
35 'users_id' : fields.many2many('res.users', 'scrum_team_users_rel', 'team_id','user_id', 'Users'),
39 class scrum_project(osv.osv):
40 _name = 'scrum.project'
41 _inherit = 'project.project'
42 _table = 'project_project'
43 _description = 'Scrum Project'
45 'product_owner_id': fields.many2one('res.users', 'Product Owner'),
46 'tasks': fields.one2many('scrum.task', 'project_id', 'Scrum Tasks'),
47 'sprint_size': fields.integer('Sprint Days'),
48 'scrum': fields.integer('Is Scrum'),
49 'parent_id': fields.many2one('scrum.project', 'Parent project'),
52 'product_owner_id': lambda self,cr,uid,context={}: uid,
53 'warn_manager': lambda *a: 1,
54 'sprint_size': lambda *a: 14,
59 class scrum_sprint(osv.osv):
60 _name = 'scrum.sprint'
61 _description = 'Scrum Sprint'
62 def _calc_progress(self, cr, uid, ids, name, args, context):
64 for sprint in self.browse(cr, uid, ids):
67 for bl in sprint.backlog_ids:
68 tot += bl.planned_hours
69 prog += bl.planned_hours * bl.progress / 100.0
70 res.setdefault(sprint.id, 0.0)
72 res[sprint.id] = round(prog/tot*100)
74 def _calc_effective(self, cr, uid, ids, name, args, context):
76 for sprint in self.browse(cr, uid, ids):
77 res.setdefault(sprint.id, 0.0)
78 for bl in sprint.backlog_ids:
79 res[sprint.id] += bl.effective_hours
81 def _calc_planned(self, cr, uid, ids, name, args, context):
83 for sprint in self.browse(cr, uid, ids):
84 res.setdefault(sprint.id, 0.0)
85 for bl in sprint.backlog_ids:
86 res[sprint.id] += bl.planned_hours
89 'name' : fields.char('Sprint Name', required=True, size=64),
90 'date_start': fields.date('Starting Date', required=True),
91 'date_stop': fields.date('Ending Date', required=True),
92 'project_id': fields.many2one('scrum.project', 'Project', required=True, domain=[('scrum','=',1)]),
93 'product_owner_id': fields.many2one('res.users', 'Product Owner', required=True),
94 'scrum_master_id': fields.many2one('res.users', 'Scrum Master', required=True),
95 'meetings_id': fields.one2many('scrum.meeting', 'sprint_id', 'Daily Scrum'),
96 'review': fields.text('Sprint Review'),
97 'retrospective': fields.text('Sprint Retrospective'),
98 'backlog_ids': fields.one2many('scrum.product.backlog', 'sprint_id', 'Sprint Backlog'),
99 'progress': fields.function(_calc_progress, method=True, string='Progress (0-100)'),
100 'effective_hours': fields.function(_calc_effective, method=True, string='Effective hours'),
101 'planned_hours': fields.function(_calc_planned, method=True, string='Planned Hours'),
102 'state': fields.selection([('draft','Draft'),('open','Open'),('done','Done')], 'Status', required=True),
105 'state': lambda *a: 'draft',
106 'date_start' : lambda *a:time.strftime('%Y-%m-%d'),
108 def onchange_project_id(self, cr, uid, ids, project_id):
111 proj = self.pool.get('scrum.project').browse(cr, uid, [project_id])[0]
112 v['product_owner_id']= proj.product_owner_id.id
113 v['scrum_master_id']= proj.manager.id
114 v['date_stop'] = (DateTime.now() + DateTime.RelativeDateTime(days=int(proj.sprint_size or 14))).strftime('%Y-%m-%d')
119 class scrum_product_backlog(osv.osv):
120 _name = 'scrum.product.backlog'
121 _description = 'Product Backlog'
123 def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=80):
128 match = re.match('^S\(([0-9]+)\)$', name)
130 ids = self.search(cr, uid, [('sprint_id','=', int(match.group(1)))], limit=limit, context=context)
131 return self.name_get(cr, uid, ids, context=context)
132 return super(scrum_product_backlog, self).name_search(cr, uid, name, args, operator,context, limit=limit)
134 def _calc_progress(self, cr, uid, ids, name, args, context):
136 for bl in self.browse(cr, uid, ids):
139 for task in bl.tasks_id:
140 tot += task.planned_hours
141 prog += task.planned_hours * task.progress / 100.0
142 res.setdefault(bl.id, 0.0)
144 res[bl.id] = round(prog/tot*100)
146 def _calc_effective(self, cr, uid, ids, name, args, context):
148 for bl in self.browse(cr, uid, ids):
149 res.setdefault(bl.id, 0.0)
150 for task in bl.tasks_id:
151 res[bl.id] += task.effective_hours
153 def _calc_planned(self, cr, uid, ids, name, args, context):
155 for bl in self.browse(cr, uid, ids):
156 res.setdefault(bl.id, 0.0)
157 for task in bl.tasks_id:
158 res[bl.id] += task.planned_hours
161 'name' : fields.char('Feature', size=64, required=True),
162 'note' : fields.text('Note'),
163 'active' : fields.boolean('Active'),
164 'project_id': fields.many2one('scrum.project', 'Scrum Project', required=True, domain=[('scrum','=',1)]),
165 'user_id': fields.many2one('res.users', 'User'),
166 'sprint_id': fields.many2one('scrum.sprint', 'Sprint'),
167 'sequence' : fields.integer('Sequence'),
168 'priority' : fields.selection([('4','Very Low'), ('3','Low'), ('2','Medium'), ('1','Urgent'), ('0','Very urgent')], 'Priority'),
169 'tasks_id': fields.one2many('scrum.task', 'product_backlog_id', 'Tasks Details'),
170 'state': fields.selection([('draft','Draft'),('open','Open'),('done','Done')], 'Status', required=True),
171 'progress': fields.function(_calc_progress, method=True, string='Progress (0-100)'),
172 'effective_hours': fields.function(_calc_effective, method=True, string='Effective hours'),
173 'planned_hours': fields.function(_calc_planned, method=True, string='Planned Hours')
176 'priority': lambda *a: '4',
177 'state': lambda *a: 'draft',
178 'active': lambda *a: 1
180 _order = "priority,sequence"
181 scrum_product_backlog()
183 class scrum_task(osv.osv):
185 _inherit = 'project.task'
186 _table = 'project_task'
187 _description = 'Scrum Task'
189 'product_backlog_id': fields.many2one('scrum.product.backlog', 'Product Backlog'),
190 'scrum': fields.integer('Is Scrum'),
193 'scrum': lambda *a: 1,
195 def onchange_backlog_id(self, cr, uid, backlog_id):
198 project_id = self.pool.get('scrum.product.backlog').browse(cr, uid, backlog_id).project_id.id
199 return {'value': {'project_id': project_id}}
202 class scrum_meeting(osv.osv):
203 _name = 'scrum.meeting'
204 _description = 'Scrum Meeting'
206 'name' : fields.char('Meeting Name', size=64, required=True),
207 'date': fields.date('Meeting Date', required=True),
208 'sprint_id': fields.many2one('scrum.sprint', 'Sprint', required=True),
209 'question_yesterday': fields.text('Tasks since yesterday'),
210 'question_today': fields.text('Tasks for today'),
211 'question_blocks': fields.text('Blocks encountered'),
213 # Should be more formal.
215 'question_backlog': fields.text('Backlog Accurate'),
218 # Find the right sprint thanks to users and date
221 'date' : lambda *a:time.strftime('%Y-%m-%d'),
226 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: