############################################################################## # # Copyright (c) 2005 TINY SPRL. (http://tiny.be) All Rights Reserved. # # $Id: project.py 1011 2005-07-26 08:11:45Z nicoe $ # # WARNING: This program as such is intended to be used by professional # programmers who take the whole responsability of assessing all potential # consequences resulting from its eventual inadequacies and bugs # End users who are looking for a ready-to-use solution with commercial # garantees and support are strongly adviced to contract a Free Software # Service Company # # This program is Free Software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ############################################################################## from mx import DateTime from mx.DateTime import now import time import sql_db import netsvc from tools import email_send from osv import fields, osv, orm import ir class project(osv.osv): _name = "project.project" _description = "Project" def _calc_effective(self, cr, uid, ids, name, args, context): res = {} projs = self.browse(cr, uid, ids) for proj in projs: if proj.id not in res: res[proj.id] = 0 for child in proj.child_id: res[proj.id] += child.effective_hours for task in proj.tasks: res[proj.id] += task.effective_hours return res def _calc_planned(self, cr, uid, ids, name, args, context): res = {} projs = self.browse(cr, uid, ids) for proj in projs: if proj.id not in res: res[proj.id] = 0 for child in proj.child_id: res[proj.id] += child.planned_hours for task in proj.tasks: res[proj.id] += task.planned_hours return res def onchange_partner_id(self, cr, uid, ids, part): if not part: return {'value':{'contact_id': False, 'pricelist_id': False}} addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['contact']) pricelist=ir.ir_get(cr,uid,'meta','product.pricelist',[('res.partner',part)])[0][2] return {'value':{'contact_id': addr['contact'], 'pricelist_id': pricelist}} _columns = { 'name' : fields.char("Project name", size=128, required=True), 'active' : fields.boolean('Active'), 'category_id' : fields.many2one('account.project','Accounting Classification'), 'priority' : fields.integer('Priority'), 'manager' : fields.many2one('res.users', 'Project manager', relate=True), 'warn_manager' : fields.boolean('Warn manager'), 'members' : fields.many2many('res.users', 'project_user_rel', 'project_id', 'uid', 'Project members'), 'tasks' : fields.one2many('project.task', 'project_id', "Project tasks"), 'parent_id' : fields.many2one('project.project', 'Parent project'), 'child_id' : fields.one2many('project.project', 'parent_id', 'Subproject'), 'planned_hours' : fields.function(_calc_planned, method=True, string='Hours planned'), 'effective_hours' : fields.function(_calc_effective, method=True, string='Hours spent'), 'date_start' : fields.date('Project started on'), 'date_end' : fields.date('Project should end on'), 'tariff' : fields.float('Sales price'), 'mode' : fields.selection([('project', 'By project'), ('hour', 'By hour'), ('effective', 'By effective hour')], 'Price setting mode'), 'partner_id' : fields.many2one('res.partner', 'Customer'), 'contact_id' : fields.many2one('res.partner.address', 'Contact'), 'shop_id' : fields.many2one('sale.shop', 'Shop'), 'pricelist_id' : fields.many2one('product.pricelist', 'Pricelist'), 'tax_ids' : fields.many2many('account.tax', 'project_account_tax_rel', 'project_id','tax_id', 'Applicable taxes'), 'warn_customer' : fields.boolean('Warn customer'), 'warn_header' : fields.text('Mail header'), 'warn_footer' : fields.text('Mail footer'), 'notes' : fields.text('Notes'), 'timesheet_id' : fields.many2one('hr.timesheet.group', 'Timesheet'), 'state' : fields.selection([('inactive', 'Inactive'), ('draft', 'Draft'), ('finished', 'Finished'), ('open', 'Opened')]), } _defaults = { 'active' : lambda *a: True, 'manager' : lambda x,y,z,c: z, 'priority' : lambda *a: 1, 'date_start' : lambda *a:time.strftime('%Y-%m-%d'), } _order = "priority" def deactivate(self, cr, uid, ids): projs = self.browse(cr, uid, ids) task_obj = self.pool.get('project.task') for proj in projs: self.write(cr, uid, [proj.id], { 'active' : False }) for task in proj.tasks: task_obj.write(cr, uid, [task.id], { 'active' : False }) self.deactivate(cr, uid, [sp.id for sp in proj.child_id]) return {} def copy(self, cr, uid, ids): ret = [] projs = self.browse(cr, uid, ids) for proj in projs: default = {'state': 'draft', 'active' : True, 'date_start' : now().strftime('%Y-%m-%d'), 'warn_header' : '', 'warn_footer' : '', 'parent_id' : '', 'tasks' : ''} nid = super(project, self).copy(cr, uid, proj.id, default) ret.append(nid) childs = [sp.id for sp in proj.child_id] nchilds = self.copy(cr, uid, childs) self.write(cr, uid, nchilds, {'parent_id' : nid}) return ret def toggleActive(self, cr, uid, ids, context={}): if not ids: return 0 task_objs = self.pool.get('project.task') projs = self.browse(cr, uid, ids) for proj in projs: self.write(cr, uid, [proj.id], {'active' : not proj.active}) task_objs.write(cr, uid, [task.id for task in proj.tasks], {'state' : 'inactive'}) self.toggleActive(cr, uid, [child.id for child in proj.child_id], context) return 0 project() class project_task_type(osv.osv): _name = 'project.task.type' _description = 'Project task type' _columns = { 'name' : fields.char('Type', size=64), 'description' : fields.text('Description'), } project_task_type() class task(osv.osv): _name = "project.task" _description = "Task" _columns = { 'name' : fields.char('Task summary', size=128, required=True), 'active' : fields.boolean('Active'), 'description' : fields.text('Description'), 'cust_desc' : fields.text("Description for the customer"), 'priority' : fields.selection([('0', 'Very Low'), ('1', 'Low'), ('2', 'Normal'), ('3', 'High'), ('4', 'Very High')], 'Priority'), 'sequence' : fields.integer('Sequence'), 'type' : fields.many2one('project.task.type', 'Type'), 'state' : fields.selection([('inactive', 'Inactive'), ('open', 'Open'), ('progress', 'In progress'), ('cancelled', 'Cancelled'), ('done', 'Done')], 'State'), 'date_start': fields.datetime('Date Start'), 'date_deadline' : fields.datetime('Deadline'), 'date_close' : fields.datetime('Date Closed', readonly=True), 'project_id' : fields.many2one('project.project', 'Project', ondelete='cascade'), 'comments' : fields.one2many('project.task.comment', 'task_id', 'Task comments'), 'start_sequence' : fields.boolean('Wait for previous sequences'), 'planned_hours' : fields.float('Planned hours'), 'effective_hours' : fields.float('Effective hours'), 'progress' : fields.integer('Progress (0-100)'), 'billable' : fields.boolean('To be invoiced'), 'product_id' : fields.many2one('product.product','Product', relate=True), 'product_uom' : fields.many2one('product.uom','Product UoM'), 'product_qty' : fields.integer('Product Quantity'), 'order_id' : fields.many2one('sale.order','Sale Order', relate=True), 'user_id' : fields.many2one('res.users', 'Assigned to', required=True, relate=True), 'partner_id' : fields.many2one('res.partner', 'Customer'), } _defaults = { 'user_id' : lambda x,y,z,c:z, 'state' : lambda *a: 'open', 'priority' : lambda *a: '1', 'progress' : lambda *a: 0, 'sequence' : lambda *a: 10, 'active' : lambda *a: True, } _order = "state, sequence, priority desc" def do_close(self, cr, uid, ids, *args): import md5 logger = netsvc.Logger() request = self.pool.get('res.request') tasks = self.browse(cr, uid, ids) for task in tasks: project = task.project_id if project: if project.warn_manager: request.create(cr, uid, { 'name' : "Task '%s' closed" % task.name, 'state' : 'waiting', 'act_from' : uid, 'act_to' : project.manager.id, 'ref_partner_id' : task.partner_id.id, 'ref_doc1' : 'project.task,%d'% (task.id,), 'ref_doc2' : 'project.project,%d'% (project.id,), }) if project.warn_customer and task.cust_desc: signature = '' if task.user_id.address_id and task.user_id.address_id.email: From = task.user_id.address_id.email signature = task.user_id.signature else: From = 'no-one@nowhere' if project.contact_id and project.contact_id.email: To = project.contact_id.email else: To = None if To: subj = "Task '%s' closed" % task.name header = project.warn_header % {'name' : task.name, 'user_id' : task.user_id.name, 'task_id' : md5.new("%sTiny%s" % (task.id, project.id)).hexdigest()} footer = project.warn_footer % {'name' : task.name, 'user_id' : task.user_id.name, 'task_id' : md5.new("%sTiny%s" % (task.id, project.id)).hexdigest()} body = '%s\n%s\n%s\n\n-- \n%s' % (header, task.cust_desc, footer, signature) email_send(From, To, subj, body, None) self.write(cr, uid, [task.id], {'state' : 'done', 'date_close':time.strftime('%Y-%m-%d %H:%M'), 'progress': 100}) return True def do_reopen(self, cr, uid, ids, *args): request = self.pool.get('res.request') tasks = self.browse(cr, uid, ids) for task in tasks: if task.project_id.warn_manager: request.create(cr, uid, {'name' : "Task '%s' reopened" % task.name, 'act_from' : uid, 'ref_partner_id' : task.partner_id.id, 'state':'waiting', 'act_to' : task.project_id.manager.id, 'ref_doc1' : 'project.task,%d'% (task.id,), 'ref_doc2' : 'project.project,%d'% (task.project_id.id,), }) self.write(cr, uid, [task.id], {'state' : 'open'}) return True def do_cancel(self, cr, uid, ids, *args): request = self.pool.get('res.request') tasks = self.browse(cr, uid, ids) for task in tasks: if task.project_id.warn_manager: request.create(cr, uid, {'name' : "Task '%s' cancelled" % task.name, 'state' : 'waiting', 'act_from' : uid, 'act_to' : task.project_id.manager.id, 'ref_partner_id' : task.partner_id.id, 'ref_doc1' : 'project.task,%d'% (task.id,), 'ref_doc2' : 'project.project,%d'% (task.project_id.id,), }) if task.project_id.warn_customer: if task.user_id.address_id and task.user_id.address_id.email: From = task.user_id.address_id else: From = 'no-one@nowhere' if task.project_id.contact_id and task.project_id.contact_id.email: To = task.project_id.contact_id.email else: To = None if To: subj = "Task '%s' cancelled" % task.name header = task.project_id.warn_header % {'name' : task.name, 'user_id' : task.user_id.name} footer = task.project_id.warn_footer % {'name' : task.name, 'user_id' : task.user_id.name} body = '%s\n%s\n%s\n\n-- \n%s' % (header, task.cust_desc, footer, signature) email_send(From, To, subj, body, None) self.write(cr, uid, [task.id], {'state' : 'cancelled'}) return True task() class comment(osv.osv): _name = 'project.task.comment' _description = 'Task comment' _columns = { 'name' : fields.many2one('res.users', 'Author'), 'date' : fields.date('Date'), 'content' : fields.text('Comment'), 'task_id' : fields.many2one('project.task', 'Task'), } _defaults = { 'name' : lambda self,cr,uid,context:uid, 'date' : lambda *a:time.strftime('%Y-%m-%d'), } comment()