diff --git a/addons/project_calendar/__init__.py b/addons/project_calendar/__init__.py new file mode 100644 index 00000000000..08bcccf6426 --- /dev/null +++ b/addons/project_calendar/__init__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +import project_calendar +import wizard + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/project_calendar/__terp__.py b/addons/project_calendar/__terp__.py new file mode 100644 index 00000000000..6d7dd6763d9 --- /dev/null +++ b/addons/project_calendar/__terp__.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +{ + "name" : "Caldav task management", + "version" : "1.0", + "author" : "Tiny", + "category" : "Generic Modules/Others", + "description": """ Synchronize between Project task and Caldav Vtodo.""", + "depends" : ["project", "caldav"], + "init_xml" : [], + "demo_xml" : [], + "update_xml" : ["project_calendar_view.xml", + "project_calendar_wizard.xml", + "security/ir.model.access.csv"], + "active": False, + "website": "http://www.openerp.com", + "installable": True, +} diff --git a/addons/project_calendar/project_calendar.py b/addons/project_calendar/project_calendar.py new file mode 100644 index 00000000000..d37026df747 --- /dev/null +++ b/addons/project_calendar/project_calendar.py @@ -0,0 +1,333 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from datetime import datetime +from osv import fields, osv +from service import web_services +import base64 + +def caldevIDs2readIDs(caldev_ID = None): + if caldev_ID: + if isinstance(caldev_ID, str): + return int(caldev_ID.split('-')[0]) + return caldev_ID + +class crm_caldav_attendee(osv.osv): + _name = 'crm.caldav.attendee' + _description = 'Attendee information' + _rec_name = 'cutype' + + __attribute__ = { + 'cutype': {'field':'cutype', 'type':'text'}, + 'member': {'field':'member', 'type':'text'}, + 'role': {'field':'role', 'type':'selection'}, + 'partstat': {'field':'partstat', 'type':'text'}, + 'rsvp': {'field':'rsvp', 'type':'boolean'}, + 'delegated-to': {'field':'delegated_to', 'type':'char'}, + 'delegated-from': {'field':'delegated_from', 'type':'char'}, + 'sent-by': {'field':'sent_by', 'type':'text'}, + 'cn': {'field':'cn', 'type':'text'}, + 'dir': {'field':'dir', 'type':'text'}, + 'language': {'field':'language', 'type':'text'}, + } + + _columns = { + 'cutype': fields.selection([('INDIVIDUAL', 'INDIVIDUAL'), ('GROUP', 'GROUP'), \ + ('RESOURCE', 'RESOURCE'), ('ROOM', 'ROOM'), \ + ('UNKNOWN', 'UNKNOWN') ], 'CUTYPE'), + 'member': fields.char('Member', size=124), + 'role': fields.selection([ ('REQ-PARTICIPANT', 'REQ-PARTICIPANT'), \ + ('CHAIR', 'CHAIR'), ('OPT-PARTICIPANT', 'OPT-PARTICIPANT'), \ + ('NON-PARTICIPANT', 'NON-PARTICIPANT')], 'ROLE'), + 'partstat': fields.selection([('NEEDS-ACTION', 'NEEDS-ACTION'), \ + ('ACCEPTED', 'ACCEPTED'), ('DECLINED', 'DECLINED'), \ + ('TENTATIVE', 'TENTATIVE'), \ + ('DELEGATED', 'DELEGATED')], 'PARTSTAT'), + 'rsvp': fields.boolean('RSVP'), + 'delegated_to': fields.char('DELEGATED-TO', size=124), + 'delegated_from': fields.char('DELEGATED-FROM', size=124), + 'sent_by': fields.char('SENT-BY', size=124), + 'cn': fields.char('CN', size=124), + 'dir': fields.char('DIR', size=124), + 'language': fields.char('LANGUAGE', size=124), + } + _defaults = { + 'cn': lambda *x: 'MAILTO:', + } + +crm_caldav_attendee() + +class crm_caldav_alarm(osv.osv): + _name = 'crm.caldav.alarm' + _description = 'Event alarm information' + + __attribute__ = { + 'action': {'field': 'action', 'type': 'text'}, + 'description': {'field': 'name', 'type': 'text'}, + 'summary': {'field': 'description', 'type': 'text'}, + 'attendee': {'field': 'attendee_ids', 'type': 'text'}, + 'trigger_related': {'field': 'trigger_related', 'type': 'text'}, + 'trigger_duration': {'field': 'trigger_duration', 'type': 'text'}, + 'trigger_occurs': {'field': 'trigger_occurs', 'type': 'text'}, + 'trigger_interval': {'field': 'trigger_interval', 'type': 'text'}, + 'duration': {'field': 'duration', 'type': 'text'}, + 'repeat': {'field': 'repeat', 'type': 'text'}, + 'attach': {'field': 'attach', 'type': 'text'}, + } + + _columns = { + 'name': fields.char('Summary', size=124), + 'action': fields.selection([('AUDIO', 'AUDIO'), ('DISPLAY', 'DISPLAY'), \ + ('PROCEDURE', 'PROCEDURE'), ('EMAIL', 'EMAIL') ], 'Action', required=True), + 'description': fields.text('Description'), + 'attendee_ids': fields.many2many('crm.caldav.attendee', 'alarm_attendee_rel', \ + 'alarm_id', 'attendee_id', 'Attendees'), + 'trigger_occurs': fields.selection([('BEFORE', 'BEFORE'), ('AFTER', 'AFTER')], \ + 'Trigger time', required=True), + 'trigger_interval': fields.selection([('MINUTES', 'MINUTES'), ('HOURS', 'HOURS'), \ + ('DAYS', 'DAYS')], 'Trugger duration', required=True), + 'trigger_duration': fields.integer('TIme', required=True), + 'trigger_related': fields.selection([('start', 'The event starts'), ('end', \ + 'The event ends')], 'Trigger Occures at', required=True), + 'duration': fields.integer('Duration'), + 'repeat': fields.integer('Repeat'), # TODO + 'attach': fields.binary('Attachment'), + 'active': fields.boolean('Active'), + } + + _defaults = { + 'action': lambda *x: 'EMAIL', + 'trigger_interval': lambda *x: 'MINUTES', + 'trigger_duration': lambda *x: 5, + 'trigger_occurs': lambda *x: 'BEFORE', + 'trigger_related': lambda *x: 'start', + } + +crm_caldav_alarm() + +class project_task(osv.osv): + _inherit = "project.task" + + def _get_rdates(self, cr, uid, ids, name, arg, context=None): + res = {} + context.update({'read':True}) + for task in self.read(cr, uid, ids, ['date_start', 'rrule'], context=context): + if task['rrule']: + rule = task['rrule'].split('\n')[0] + if rule.upper().find('COUNT') < 0: # Temp fix, needs review + rule += ';COUNT=5' + exdate = task['rrule'].split('\n')[1:] + todo_obj = self.pool.get('caldav.todo') + res[task['id']] = str(todo_obj.get_recurrent_dates(str(rule), exdate, task['date_start'])) + return res + + _columns = { + 'class': fields.selection([('PUBLIC', 'PUBLIC'), ('PRIVATE', 'PRIVATE'), \ + ('CONFIDENTIAL', 'CONFIDENTIAL')], 'Class'), + 'location': fields.text('Location'), + 'rrule': fields.text('Recurrent Rule'), + 'rdates': fields.function(_get_rdates, method=True, string='Recurrent Dates', \ + store=True, type='text'), + 'attendee_ids': fields.many2many('crm.caldav.attendee', 'crm_attendee_rel', 'case_id', \ + 'attendee_id', 'Attendees'), + 'alarm_id': fields.many2one('crm.caldav.alarm', 'Alarm'), + 'caldav_url': fields.char('Caldav URL', size=34), + } + + __attribute__ = { + 'class': {'field': 'class', 'type': 'text'}, + 'completed': {'field': 'date_close', 'type': 'datetime'}, + 'description': {'field': 'description', 'type': 'text'}, + 'dtstart': {'field': 'date_start', 'type': 'datetime'}, + 'duration': {'field': 'planned_hours', 'type': 'int'}, + 'due': {'field': 'date_deadline', 'type': 'datetime'}, + 'location': {'field': 'location', 'type': 'text'}, # To add + 'organizer': {'field': 'partner_id', 'type': 'many2one', 'object': 'res.partner'}, + 'percent': {'field': 'progress_rate', 'type': 'int'}, + 'priority': {'field': 'priority', 'type': 'text'}, + 'seq': {'field': 'sequence', 'type': 'text'}, + 'status': {'field': 'state', 'type': 'selection', 'mapping': {'NEEDS-ACTION': 'draft', \ + 'COMPLETED': 'done', 'IN-PROCESS': 'open', \ + 'CANCELLED': 'cancelled'}}, + 'summary': {'field': 'name', 'type': 'text'}, + 'url': {'field': 'caldav_url', 'type': 'text'}, # To add + 'attendee': {'field': 'attendee_ids', 'type': 'many2many', 'object': 'crm.caldav.attendee'}, +# 'categories': {'field': 'type', 'type': 'text'}, # Needs review + 'comment': {'field': 'notes', 'type': 'text'}, + 'rrule': {'field': 'rrule', 'type': 'text'}, + } + + def import_cal(self, cr, uid, ids, data, context={}): + file_content = base64.decodestring(data['form']['file_path']) + todo_obj = self.pool.get('caldav.todo') + todo_obj.__attribute__.update(self.__attribute__) + + attendee_obj = self.pool.get('caldav.attendee') + crm_attendee = self.pool.get('crm.caldav.attendee') + attendee_obj.__attribute__.update(crm_attendee.__attribute__) + + alarm_obj = self.pool.get('caldav.alarm') + crm_alarm = self.pool.get('crm.caldav.alarm') + alarm_obj.__attribute__.update(crm_alarm.__attribute__) + + vals = todo_obj.import_ical(cr, uid, file_content) + if not vals.has_key('duration'): + # 'Compute duration' + vals['planned_hours'] = 16 + task_id = self.create(cr, uid, vals) + return + + def read(self, cr, uid, ids, fields=None, context={}, load='_classic_read'): + """ logic for recurrent task + example : 123-20091111170822""", ids, fields, context + if context and context.has_key('read'): + return super(project_task, self).read(cr, uid, ids, fields, context, load) + if not type(ids) == list : + return super(project_task, self).read(cr, uid, caldevIDs2readIDs(ids),\ + fields=fields, context=context, load=load) + else: + ids = map(lambda x:caldevIDs2readIDs(x), ids) + res = super(project_task, self).read(cr, uid, ids, fields=fields, context=context, load=load) + read_ids = ",".join([str(x) for x in ids]) + if not read_ids: + return [] + cr.execute('select id,rrule,rdates from project_task where id in (%s)' % read_ids) + rrules = filter(lambda x: not x['rrule']==None, cr.dictfetchall()) + rdates = [] + if not rrules: + return res + result = res + [] + for data in rrules: + if data['rrule'] and data['rdates']: + rdates = eval(data['rdates']) + for res_temp in res: + if res_temp['id'] == data['id']: + val = res_temp + if rdates: + result.remove(val) + + for rdate in rdates: + import re + idval = (re.compile('\d')).findall(rdate) + val['date'] = rdate + id = str(val['id']).split('-')[0] + val['id'] = id + '-' + ''.join(idval) + val1 = val.copy() + result += [val1] + return result + +project_task() + +class ir_attachment(osv.osv): + _name = 'ir.attachment' + _inherit = 'ir.attachment' + + def search_count(self, cr, user, args, context=None): + args1 = [] + for arg in args: + args1.append(map(lambda x:str(x).split('-')[0], arg)) + return super(ir_attachment, self).search_count(cr, user, args1, context) + + def search(self, cr, uid, args, offset=0, limit=None, order=None, + context=None, count=False): + new_args = [] + if len(args) > 1: + new_args = [args[0]] + if args[1][0] == 'res_id': + new_args.append((args[1][0], args[1][1], caldevIDs2readIDs(args[1][2]))) + if new_args: + args = new_args + return super(ir_attachment, self).search(cr, uid, args, offset=offset, + limit=limit, order=order, + context=context, count=False) +ir_attachment() + +class ir_values(osv.osv): + _inherit = 'ir.values' + + def set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, \ + meta=False, preserve_user=False, company=False): + new_model = [] + for data in models: + if type(data) in (list, tuple): + new_model.append((data[0], caldevIDs2readIDs(data[1]))) + else: + new_model.append(data) + return super(ir_values, self).set(cr, uid, key, key2, name, new_model, value, \ + replace, isobject, meta, preserve_user, company) + + def get(self, cr, uid, key, key2, models, meta=False, context={}, res_id_req=False, \ + without_user=True, key2_req=True): + new_model = [] + for data in models: + if type(data) in (list, tuple): + new_model.append((data[0], caldevIDs2readIDs(data[1]))) + else: + new_model.append(data) + return super(ir_values, self).get(cr, uid, key, key2, new_model, meta, context, \ + res_id_req, without_user, key2_req) + +ir_values() + +class ir_model(osv.osv): + + _inherit = 'ir.model' + + def read(self, cr, uid, ids, fields=None, context={}, + load='_classic_read'): + data = super(ir_model, self).read(cr, uid, ids, fields=fields, context=context, load=load) + if data: + for val in data: + val['id'] = caldevIDs2readIDs(val['id']) + return data + +ir_model() + +class virtual_report_spool(web_services.report_spool): + + def exp_report(self, db, uid, object, ids, datas=None, context=None): + if object == 'printscreen.list': + return super(virtual_report_spool, self).exp_report(db, uid, object, ids, datas, context) + new_ids = [] + for id in ids: + new_ids.append(caldevIDs2readIDs(id)) + datas['id'] = caldevIDs2readIDs(datas['id']) + super(virtual_report_spool, self).exp_report(db, uid, object, new_ids, datas, context) + return super(virtual_report_spool, self).exp_report(db, uid, object, new_ids, datas, context) + +virtual_report_spool() + +class virtual_wizard(web_services.wizard): + def exp_execute(self, db, uid, wiz_id, datas, action='init', context=None): + if wiz_id not in self.wiz_uid: + super(virtual_wizard,self).exp_create(db, uid, 'module.upgrade', datas) + new_ids = [] + if 'id' in datas: + datas['id'] = caldevIDs2readIDs(datas['id']) + for id in datas['ids']: + new_ids.append(caldevIDs2readIDs(id)) + datas['ids'] = new_ids + res=super(virtual_wizard, self).exp_execute(db, uid, wiz_id, datas, action, context) + return res + +virtual_wizard() + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/project_calendar/project_calendar_view.xml b/addons/project_calendar/project_calendar_view.xml new file mode 100644 index 00000000000..826d0788b04 --- /dev/null +++ b/addons/project_calendar/project_calendar_view.xml @@ -0,0 +1,126 @@ + + + + + crm.case.caldav.attendee + crm.caldav.attendee + form + +
+ + + + + + + + + + + + + +
+ + + crm.case.caldav.attendee + crm.caldav.attendee + tree + + + + + + + + + + + + + + + + + + + crm.case.caldav.alarm.form + crm.caldav.alarm + form + +
+ + + + + + + + + + + + + + + +
+ + + crm.case.caldav.alarm.tree + crm.caldav.alarm + tree + + + + + + + + + + + + + + + project.task.caldav.tree + project.task + tree + + + + + + + + + + project.task.caldav.form + project.task + + form + + + + + + + + + + + + + + + + + + + + + + +
+
diff --git a/addons/project_calendar/project_calendar_wizard.xml b/addons/project_calendar/project_calendar_wizard.xml new file mode 100644 index 00000000000..9d3ea03ddda --- /dev/null +++ b/addons/project_calendar/project_calendar_wizard.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/addons/project_calendar/security/ir.model.access.csv b/addons/project_calendar/security/ir.model.access.csv new file mode 100644 index 00000000000..f65c32af108 --- /dev/null +++ b/addons/project_calendar/security/ir.model.access.csv @@ -0,0 +1,3 @@ +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" +"access_crm_caldav_attendee","crm.caldav.attendee","model_crm_caldav_attendee",,1,1,1,1 +"access_crm_caldav_alarm","crm.caldav.alarm","model_crm_caldav_alarm",,1,1,1,1 diff --git a/addons/project_calendar/wizard/__init__.py b/addons/project_calendar/wizard/__init__.py new file mode 100644 index 00000000000..030daa5cb83 --- /dev/null +++ b/addons/project_calendar/wizard/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +import wizard_cal_import + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file diff --git a/addons/project_calendar/wizard/wizard_cal_import.py b/addons/project_calendar/wizard/wizard_cal_import.py new file mode 100644 index 00000000000..7ca19bcee6e --- /dev/null +++ b/addons/project_calendar/wizard/wizard_cal_import.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +import time +import tools +import wizard +import os +import mx.DateTime +import base64 +import pooler +import vobject + + +class project_cal_imp_wizard(wizard.interface): + form1 = ''' +
+ + + ''' + + form1_fields = { + 'file_path': { + 'string': 'Select ICS file', + 'type': 'binary', + 'required': True, + 'filters': '*.ics' + } + } + + def _process_imp_ics(self, cr, uid, data, context=None): + task_obj = pooler.get_pool(cr.dbname).get('project.task') + task_obj.import_cal(cr, uid, data['ids'], data, context) + return {} + + states = { + 'init': { + 'actions': [], + 'result': {'type': 'form', 'arch':form1, 'fields':form1_fields, \ + 'state': [('end', '_Cancel', 'gtk-cancel'), ('open', '_Import', 'gtk-ok')]} + }, + 'open': { + 'actions': [], + 'result': {'type': 'action', 'action':_process_imp_ics, 'state':'end'} + } + } + +project_cal_imp_wizard('caldav.project.import') + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file