[MERGE] Merged Dhruti's branch for the fix of avoiding crash when reservation is...
[odoo/odoo.git] / addons / scrum / scrum.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution   
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
6 #    $Id$
7 #
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.
12 #
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.
17 #
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/>.
20 #
21 ##############################################################################
22
23 import time
24 import netsvc
25 from osv import fields, osv, orm
26
27 from mx import DateTime
28 import re
29
30 class scrum_team(osv.osv):
31     _name = 'scrum.team'
32     _description = 'Scrum Team'
33     _columns = {
34         'name' : fields.char('Team Name', size=64),
35         'users_id' : fields.many2many('res.users', 'scrum_team_users_rel', 'team_id','user_id', 'Users'),
36     }
37 scrum_team()
38
39 class scrum_project(osv.osv):
40     _name = 'scrum.project'
41     _inherit = 'project.project'
42     _table = 'project_project'
43     _description = 'Scrum Project'
44     _columns = {
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'),
50     }
51     _defaults = {
52         'product_owner_id': lambda self,cr,uid,context={}: uid,
53         'warn_manager': lambda *a: 1,
54         'sprint_size': lambda *a: 14,
55         'scrum': lambda *a: 1
56     }
57 scrum_project()
58
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):
63         res = {}
64         for sprint in self.browse(cr, uid, ids):
65             tot = 0.0
66             prog = 0.0
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)
71             if tot>0:
72                 res[sprint.id] = round(prog/tot*100)
73         return res
74     def _calc_effective(self, cr, uid, ids, name, args, context):
75         res = {}
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
80         return res
81     def _calc_planned(self, cr, uid, ids, name, args, context):
82         res = {}
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
87         return res
88     _columns = {
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),
103     }
104     _defaults = {
105         'state': lambda *a: 'draft',
106         'date_start' : lambda *a:time.strftime('%Y-%m-%d'),
107     }
108     def onchange_project_id(self, cr, uid, ids, project_id):
109         v = {}
110         if 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')
115         return {'value':v}
116         
117 scrum_sprint()
118
119 class scrum_product_backlog(osv.osv):
120     _name = 'scrum.product.backlog'
121     _description = 'Product Backlog'
122
123     def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=80):
124         if not args:
125             args=[]
126         if not context:
127             context={}
128         match = re.match('^S\(([0-9]+)\)$', name)
129         if match:
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)
133
134     def _calc_progress(self, cr, uid, ids, name, args, context):
135         res = {}
136         for bl in self.browse(cr, uid, ids):
137             tot = 0.0
138             prog = 0.0
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)
143             if tot>0:
144                 res[bl.id] = round(prog/tot*100)
145         return res
146     def _calc_effective(self, cr, uid, ids, name, args, context):
147         res = {}
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
152         return res
153     def _calc_planned(self, cr, uid, ids, name, args, context):
154         res = {}
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
159         return res
160     _columns = {
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')
174     }
175     _defaults = {
176         'priority': lambda *a: '4',
177         'state': lambda *a: 'draft',
178         'active': lambda *a: 1
179     }
180     _order = "priority,sequence"
181 scrum_product_backlog()
182
183 class scrum_task(osv.osv):
184     _name = 'scrum.task'
185     _inherit = 'project.task'
186     _table = 'project_task'
187     _description = 'Scrum Task'
188     _columns = {
189         'product_backlog_id': fields.many2one('scrum.product.backlog', 'Product Backlog'),
190         'scrum': fields.integer('Is Scrum'),
191     }
192     _defaults = {
193         'scrum': lambda *a: 1,
194     }
195     def onchange_backlog_id(self, cr, uid, backlog_id):
196         if not backlog_id:
197             return {}
198         project_id = self.pool.get('scrum.product.backlog').browse(cr, uid, backlog_id).project_id.id
199         return {'value': {'project_id': project_id}}
200 scrum_task()
201
202 class scrum_meeting(osv.osv):
203     _name = 'scrum.meeting'
204     _description = 'Scrum Meeting'
205     _columns = {
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'),
212         #
213         # Should be more formal.
214         #
215         'question_backlog': fields.text('Backlog Accurate'),
216     }
217     #
218     # Find the right sprint thanks to users and date
219     #
220     _defaults = {
221         'date' : lambda *a:time.strftime('%Y-%m-%d'),
222     }
223 scrum_meeting()
224
225
226 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
227