From dd285d23af8060557f3bf5c5c1f7205e5bbdf8a5 Mon Sep 17 00:00:00 2001 From: jke-openerp Date: Thu, 19 Dec 2013 15:47:39 +0100 Subject: [TYPO] Rename base_calendar into calendar bzr revid: jke@openerp.com-20131219144739-9ic700ycef8uklbc --- addons/base_calendar/__init__.py | 26 - addons/base_calendar/__openerp__.py | 65 - addons/base_calendar/base_calendar.py | 1811 ------------------ addons/base_calendar/base_calendar_data.xml | 489 ----- addons/base_calendar/base_calendar_demo.xml | 131 -- addons/base_calendar/base_calendar_view.xml | 321 ---- addons/base_calendar/controllers/__init__.py | 3 - addons/base_calendar/controllers/main.py | 93 - addons/base_calendar/doc/calnedar_attendee.rst | 23 - addons/base_calendar/doc/changelog.rst | 29 - addons/base_calendar/doc/crm_meeting.rst | 38 - addons/base_calendar/i18n/af.po | 1690 ----------------- addons/base_calendar/i18n/ar.po | 1914 ------------------- addons/base_calendar/i18n/base_calendar.pot | 1575 ---------------- addons/base_calendar/i18n/bg.po | 1838 ------------------ addons/base_calendar/i18n/bn.po | 1641 ---------------- addons/base_calendar/i18n/bs.po | 1619 ---------------- addons/base_calendar/i18n/ca.po | 1904 ------------------- addons/base_calendar/i18n/cs.po | 1875 ------------------- addons/base_calendar/i18n/da.po | 1633 ---------------- addons/base_calendar/i18n/de.po | 1959 ------------------- addons/base_calendar/i18n/el.po | 1804 ------------------ addons/base_calendar/i18n/es.po | 1961 ------------------- addons/base_calendar/i18n/es_CR.po | 1943 ------------------- addons/base_calendar/i18n/es_EC.po | 1690 ----------------- addons/base_calendar/i18n/es_MX.po | 1602 ---------------- addons/base_calendar/i18n/es_PY.po | 1891 ------------------- addons/base_calendar/i18n/es_VE.po | 1702 ----------------- addons/base_calendar/i18n/et.po | 1624 ---------------- addons/base_calendar/i18n/fa.po | 1602 ---------------- addons/base_calendar/i18n/fi.po | 1901 ------------------- addons/base_calendar/i18n/fr.po | 1935 ------------------- addons/base_calendar/i18n/gl.po | 1903 ------------------- addons/base_calendar/i18n/hr.po | 1846 ------------------ addons/base_calendar/i18n/hu.po | 1800 ------------------ addons/base_calendar/i18n/id.po | 1633 ---------------- addons/base_calendar/i18n/it.po | 1894 ------------------- addons/base_calendar/i18n/ja.po | 1789 ------------------ addons/base_calendar/i18n/ln.po | 1611 ---------------- addons/base_calendar/i18n/lt.po | 1743 ----------------- addons/base_calendar/i18n/lv.po | 1742 ----------------- addons/base_calendar/i18n/mk.po | 1632 ---------------- addons/base_calendar/i18n/mn.po | 1882 ------------------- addons/base_calendar/i18n/nb.po | 1653 ---------------- addons/base_calendar/i18n/nl.po | 1956 ------------------- addons/base_calendar/i18n/pl.po | 1889 ------------------- addons/base_calendar/i18n/pt.po | 1936 ------------------- addons/base_calendar/i18n/pt_BR.po | 1962 ------------------- addons/base_calendar/i18n/ro.po | 1963 -------------------- addons/base_calendar/i18n/ru.po | 1933 ------------------- addons/base_calendar/i18n/sk.po | 1672 ----------------- addons/base_calendar/i18n/sl.po | 1773 ------------------ addons/base_calendar/i18n/sq.po | 1602 ---------------- addons/base_calendar/i18n/sr.po | 1845 ------------------ addons/base_calendar/i18n/sr@latin.po | 1845 ------------------ addons/base_calendar/i18n/sv.po | 1844 ------------------ addons/base_calendar/i18n/th.po | 1658 ----------------- addons/base_calendar/i18n/tr.po | 1953 ------------------- addons/base_calendar/i18n/vi.po | 1602 ---------------- addons/base_calendar/i18n/zh_CN.po | 1923 ------------------- addons/base_calendar/i18n/zh_TW.po | 1823 ------------------ .../base_calendar/security/calendar_security.xml | 9 - addons/base_calendar/security/ir.model.access.csv | 13 - addons/base_calendar/static/description/icon.png | Bin 7726 -> 0 bytes .../base_calendar/static/src/css/base_calender.css | 73 - .../base_calendar/static/src/js/base_calendar.js | 153 -- .../base_calendar/static/src/xml/base_calendar.xml | 43 - addons/base_calendar/test/base_calendar_test.yml | 66 - .../test/test_crm_recurrent_meeting_case2.yml | 95 - addons/calendar/__init__.py | 26 + addons/calendar/__openerp__.py | 65 + addons/calendar/calendar.py | 1811 ++++++++++++++++++ addons/calendar/calendar_data.xml | 489 +++++ addons/calendar/calendar_demo.xml | 131 ++ addons/calendar/calendar_view.xml | 321 ++++ addons/calendar/controllers/__init__.py | 3 + addons/calendar/controllers/main.py | 90 + addons/calendar/doc/calnedar_attendee.rst | 23 + addons/calendar/doc/changelog.rst | 29 + addons/calendar/doc/crm_meeting.rst | 38 + addons/calendar/i18n/af.po | 1690 +++++++++++++++++ addons/calendar/i18n/ar.po | 1914 +++++++++++++++++++ addons/calendar/i18n/base_calendar.pot | 1575 ++++++++++++++++ addons/calendar/i18n/bg.po | 1838 ++++++++++++++++++ addons/calendar/i18n/bn.po | 1641 ++++++++++++++++ addons/calendar/i18n/bs.po | 1619 ++++++++++++++++ addons/calendar/i18n/ca.po | 1904 +++++++++++++++++++ addons/calendar/i18n/cs.po | 1875 +++++++++++++++++++ addons/calendar/i18n/da.po | 1633 ++++++++++++++++ addons/calendar/i18n/de.po | 1959 +++++++++++++++++++ addons/calendar/i18n/el.po | 1804 ++++++++++++++++++ addons/calendar/i18n/es.po | 1961 +++++++++++++++++++ addons/calendar/i18n/es_CR.po | 1943 +++++++++++++++++++ addons/calendar/i18n/es_EC.po | 1690 +++++++++++++++++ addons/calendar/i18n/es_MX.po | 1602 ++++++++++++++++ addons/calendar/i18n/es_PY.po | 1891 +++++++++++++++++++ addons/calendar/i18n/es_VE.po | 1702 +++++++++++++++++ addons/calendar/i18n/et.po | 1624 ++++++++++++++++ addons/calendar/i18n/fa.po | 1602 ++++++++++++++++ addons/calendar/i18n/fi.po | 1901 +++++++++++++++++++ addons/calendar/i18n/fr.po | 1935 +++++++++++++++++++ addons/calendar/i18n/gl.po | 1903 +++++++++++++++++++ addons/calendar/i18n/hr.po | 1846 ++++++++++++++++++ addons/calendar/i18n/hu.po | 1800 ++++++++++++++++++ addons/calendar/i18n/id.po | 1633 ++++++++++++++++ addons/calendar/i18n/it.po | 1894 +++++++++++++++++++ addons/calendar/i18n/ja.po | 1789 ++++++++++++++++++ addons/calendar/i18n/ln.po | 1611 ++++++++++++++++ addons/calendar/i18n/lt.po | 1743 +++++++++++++++++ addons/calendar/i18n/lv.po | 1742 +++++++++++++++++ addons/calendar/i18n/mk.po | 1632 ++++++++++++++++ addons/calendar/i18n/mn.po | 1882 +++++++++++++++++++ addons/calendar/i18n/nb.po | 1653 ++++++++++++++++ addons/calendar/i18n/nl.po | 1956 +++++++++++++++++++ addons/calendar/i18n/pl.po | 1889 +++++++++++++++++++ addons/calendar/i18n/pt.po | 1936 +++++++++++++++++++ addons/calendar/i18n/pt_BR.po | 1962 +++++++++++++++++++ addons/calendar/i18n/ro.po | 1963 ++++++++++++++++++++ addons/calendar/i18n/ru.po | 1933 +++++++++++++++++++ addons/calendar/i18n/sk.po | 1672 +++++++++++++++++ addons/calendar/i18n/sl.po | 1773 ++++++++++++++++++ addons/calendar/i18n/sq.po | 1602 ++++++++++++++++ addons/calendar/i18n/sr.po | 1845 ++++++++++++++++++ addons/calendar/i18n/sr@latin.po | 1845 ++++++++++++++++++ addons/calendar/i18n/sv.po | 1844 ++++++++++++++++++ addons/calendar/i18n/th.po | 1658 +++++++++++++++++ addons/calendar/i18n/tr.po | 1953 +++++++++++++++++++ addons/calendar/i18n/vi.po | 1602 ++++++++++++++++ addons/calendar/i18n/zh_CN.po | 1923 +++++++++++++++++++ addons/calendar/i18n/zh_TW.po | 1823 ++++++++++++++++++ addons/calendar/security/calendar_security.xml | 9 + addons/calendar/security/ir.model.access.csv | 13 + addons/calendar/static/description/icon.png | Bin 0 -> 7726 bytes addons/calendar/static/src/css/calendar.css | 73 + addons/calendar/static/src/js/base_calendar.js | 153 ++ addons/calendar/static/src/xml/base_calendar.xml | 43 + addons/calendar/test/base_calendar_test.yml | 66 + .../test/test_crm_recurrent_meeting_case2.yml | 95 + addons/crm/__openerp__.py | 2 +- addons/crm/base_partner_merge.py | 2 +- addons/crm/crm_lead.py | 2 +- addons/crm/crm_meeting.py | 2 +- addons/crm/crm_phonecall.py | 2 +- addons/crm/res_partner_view.xml | 2 +- addons/crm/test/lead2opportunity2win.yml | 2 +- addons/crm/wizard/crm_phonecall_to_meeting.py | 2 +- addons/google_calendar/__openerp__.py | 2 +- addons/google_calendar/res_config_view.xml | 2 +- addons/hr_evaluation/__openerp__.py | 2 +- addons/hr_holidays/__openerp__.py | 2 +- addons/hr_recruitment/__openerp__.py | 2 +- addons/hr_recruitment/hr_recruitment.py | 2 +- addons/note/note_demo.xml | 2 +- 153 files changed, 93103 insertions(+), 93106 deletions(-) delete mode 100644 addons/base_calendar/__init__.py delete mode 100644 addons/base_calendar/__openerp__.py delete mode 100644 addons/base_calendar/base_calendar.py delete mode 100644 addons/base_calendar/base_calendar_data.xml delete mode 100644 addons/base_calendar/base_calendar_demo.xml delete mode 100644 addons/base_calendar/base_calendar_view.xml delete mode 100644 addons/base_calendar/controllers/__init__.py delete mode 100644 addons/base_calendar/controllers/main.py delete mode 100644 addons/base_calendar/doc/calnedar_attendee.rst delete mode 100644 addons/base_calendar/doc/changelog.rst delete mode 100644 addons/base_calendar/doc/crm_meeting.rst delete mode 100644 addons/base_calendar/i18n/af.po delete mode 100644 addons/base_calendar/i18n/ar.po delete mode 100644 addons/base_calendar/i18n/base_calendar.pot delete mode 100644 addons/base_calendar/i18n/bg.po delete mode 100644 addons/base_calendar/i18n/bn.po delete mode 100644 addons/base_calendar/i18n/bs.po delete mode 100644 addons/base_calendar/i18n/ca.po delete mode 100644 addons/base_calendar/i18n/cs.po delete mode 100644 addons/base_calendar/i18n/da.po delete mode 100644 addons/base_calendar/i18n/de.po delete mode 100644 addons/base_calendar/i18n/el.po delete mode 100644 addons/base_calendar/i18n/es.po delete mode 100644 addons/base_calendar/i18n/es_CR.po delete mode 100644 addons/base_calendar/i18n/es_EC.po delete mode 100644 addons/base_calendar/i18n/es_MX.po delete mode 100644 addons/base_calendar/i18n/es_PY.po delete mode 100644 addons/base_calendar/i18n/es_VE.po delete mode 100644 addons/base_calendar/i18n/et.po delete mode 100644 addons/base_calendar/i18n/fa.po delete mode 100644 addons/base_calendar/i18n/fi.po delete mode 100644 addons/base_calendar/i18n/fr.po delete mode 100644 addons/base_calendar/i18n/gl.po delete mode 100644 addons/base_calendar/i18n/hr.po delete mode 100644 addons/base_calendar/i18n/hu.po delete mode 100644 addons/base_calendar/i18n/id.po delete mode 100644 addons/base_calendar/i18n/it.po delete mode 100644 addons/base_calendar/i18n/ja.po delete mode 100644 addons/base_calendar/i18n/ln.po delete mode 100644 addons/base_calendar/i18n/lt.po delete mode 100644 addons/base_calendar/i18n/lv.po delete mode 100644 addons/base_calendar/i18n/mk.po delete mode 100644 addons/base_calendar/i18n/mn.po delete mode 100644 addons/base_calendar/i18n/nb.po delete mode 100644 addons/base_calendar/i18n/nl.po delete mode 100644 addons/base_calendar/i18n/pl.po delete mode 100644 addons/base_calendar/i18n/pt.po delete mode 100644 addons/base_calendar/i18n/pt_BR.po delete mode 100644 addons/base_calendar/i18n/ro.po delete mode 100644 addons/base_calendar/i18n/ru.po delete mode 100644 addons/base_calendar/i18n/sk.po delete mode 100644 addons/base_calendar/i18n/sl.po delete mode 100644 addons/base_calendar/i18n/sq.po delete mode 100644 addons/base_calendar/i18n/sr.po delete mode 100644 addons/base_calendar/i18n/sr@latin.po delete mode 100644 addons/base_calendar/i18n/sv.po delete mode 100644 addons/base_calendar/i18n/th.po delete mode 100644 addons/base_calendar/i18n/tr.po delete mode 100644 addons/base_calendar/i18n/vi.po delete mode 100644 addons/base_calendar/i18n/zh_CN.po delete mode 100644 addons/base_calendar/i18n/zh_TW.po delete mode 100644 addons/base_calendar/security/calendar_security.xml delete mode 100644 addons/base_calendar/security/ir.model.access.csv delete mode 100644 addons/base_calendar/static/description/icon.png delete mode 100644 addons/base_calendar/static/src/css/base_calender.css delete mode 100644 addons/base_calendar/static/src/js/base_calendar.js delete mode 100644 addons/base_calendar/static/src/xml/base_calendar.xml delete mode 100644 addons/base_calendar/test/base_calendar_test.yml delete mode 100644 addons/base_calendar/test/test_crm_recurrent_meeting_case2.yml create mode 100644 addons/calendar/__init__.py create mode 100644 addons/calendar/__openerp__.py create mode 100644 addons/calendar/calendar.py create mode 100644 addons/calendar/calendar_data.xml create mode 100644 addons/calendar/calendar_demo.xml create mode 100644 addons/calendar/calendar_view.xml create mode 100644 addons/calendar/controllers/__init__.py create mode 100644 addons/calendar/controllers/main.py create mode 100644 addons/calendar/doc/calnedar_attendee.rst create mode 100644 addons/calendar/doc/changelog.rst create mode 100644 addons/calendar/doc/crm_meeting.rst create mode 100644 addons/calendar/i18n/af.po create mode 100644 addons/calendar/i18n/ar.po create mode 100644 addons/calendar/i18n/base_calendar.pot create mode 100644 addons/calendar/i18n/bg.po create mode 100644 addons/calendar/i18n/bn.po create mode 100644 addons/calendar/i18n/bs.po create mode 100644 addons/calendar/i18n/ca.po create mode 100644 addons/calendar/i18n/cs.po create mode 100644 addons/calendar/i18n/da.po create mode 100644 addons/calendar/i18n/de.po create mode 100644 addons/calendar/i18n/el.po create mode 100644 addons/calendar/i18n/es.po create mode 100644 addons/calendar/i18n/es_CR.po create mode 100644 addons/calendar/i18n/es_EC.po create mode 100644 addons/calendar/i18n/es_MX.po create mode 100644 addons/calendar/i18n/es_PY.po create mode 100644 addons/calendar/i18n/es_VE.po create mode 100644 addons/calendar/i18n/et.po create mode 100644 addons/calendar/i18n/fa.po create mode 100644 addons/calendar/i18n/fi.po create mode 100644 addons/calendar/i18n/fr.po create mode 100644 addons/calendar/i18n/gl.po create mode 100644 addons/calendar/i18n/hr.po create mode 100644 addons/calendar/i18n/hu.po create mode 100644 addons/calendar/i18n/id.po create mode 100644 addons/calendar/i18n/it.po create mode 100644 addons/calendar/i18n/ja.po create mode 100644 addons/calendar/i18n/ln.po create mode 100644 addons/calendar/i18n/lt.po create mode 100644 addons/calendar/i18n/lv.po create mode 100644 addons/calendar/i18n/mk.po create mode 100644 addons/calendar/i18n/mn.po create mode 100644 addons/calendar/i18n/nb.po create mode 100644 addons/calendar/i18n/nl.po create mode 100644 addons/calendar/i18n/pl.po create mode 100644 addons/calendar/i18n/pt.po create mode 100644 addons/calendar/i18n/pt_BR.po create mode 100644 addons/calendar/i18n/ro.po create mode 100644 addons/calendar/i18n/ru.po create mode 100644 addons/calendar/i18n/sk.po create mode 100644 addons/calendar/i18n/sl.po create mode 100644 addons/calendar/i18n/sq.po create mode 100644 addons/calendar/i18n/sr.po create mode 100644 addons/calendar/i18n/sr@latin.po create mode 100644 addons/calendar/i18n/sv.po create mode 100644 addons/calendar/i18n/th.po create mode 100644 addons/calendar/i18n/tr.po create mode 100644 addons/calendar/i18n/vi.po create mode 100644 addons/calendar/i18n/zh_CN.po create mode 100644 addons/calendar/i18n/zh_TW.po create mode 100644 addons/calendar/security/calendar_security.xml create mode 100644 addons/calendar/security/ir.model.access.csv create mode 100644 addons/calendar/static/description/icon.png create mode 100644 addons/calendar/static/src/css/calendar.css create mode 100644 addons/calendar/static/src/js/base_calendar.js create mode 100644 addons/calendar/static/src/xml/base_calendar.xml create mode 100644 addons/calendar/test/base_calendar_test.yml create mode 100644 addons/calendar/test/test_crm_recurrent_meeting_case2.yml (limited to 'addons') diff --git a/addons/base_calendar/__init__.py b/addons/base_calendar/__init__.py deleted file mode 100644 index b76507766c5..00000000000 --- a/addons/base_calendar/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 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 base_calendar -import controllers -#import res_config - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/base_calendar/__openerp__.py b/addons/base_calendar/__openerp__.py deleted file mode 100644 index 79516e8d05f..00000000000 --- a/addons/base_calendar/__openerp__.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 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': 'Calendar', - 'version': '1.0', - 'depends': ['base', 'mail', 'base_action_rule','web_calendar'], - 'summary': 'Personal & Shared Calendar', - 'description': """ -This is a full-featured calendar system. -======================================== - -It supports: ------------- - - Calendar of events - - Recurring events - -If you need to manage your meetings, you should install the CRM module. - """, - 'author': 'OpenERP SA', - 'category': 'Hidden/Dependency', - 'website': 'http://www.openerp.com', - 'demo': ['base_calendar_demo.xml'], - 'data': [ - 'security/calendar_security.xml', - 'security/ir.model.access.csv', - 'base_calendar_view.xml', - 'base_calendar_data.xml', - ], - 'js': [ - 'static/src/js/*.js' - ], - 'qweb': ['static/src/xml/*.xml'], - 'css': [ - 'static/src/css/base_calender.css' - ], - 'test' : [ - 'test/base_calendar_test.yml', - 'test/test_crm_recurrent_meeting_case2.yml' - ], - 'installable': True, - 'application': True, - 'auto_install': False, - 'images': ['images/base_calendar1.jpeg','images/base_calendar2.jpeg','images/base_calendar3.jpeg','images/base_calendar4.jpeg'], -} - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/base_calendar/base_calendar.py b/addons/base_calendar/base_calendar.py deleted file mode 100644 index cdb24f3a3ba..00000000000 --- a/addons/base_calendar/base_calendar.py +++ /dev/null @@ -1,1811 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 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 hashlib -import pytz -import re -import time -import openerp -import openerp.service.report - -from datetime import datetime, timedelta, date -from dateutil import parser -from dateutil import rrule -from dateutil.relativedelta import relativedelta - -from openerp import tools, SUPERUSER_ID -from openerp.osv import fields, osv -from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT -from openerp.tools.translate import _ - - - - -def base_calendar_id2real_id(base_calendar_id=None, with_date=False): - """ - Convert a "virtual/recurring event id" (type string) into a real event id (type int). - E.g. virtual/recurring event id is 4-20091201100000, so it will return 4. - @param base_calendar_id: id of calendar - @param with_date: if a value is passed to this param it will return dates based on value of withdate + base_calendar_id - @return: real event id - """ - if base_calendar_id and isinstance(base_calendar_id, (str, unicode)): - res = base_calendar_id.split('-') - if len(res) >= 2: - real_id = res[0] - if with_date: - real_date = time.strftime("%Y-%m-%d %H:%M:%S", time.strptime(res[1], "%Y%m%d%H%M%S")) - start = datetime.strptime(real_date, "%Y-%m-%d %H:%M:%S") - end = start + timedelta(hours=with_date) - return (int(real_id), real_date, end.strftime("%Y-%m-%d %H:%M:%S")) - return int(real_id) - - return base_calendar_id and int(base_calendar_id) or base_calendar_id - -def get_real_ids(ids): - if isinstance(ids, (str, int, long)): - return base_calendar_id2real_id(ids) - - if isinstance(ids, (list, tuple)): - res = [] - for id in ids: - res.append(base_calendar_id2real_id(id)) - return res - -class calendar_attendee(osv.osv): - """ - Calendar Attendee Information - """ - _name = 'calendar.attendee' - _description = 'Attendee information' - - def _compute_data(self, cr, uid, ids, name, arg, context=None): - """ - Compute data on function fields for attendee values. - @param ids: list of calendar attendee's IDs - @param name: name of field - @return: dictionary of form {id: {'field Name': value'}} - """ - name = name[0] - result = {} - for attdata in self.browse(cr, uid, ids, context=context): - id = attdata.id - result[id] = {} - - if name == 'cn': - if attdata.partner_id: - result[id][name] = attdata.partner_id.name or False - else: - result[id][name] = attdata.email or '' - - if name == 'event_date': - result[id][name] = attdata.event_id.date - - if name == 'event_end_date': - result[id][name] = attdata.event_id.date_deadline - - return result - - _columns = { - 'state': fields.selection([('needsAction', 'Needs Action'),('tentative', 'Uncertain'),('declined', 'Declined'),('accepted', 'Accepted')], 'Status', readonly=True, help="Status of the attendee's participation"), - 'cn': fields.function(_compute_data, string='Common name', type="char", size=124, multi='cn', store=True), - 'dir': fields.char('URI Reference', size=124, help="Reference to the URI that points to the directory information corresponding to the attendee."), - 'partner_id': fields.many2one('res.partner', 'Contact',readonly="True"), - 'email': fields.char('Email', size=124, help="Email of Invited Person"), - 'event_date': fields.function(_compute_data, string='Event Date', type="datetime", multi='event_date'), - 'event_end_date': fields.function(_compute_data, string='Event End Date', type="datetime", multi='event_end_date'), - 'availability': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Free/Busy', readonly="True"), - 'access_token':fields.char('Invitation Token', size=256), - 'event_id': fields.many2one('crm.meeting','Meeting linked'), - } - _defaults = { - 'state': 'needsAction', - } - - def copy(self, cr, uid, id, default=None, context=None): - raise osv.except_osv(_('Warning!'), _('You cannot duplicate a calendar attendee.')) - - def onchange_partner_id(self, cr, uid, ids, partner_id,context=None): - """ - Make entry on email and availability on change of partner_id field. - @param partner_id: changed value of partner id - """ - if not partner_id: - return {'value': {'email': ''}} - partner = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context) - return {'value': {'email': partner.email}} - - def get_ics_file(self, cr, uid, event_obj, context=None): - """ - Returns iCalendar file for the event invitation. - @param event_obj: event object (browse record) - @return: .ics file content - """ - res = None - def ics_datetime(idate, short=False): - if idate: - #returns the datetime as UTC, because it is stored as it in the database - return datetime.strptime(idate.split('.')[0], '%Y-%m-%d %H:%M:%S').replace(tzinfo=pytz.timezone('UTC')) - #return datetime.strptime(idate, DEFAULT_SERVER_DATETIME_FORMAT).replace(tzinfo=pytz.timezone('UTC')) - - return False - - try: - # FIXME: why isn't this in CalDAV? - import vobject - except ImportError: - return res - - cal = vobject.iCalendar() - event = cal.add('vevent') - if not event_obj.date_deadline or not event_obj.date: - raise osv.except_osv(_('Warning!'),_("First you have to specify the date of the invitation.")) - event.add('created').value = ics_datetime(time.strftime('%Y-%m-%d %H:%M:%S')) - event.add('dtstart').value = ics_datetime(event_obj.date) - event.add('dtend').value = ics_datetime(event_obj.date_deadline) - event.add('summary').value = event_obj.name - if event_obj.description: - event.add('description').value = event_obj.description - if event_obj.location: - event.add('location').value = event_obj.location - if event_obj.rrule: - event.add('rrule').value = event_obj.rrule - - if event_obj.alarm_ids: - for alarm in event_obj.alarm_ids: - # computes alarm data - valarm = event.add('valarm') - # Compute trigger data - interval = alarm.interval - occurs = 'before' - duration = alarm.duration - - trigger = valarm.add('TRIGGER') - trigger.params['related'] = ["START"] - if interval == 'days': - delta = timedelta(days=duration) - elif interval == 'hours': - delta = timedelta(hours=duration) - elif interval == 'minutes': - delta = timedelta(minutes=duration) - - trigger.value = delta - # Compute other details - valarm.add('DESCRIPTION').value = alarm.name or 'OpenERP' - - for attendee in event_obj.attendee_ids: - attendee_add = event.add('attendee') - attendee_add.value = 'MAILTO:' + (attendee.email or '') - res = cal.serialize() - return res - - def _send_mail_to_attendees(self, cr, uid, ids, email_from=tools.config.get('email_from', False), template_xmlid='crm_email_template_meeting_invitation', context=None): - """ - Send mail for event invitation to event attendees. - @param email_from: email address for user sending the mail - """ - res = False - - mail_ids = [] - data_pool = self.pool.get('ir.model.data') - mailmess_pool = self.pool.get('mail.message') - mail_pool = self.pool.get('mail.mail') - template_pool = self.pool.get('email.template') - local_context = context.copy() - color = { - 'needsAction' : 'grey', - 'accepted' :'green', - 'tentative' :'#FFFF00', - 'declined':'red' - } - - if not isinstance(ids, (tuple, list)): - ids = [ids] - - for attendee in self.browse(cr, uid, ids, context=context): - dummy,template_id = data_pool.get_object_reference(cr, uid, 'base_calendar',template_xmlid) - dummy,act_id = data_pool.get_object_reference(cr, uid, 'base_calendar', "view_crm_meeting_calendar") - - if attendee.email and email_from: - ics_file = self.get_ics_file(cr, uid, attendee.event_id, context=context) - local_context['color'] = color - local_context['action_id'] = self.pool.get('ir.actions.act_window').search(cr, uid, [('view_id','=',act_id)], context=context)[0] - local_context['dbname'] = cr.dbname - local_context['base_url'] = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url', default='http://localhost:8069', context=context) - mail_id = template_pool.send_mail(cr, uid, template_id, attendee.id, context=local_context) - - - vals = {} - if ics_file: - vals['attachment_ids'] = [(0,0,{'name': 'invitation.ics', - 'datas_fname': 'invitation.ics', - 'datas': str(ics_file).encode('base64')})] - vals['model'] = None #We don't want to have the mail in the tchatter while in queue! - the_mailmess = mail_pool.browse(cr,uid,mail_id,context=context).mail_message_id - mailmess_pool.write(cr,uid,[the_mailmess.id],vals,context=context) - mail_ids.append(mail_id) - - -# -# if not attendee.partner_id.opt_out: -# if 'partner_to' in vals: -# del vals['partner_to'] #hack between mail.mail and template.mail -> tde - - if mail_ids: - try: - res = mail_pool.send(cr, uid, mail_ids, context=context) - except Exception as e: - print e - - return res - - def ORI_send_mail_to_attendees(self, cr, uid, ids, email_from=tools.config.get('email_from', False), template_xmlid='crm_email_template_meeting_invitation', context=None): - """ - Send mail for event invitation to event attendees. - @param email_from: email address for user sending the mail - """ - res = False - - mail_id = [] - data_pool = self.pool.get('ir.model.data') - mail_pool = self.pool.get('mail.mail') - template_pool = self.pool.get('email.template') - local_context = context.copy() - color = { - 'needsAction' : 'grey', - 'accepted' :'green', - 'tentative' :'#FFFF00', - 'declined':'red' - } - - if not isinstance(ids, (tuple, list)): - ids = [ids] - - for attendee in self.browse(cr, uid, ids, context=context): - dummy,template_id = data_pool.get_object_reference(cr, uid, 'base_calendar', template_xmlid) - dummy,act_id = data_pool.get_object_reference(cr, uid, 'base_calendar', "view_crm_meeting_calendar") - body = template_pool.browse(cr, uid, template_id, context=context).body_html - if attendee.email and email_from: - ics_file = self.get_ics_file(cr, uid, attendee.event_id, context=context) - local_context['att_obj'] = attendee - local_context['color'] = color - local_context['action_id'] = self.pool.get('ir.actions.act_window').search(cr, uid, [('view_id','=',act_id)], context=context)[0] - local_context['dbname'] = cr.dbname - local_context['base_url'] = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url', default='http://localhost:8069', context=context) - vals = template_pool.generate_email(cr, uid, template_id, attendee.event_id.id, context=local_context) - if ics_file: - vals['attachment_ids'] = [(0,0,{'name': 'invitation.ics', - 'datas_fname': 'invitation.ics', - 'datas': str(ics_file).encode('base64')})] - - vals['model'] = None #We don't want to have the mail in the tchatter while in queue! - vals['auto_delete'] = True #We don't need mail after it has been sended ! - - if (vals['email_to']== attendee.partner_id.email): - vals['email_to'] = '' - vals['recipient_ids'] = [(4,attendee.partner_id.id),] - - if not attendee.partner_id.opt_out: - if 'partner_to' in vals: - del vals['partner_to'] #hack between mail.mail and template.mail -> tde - mail_id.append(mail_pool.create(cr, SUPERUSER_ID, vals, context=context)) - - - if mail_id: - try: - res = mail_pool.send(cr, uid, mail_id, context=context) - except Exception as e: - print e - - return res - - def onchange_user_id(self, cr, uid, ids, user_id, *args, **argv): - """ - Make entry on email and availbility on change of user_id field. - @param ids: list of attendee's IDs - @param user_id: changed value of User id - @return: dictionary of values which put value in email and availability fields - """ - if not user_id: - return {'value': {'email': ''}} - - usr_obj = self.pool.get('res.users').browse(cr, uid, user_id, *args) - return {'value': {'email': user.email, 'availability':user.availability}} - - def do_tentative(self, cr, uid, ids, context=None, *args): - """ - Makes event invitation as Tentative. - @param ids: list of attendee's IDs - """ - return self.write(cr, uid, ids, {'state': 'tentative'}, context) - - def do_accept(self, cr, uid, ids, context=None, *args): - """ - Marks event invitation as Accepted. - @param ids: list of attendee's IDs - """ - if context is None: - context = {} - meeting_obj = self.pool.get('crm.meeting') - res = self.write(cr, uid, ids, {'state': 'accepted'}, context) - for attendee in self.browse(cr, uid, ids, context=context): - meeting_obj.message_post(cr, uid, attendee.event_id.id, body=_(("%s has accepted invitation") % (attendee.cn)),subtype="base_calendar.subtype_invitation", context=context) - - return res - - def do_decline(self, cr, uid, ids, context=None, *args): - """ - Marks event invitation as Declined. - @param ids: list of calendar attendee's IDs - """ - if context is None: - context = {} - meeting_obj = self.pool.get('crm.meeting') - res = self.write(cr, uid, ids, {'state': 'declined'}, context) - for attendee in self.browse(cr, uid, ids, context=context): - meeting_obj.message_post(cr, uid, attendee.event_id.id, body=_(("%s has declined invitation") % (attendee.cn)),subtype="base_calendar.subtype_invitation", context=context) - return res - - def create(self, cr, uid, vals, context=None): - if context is None: - context = {} - if not vals.get("email") and vals.get("cn"): - cnval = vals.get("cn").split(':') - email = filter(lambda x:x.__contains__('@'), cnval) - vals['email'] = email and email[0] or '' - vals['cn'] = vals.get("cn") - res = super(calendar_attendee, self).create(cr, uid, vals, context=context) - return res - - -class res_partner(osv.osv): - _inherit = 'res.partner' - - _columns = { - 'cal_last_notif': fields.datetime('Last Notification from base Calendar'), - } - - def get_attendee_detail(self, cr, uid, ids, meeting_id, context=None): - datas = [] - meeting = False - if meeting_id: - meeting = self.pool.get('crm.meeting').browse(cr, uid, get_real_ids(meeting_id),context) - for partner in self.browse(cr, uid, ids, context=context): - data = self.name_get(cr, uid, [partner.id], context)[0] - if meeting: - for attendee in meeting.attendee_ids: - if attendee.partner_id.id == partner.id: - data = (data[0], data[1], attendee.state) - datas.append(data) - return datas - - def update_cal_last_event(self,cr,uid,context=None): - partner = self.pool.get('res.users').browse(cr,uid,uid,context=context).partner_id; - self.write(cr,uid,partner.id,{'cal_last_notif' : datetime.now() } ,context=context) - return "OK" - - -class calendar_alarm_manager(osv.osv): - _name = 'calendar.alarm_manager' - - def get_next_potential_limit_alarm(self,cr,uid,seconds, notif=True, mail=True, partner_id=None, context=None): - res = {} - base_request = """ - SELECT - crm.id, - crm.date - interval '1' minute * calcul_delta.max_delta AS first_alarm, - CASE - WHEN crm.recurrency THEN crm.end_date - interval '1' minute * calcul_delta.min_delta - ELSE crm.date_deadline - interval '1' minute * calcul_delta.min_delta - END as last_alarm, - crm.date as first_event_date, - CASE - WHEN crm.recurrency THEN crm.end_date - ELSE crm.date_deadline - END as last_event_date, - calcul_delta.min_delta, - calcul_delta.max_delta, - crm.rrule - FROM - crm_meeting AS crm - RIGHT JOIN - ( - SELECT - rel.crm_meeting_id, max(alarm.duration_minutes) AS max_delta,min(alarm.duration_minutes) AS min_delta - FROM - calendar_alarm_crm_meeting_rel AS rel - LEFT JOIN calendar_alarm AS alarm ON alarm.id = rel.calendar_alarm_id - WHERE alarm.type in %s - GROUP BY rel.crm_meeting_id - ) AS calcul_delta ON calcul_delta.crm_meeting_id = crm.id - """ - - filter_user = """ - LEFT JOIN crm_meeting_res_partner_rel AS part_rel ON part_rel.crm_meeting_id = crm.id - AND part_rel.res_partner_id = %s - """ - - - #Add filter on type - type_to_read = () #('dummy',) - if notif: - type_to_read += ('notification',) - if mail: - type_to_read += ('email',) - - tuple_params = (type_to_read,) - - - #ADD FILTER ON PARTNER_ID - if partner_id: - base_request += filter_user - tuple_params += (partner_id, ) - - #Add filter on hours - tuple_params += (seconds,seconds,) - - cr.execute(""" - SELECT - * - FROM ( - """ - + base_request - + """ - ) AS ALL_EVENTS - WHERE - ALL_EVENTS.first_alarm < (now() at time zone 'utc' + interval '%s' second ) - AND ALL_EVENTS.last_alarm > (now() at time zone 'utc' - interval '%s' second ) - """,tuple_params) - - for event_id, first_alarm,last_alarm,first_meeting,last_meeting,min_duration,max_duration,rrule in cr.fetchall(): - res[event_id] = {} - res[event_id]['event_id'] = event_id - res[event_id]['first_alarm'] = first_alarm - res[event_id]['last_alarm'] = last_alarm - res[event_id]['first_meeting'] = first_meeting - res[event_id]['last_meeting'] = last_meeting - res[event_id]['min_duration'] = min_duration - res[event_id]['max_duration'] = max_duration - res[event_id]['rrule'] = rrule - - return res - - def do_check_alarm_for_one_date(self,cr,uid,one_date,event, event_maxdelta,in_the_next_X_seconds,after=False,notif=True, mail=True,context=None): - res = [] - alarm_type = [] - - if notif: - alarm_type.append('notification') - if mail: - alarm_type.append('email') - - if one_date - timedelta(minutes=event_maxdelta) < datetime.now() + timedelta(seconds=in_the_next_X_seconds): #if an alarm is possible for this date - print "ALARMIDS = ", event.alarm_ids - for alarm in event.alarm_ids: - print "type = ",alarm.type - print "after = ",after - print "cond 0 =", alarm.type in alarm_type - print "cond 1 = ", one_date - timedelta(minutes=alarm.duration_minutes), " < ", datetime.now() + timedelta(seconds=in_the_next_X_seconds) - #print "cond 2", one_date - timedelta(minutes=alarm.duration_minutes), " > ",datetime.strptime(after.split('.')[0], "%Y-%m-%d %H:%M:%S") - - - if alarm.type in alarm_type and \ - one_date - timedelta(minutes=alarm.duration_minutes) < datetime.now() + timedelta(seconds=in_the_next_X_seconds) and \ - (not after or one_date - timedelta(minutes=alarm.duration_minutes) > datetime.strptime(after.split('.')[0], "%Y-%m-%d %H:%M:%S")): - alert = { - 'alarm_id' : alarm.id, - 'event_id' : event.id, - 'notify_at' : one_date - timedelta(minutes=alarm.duration_minutes), - } - print "ALERT ADDED : ", alert - res.append(alert) - else: - print "Not in condition..." - - return res - - def do_run_scheduler_mail(self,cr,uid,context=None): - cron = self.pool.get('ir.cron').search(cr,uid,[('model','ilike',self._name)],context=context) - if cron and len(cron) == 1: - cron = self.pool.get('ir.cron').browse(cr,uid,cron[0],context=context) - else: - raise ("Cron for " + self._name + " not identified :( !") - - if cron.interval_type=="weeks": - cron_interval = cron.interval_number * 7 * 24 * 60 * 60 - elif cron.interval_type=="days": - cron_interval = cron.interval_number * 24 * 60 * 60 - elif cron.interval_type=="hours": - cron_interval = cron.interval_number * 60 * 60 - elif cron.interval_type=="minutes": - cron_interval = cron.interval_number * 60 - elif cron.interval_type=="seconds": - cron_interval = cron.interval_number - - if not cron_interval: - raise ("Cron delay for " + self._name + " not calculated :( !") - - - all_events = self.get_next_potential_limit_alarm(cr,uid,cron_interval,notif=False,context=context) - for event in all_events: #.values() - max_delta = all_events[event]['max_duration']; - curEvent = self.pool.get('crm.meeting').browse(cr,uid,event,context=context) - if curEvent.recurrency: - bFound = False - LastFound = False - for one_date in self.pool.get('crm.meeting').get_recurrent_date_by_event(cr,uid,curEvent, context=context) : - in_date_format = datetime.strptime(one_date, '%Y-%m-%d %H:%M:%S'); - LastFound = self.do_check_alarm_for_one_date(cr,uid,in_date_format,curEvent,max_delta,cron_interval,notif=False,context=context) - if LastFound: - for alert in LastFound: - self.do_mail_reminder(cr,uid,alert,context=context) - - if not bFound: #if it's the first alarm for this recurrent event - bFound = True - if bFound and not LastFound: #if the precendent event had alarm but not this one, we can stop the search fot this event - break - else: - in_date_format = datetime.strptime(curEvent.date, '%Y-%m-%d %H:%M:%S'); - LastFound = self.do_check_alarm_for_one_date(cr,uid,in_date_format,curEvent,max_delta,cron_interval,notif=False,context=context) - if LastFound: - for alert in LastFound: - self.do_mail_reminder(cr,uid,alert,context=context) - - #Purge all done - - def get_next_event(self,cr,uid,context=None): - ajax_check_every_seconds = 300 - - partner = self.pool.get('res.users').browse(cr,uid,uid,context=context).partner_id; - - all_notif = [] - - all_events = self.get_next_potential_limit_alarm(cr,uid,ajax_check_every_seconds,partner_id=partner.id,mail=False,context=context) - print all_events - - for event in all_events: #.values() - max_delta = all_events[event]['max_duration']; - curEvent = self.pool.get('crm.meeting').browse(cr,uid,event,context=context) - if curEvent.recurrency: - bFound = False - LastFound = False - for one_date in self.pool.get("crm.meeting").get_recurrent_date_by_event(cr,uid,curEvent, context=context) : - in_date_format = datetime.strptime(one_date, '%Y-%m-%d %H:%M:%S'); - LastFound = self.do_check_alarm_for_one_date(cr,uid,in_date_format,curEvent,max_delta,ajax_check_every_seconds,after=partner.cal_last_notif,mail=False,context=context) - if LastFound: - for alert in LastFound: - all_notif.append(self.do_notif_reminder(cr,uid,alert,context=context)) - if not bFound: #if it's the first alarm for this recurrent event - bFound = True - if bFound and not LastFound: #if the precendent event had alarm but not this one, we can stop the search fot this event - break - else: - in_date_format = datetime.strptime(curEvent.date, '%Y-%m-%d %H:%M:%S'); - LastFound = self.do_check_alarm_for_one_date(cr,uid,in_date_format,curEvent,max_delta,ajax_check_every_seconds,partner.cal_last_notif,mail=False,context=context) - if LastFound: - for alert in LastFound: - all_notif.append(self.do_notif_reminder(cr,uid,alert,context=context)) - - return all_notif - - def do_mail_reminder(self,cr,uid,alert,context=None): - if context is None: - context = {} - res = False - - event = self.pool.get("crm.meeting").browse(cr,uid,alert['event_id'],context=context) - alarm = self.pool.get("calendar.alarm").browse(cr,uid,alert['alarm_id'],context=context) - - if alarm.type == 'email': - mail_ids = [] - data_pool = self.pool.get('ir.model.data') - mail_pool = self.pool.get('mail.mail') - template_pool = self.pool.get('email.template') - local_context = context and context.copy() or {} - color = { - 'needsAction' : 'grey', - 'accepted' :'green', - 'tentative' :'#FFFF00', - 'declined':'red' - } - - for attendee in event.attendee_ids: - dummy,template_id = data_pool.get_object_reference(cr, uid, 'base_calendar', 'crm_email_template_meeting_reminder') - dummy,act_id = data_pool.get_object_reference(cr, uid, 'base_calendar', "view_crm_meeting_calendar") - body = template_pool.browse(cr, uid, template_id, context=context).body_html - - #mail_from = tools.config.get('email_from',event.user_id.email) - if attendee.email: - local_context['att_obj'] = attendee - local_context['color'] = color - local_context['action_id'] = self.pool.get('ir.actions.act_window').search(cr, uid, [('view_id','=',act_id)], context=context)[0] - local_context['dbname'] = cr.dbname - local_context['base_url'] = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url', default='http://localhost:8069', context=context) - vals = template_pool.generate_email(cr, uid, template_id, attendee.event_id.id, context=local_context) - - vals['model'] = None #We don't want to have the mail in the tchatter while in queue! - vals['auto_delete'] = True #We don't need mail after it has been sended ! - - if (vals['email_to']== attendee.partner_id.email): - vals['email_to'] = '' - vals['recipient_ids'] = [(4,attendee.partner_id.id),] - - if not attendee.partner_id.opt_out: - if 'partner_to' in vals: - del vals['partner_to'] #hack between mail.mail and template.mail -> tde - mail_ids.append(mail_pool.create(cr, uid, vals, context=local_context)) - if mail_ids: - try: - res = mail_pool.send(cr, uid, mail_ids, context=local_context) - except Exception as e: - print e - - return res - - def do_notif_reminder(self,cr,uid,alert,context=None): - alarm = self.pool.get("calendar.alarm").browse(cr,uid,alert['alarm_id'],context=context) - event = self.pool.get("crm.meeting").browse(cr,uid,alert['event_id'],context=context) - - if alarm.type == 'notification': - mail_id = [] - mail_pool = self.pool.get('mail.mail') - data_pool = self.pool.get('ir.model.data') - template_pool = self.pool.get('email.template') - local_context = context.copy() - message = event.display_time - - delta = alert['notify_at'] - datetime.now() - delta = delta.seconds + delta.days * 3600 * 24 - - return { - 'event_id' : event.id, - 'title' : event.name, - 'message' : message, - 'timer' : delta, #Now - event_date - alaram.duration_minute - 'notify_at' : alert['notify_at'].strftime("%Y-%m-%d %H:%M:%S"), #Now - event_date - alaram.duration_minute - } - - - -class calendar_alarm(osv.osv): - _name = 'calendar.alarm' - _description = 'Event alarm' - - def _get_duration(self, cr, uid, ids, field_name, arg, context=None): - res = {} - for alarm in self.browse(cr, uid, ids, context=context): - if alarm.interval == "minutes": - res[alarm.id] = alarm.duration - elif alarm.interval == "hours": - res[alarm.id] = alarm.duration * 60 - elif alarm.interval == "days": - res[alarm.id] = alarm.duration * 60 * 24 - else: - res[alarm.id] = 0 - return res - - _columns = { - 'name':fields.char('Name', size=256, required=True), # fields function - 'type': fields.selection([('notification', 'Notification'), ('email', 'Email')], 'Type', required=True), - 'duration': fields.integer('Amount', required=True), - 'interval': fields.selection([('minutes', 'Minutes'), ('hours', 'Hours'), ('days', 'Days')], 'Unit', required=True), - - 'duration_minutes': fields.function(_get_duration, type='integer', string='duration_minutes',store=True), - } - - _defaults = { - 'type': 'notification', - 'duration': 1, - 'interval': 'hours', - } - - -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], base_calendar_id2real_id(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=None, res_id_req=False, without_user=True, key2_req=True): - if context is None: - context = {} - new_model = [] - for data in models: - if type(data) in (list, tuple): - new_model.append((data[0], base_calendar_id2real_id(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) - - -class ir_model(osv.osv): - - _inherit = 'ir.model' - - def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'): - - new_ids = isinstance(ids, (str, int, long)) and [ids] or ids - if context is None: - context = {} - data = super(ir_model, self).read(cr, uid, new_ids, fields=fields, \ - context=context, load=load) - if data: - for val in data: - val['id'] = base_calendar_id2real_id(val['id']) - return isinstance(ids, (str, int, long)) and data[0] or data - - -original_exp_report = openerp.service.report.exp_report - -def exp_report(db, uid, object, ids, data=None, context=None): - """ - Export Report - """ - if object == 'printscreen.list': - original_exp_report(db, uid, object, ids, data, context) - new_ids = [] - for id in ids: - new_ids.append(base_calendar_id2real_id(id)) - if data.get('id', False): - data['id'] = base_calendar_id2real_id(data['id']) - return original_exp_report(db, uid, object, new_ids, data, context) - -openerp.service.report.exp_report = exp_report - - -class crm_meeting_type(osv.Model): - _name = 'crm.meeting.type' - _description = 'Meeting Type' - _columns = { - 'name': fields.char('Name', size=64, required=True, translate=True), - } - - -class crm_meeting(osv.Model): - """ Model for CRM meetings """ - _name = 'crm.meeting' - _description = "Meeting" - _order = "id desc" - _inherit = ["mail.thread", "ir.needaction_mixin"] - - def do_run_scheduler(self,cr,uid,id,context=None): - self.pool.get('calendar.alarm_manager').do_run_scheduler(cr,uid,context=context) - - def get_recurrent_date_by_event(self,cr,uid,event,context=None): - """Get recurrent dates based on Rule string and all event where recurrent_id is child - """ - - def todate(date): - val = parser.parse(''.join((re.compile('\d')).findall(date))) - ## Dates are localized to saved timezone if any, else current timezone. - if not val.tzinfo: - val = pytz.UTC.localize(val) - return val.astimezone(timezone) - - timezone = pytz.timezone(event.vtimezone or context.get('tz') or 'UTC') - - startdate = pytz.UTC.localize(datetime.strptime(event.date, "%Y-%m-%d %H:%M:%S")) #Add "+hh:mm" timezone - if not startdate: - startdate = datetime.now() - - ## Convert the start date to saved timezone (or context tz) as it'll - ## define the correct hour/day asked by the user to repeat for recurrence. - startdate = startdate.astimezone(timezone) #transform "+hh:mm" timezone - - rset1 = rrule.rrulestr(str(event.rrule), dtstart=startdate, forceset=True) - - ids_depending = self.search(cr,uid,[('recurrent_id','=',event.id),'|',('active','=',False),('active','=',True)],context=context) - all_events = self.browse(cr,uid,ids_depending,context=context) - - for ev in all_events: - rset1._exdate.append(todate(ev.recurrent_id_date)) - #rset1.rdate(pytz.UTC.localize(datetime.strptime(ev.date, "%Y-%m-%d %H:%M:%S"))) - - return [d.astimezone(pytz.UTC) for d in rset1] - - - def _get_recurrency_end_date(self, data, context=None): - if data.get('recurrency') and data.get('end_type') in ('count', unicode('count')): - data_date_deadline = datetime.strptime(data.get('date_deadline'), '%Y-%m-%d %H:%M:%S') - if data.get('rrule_type') in ('daily', unicode('count')): - rel_date = relativedelta(days=data.get('count')+1) - elif data.get('rrule_type') in ('weekly', unicode('weekly')): - rel_date = relativedelta(days=(data.get('count')+1)*7) - elif data.get('rrule_type') in ('monthly', unicode('monthly')): - rel_date = relativedelta(months=data.get('count')+1) - elif data.get('rrule_type') in ('yearly', unicode('yearly')): - rel_date = relativedelta(years=data.get('count')+1) - end_date = data_date_deadline + rel_date - else: - end_date = data.get('end_date') - return end_date - - def _find_my_attendee(self, cr, uid, meeting_ids, context=None): - """ - Return the first attendee where the user connected has been invited from all the meeting_ids in parameters - """ - user = self.pool.get('res.users').browse(cr, uid, uid, context=context) - for meeting_id in meeting_ids: - for attendee in self.browse(cr,uid,meeting_id,context).attendee_ids: - if user.partner_id.id == attendee.partner_id.id: - return attendee - return False - - def _get_display_time(self, cr, uid, meeting_id, context=None): - """ - Return date and time (from to from) based on duration with timezone in string : - eg. - 1) if user add duration for 2 hours, return : August-23-2013 at ( 04-30 To 06-30) (Europe/Brussels) - 2) if event all day ,return : AllDay, July-31-2013 - """ - if context is None: - context = {} - - tz = context.get('tz', False) - if not tz: #tz can have a value False, so dont do it in the default value of get ! - tz = pytz.timezone('UTC') - - meeting = self.browse(cr, uid, meeting_id, context=context) - date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(meeting.date, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context) - date_deadline = fields.datetime.context_timestamp(cr, uid, datetime.strptime(meeting.date_deadline, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context) - event_date = date.strftime('%B-%d-%Y') - display_time = date.strftime('%H-%M') - if meeting.allday: - time = _("AllDay , %s") % (event_date) - elif meeting.duration < 24: - duration = date + timedelta(hours= meeting.duration) - time = ("%s at ( %s To %s) (%s)") % (event_date, display_time, duration.strftime('%H-%M'), tz) - else : - time = ("%s at %s To\n %s at %s (%s)") % (event_date, display_time, date_deadline.strftime('%B-%d-%Y'), date_deadline.strftime('%H-%M'), tz) - return time - - def _compute(self, cr, uid, ids, fields, arg, context=None): - res = {} - for meeting_id in ids: - res[meeting_id] = {} - attendee = self._find_my_attendee(cr, uid, [meeting_id], context) - for field in fields: - if field == 'is_attendee': - res[meeting_id][field] = True if attendee else False - elif field == 'attendee_status': - res[meeting_id][field] = attendee.state if attendee else 'needsAction' - elif field == 'display_time': - res[meeting_id][field] = self._get_display_time(cr, uid, meeting_id, context=context) - return res - - def _get_rulestring(self, cr, uid, ids, name, arg, context=None): - """ - Gets Recurrence rule string according to value type RECUR of iCalendar from the values given. - @return: dictionary of rrule value. - """ - - result = {} - if not isinstance(ids, list): - ids = [ids] - - for id in ids: - #read these fields as SUPERUSER because if the record is private a normal search could return False and raise an error - data = self.browse(cr, SUPERUSER_ID, id, context=context) - - if data.interval and data.interval < 0: - raise osv.except_osv(_('Warning!'), _('Interval cannot be negative.')) - if data.count and data.count <= 0: - raise osv.except_osv(_('Warning!'), _('Count cannot be negative or 0.')) - - data = self.read(cr, uid, id, ['id','byday','recurrency', 'month_list','end_date', 'rrule_type', 'month_by', 'interval', 'count', 'end_type', 'mo', 'tu', 'we', 'th', 'fr', 'sa', 'su', 'day', 'week_list' ], context=context) - event = data['id'] - if data['recurrency']: - result[event] = self.compute_rule_string(data) - else: - result[event] = "" - return result - - def _rrule_write(self, cr, uid, ids, field_name, field_value, args, context=None): - if not isinstance(ids, list): - ids = [ids] - data = self._get_empty_rrule_data() - if field_value: - - data['recurrency'] = True - for event in self.browse(cr, uid, ids, context=context): - rdate = event.date - update_data = self._parse_rrule(field_value, dict(data), rdate) - data.update(update_data) - self.write(cr, uid, ids, data, context=context) - return True - - def _tz_get(self, cr, uid, context=None): - return [(x.lower(), x) for x in pytz.all_timezones] - - _track = { - 'location': { - 'base_calendar.subtype_invitation': lambda self, cr, uid, obj, ctx=None: True, - }, - 'date': { - 'base_calendar.subtype_invitation': lambda self, cr, uid, obj, ctx=None: True, - }, - } - _columns = { -# 'create_date': fields.datetime('Creation Date', readonly=True), -# 'write_date': fields.datetime('Write Date', readonly=True), - 'id': fields.integer('ID', readonly=True), - - - 'state': fields.selection([('draft', 'Unconfirmed'), ('open', 'Confirmed')], string='Status', size=16, readonly=True, track_visibility='onchange'), - - 'name': fields.char('Meeting Subject', size=128, required=True, states={'done': [('readonly', True)]}), - 'is_attendee': fields.function(_compute, string='Attendee', type="boolean", multi='attendee'), - 'attendee_status': fields.function(_compute, string='Attendee Status', type="selection", multi='attendee'), - 'display_time': fields.function(_compute, string='Event Time', type="char", multi='attendee'), - - 'sequence': fields.integer('Sequence'), - - 'date': fields.datetime('Date', states={'done': [('readonly', True)]}, required=True,track_visibility='onchange'), - 'date_deadline': fields.datetime('End Date', states={'done': [('readonly', True)]}, required=True,), - - 'duration': fields.float('Duration', states={'done': [('readonly', True)]}), - 'description': fields.text('Description', states={'done': [('readonly', True)]}), - 'class': fields.selection([('public', 'Public'), ('private', 'Private'), ('confidential', 'Public for Employees')], 'Privacy', states={'done': [('readonly', True)]}), - 'location': fields.char('Location', size=264, help="Location of Event", track_visibility='onchange', states={'done': [('readonly', True)]}), - 'show_as': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Show Time as', states={'done': [('readonly', True)]}), - - #'state': fields.selection([('tentative', 'Uncertain'),('cancelled', 'Cancelled'),('confirmed', 'Confirmed'),],'Status', readonly=True, track_visibility='onchange'), - - #FIELD FOR RECURRENCY - #'exdate': fields.text('Exception Date/Times', help="This property defines the list of date/time exceptions for a recurring calendar component."), - 'rrule': fields.function(_get_rulestring, type='char', size=124, fnct_inv=_rrule_write, store=True, string='Recurrent Rule'), - 'rrule_type': fields.selection([('daily', 'Day(s)'),('weekly', 'Week(s)'),('monthly', 'Month(s)'),('yearly', 'Year(s)')], 'Recurrency', states={'done': [('readonly', True)]}, help="Let the event automatically repeat at that interval"), - 'recurrency': fields.boolean('Recurrent', help="Recurrent Meeting"), - 'recurrent_id': fields.integer('Recurrent ID'), - 'recurrent_id_date': fields.datetime('Recurrent ID date'), - #'recurrence_end_date': fields.function(_get_recurrence_end_date, type='datetime', store=True, string='Recurrence end date',priority=30), - 'vtimezone': fields.selection(_tz_get, size=64, string='Timezone'), - 'end_type' : fields.selection([('count', 'Number of repetitions'), ('end_date','End date')], 'Recurrence Termination'), - 'interval': fields.integer('Repeat Every', help="Repeat every (Days/Week/Month/Year)"), - 'count': fields.integer('Repeat', help="Repeat x times"), - 'mo': fields.boolean('Mon'), - 'tu': fields.boolean('Tue'), - 'we': fields.boolean('Wed'), - 'th': fields.boolean('Thu'), - 'fr': fields.boolean('Fri'), - 'sa': fields.boolean('Sat'), - 'su': fields.boolean('Sun'), - 'month_by': fields.selection([('date', 'Date of month'),('day', 'Day of month')], 'Option'), - 'day': fields.integer('Date of month'), - 'week_list': fields.selection([('MO', 'Monday'),('TU', 'Tuesday'),('WE', 'Wednesday'),('TH', 'Thursday'),('FR', 'Friday'),('SA', 'Saturday'),('SU', 'Sunday')], 'Weekday'), - 'byday': fields.selection([('1', 'First'),('2', 'Second'),('3', 'Third'),('4', 'Fourth'),('5', 'Fifth'),('-1', 'Last')], 'By day'), - 'end_date': fields.date('Repeat Until'), - 'allday': fields.boolean('All Day', states={'done': [('readonly', True)]}), - - 'user_id': fields.many2one('res.users', 'Responsible', states={'done': [('readonly', True)]}), - 'color_partner_id': fields.related('user_id','partner_id','id',type="int",string="colorize",store=False), #Color of creator - 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the event alarm information without removing it."), - - 'categ_ids': fields.many2many('crm.meeting.type', 'meeting_category_rel', 'event_id', 'type_id', 'Tags'), - 'attendee_ids': fields.one2many('calendar.attendee', 'event_id', 'Attendees', ondelete='cascade'), - #'attendee_ids': fields.many2many('calendar.attendee', 'crmmeeting_attendee_rel', 'crmmeeting_id', 'attendee_id', 'Attendees', ondelete='cascade'), - 'partner_ids': fields.many2many('res.partner', string='Attendees', states={'done': [('readonly', True)]}), - 'alarm_ids': fields.many2many('calendar.alarm', string='Reminders',ondelete="restrict"), - } - _defaults = { - 'end_type': 'count', - 'count': 1, - 'rrule_type': False, - 'state': 'draft', - 'class': 'public', - 'show_as': 'busy', - 'month_by': 'date', - 'interval': 1, - 'active': 1, - 'user_id': lambda self, cr, uid, ctx: uid, - 'partner_ids': lambda self, cr, uid, ctx: [self.pool.get('res.users').browse(cr, uid, [uid],context=ctx)[0].partner_id.id] - } - - def _check_closing_date(self, cr, uid, ids, context=None): - for event in self.browse(cr, uid, ids, context=context): - if event.date_deadline < event.date: - return False - return True - - _constraints = [ - (_check_closing_date, 'Error ! End date cannot be set before start date.', ['date_deadline']), - ] - - def onchange_dates(self, cr, uid, ids, start_date, duration=False, end_date=False, allday=False, context=None): - """Returns duration and/or end date based on values passed - @param ids: List of calendar event's IDs. - @param start_date: Starting date - @param duration: Duration between start date and end date - @param end_date: Ending Datee - """ - if context is None: - context = {} - - value = {} - if not start_date: - return value - - if not end_date and not duration: - duration = 1.00 - value['duration'] = duration - - - if allday: # For all day event - start = datetime.strptime(start_date.split(' ')[0].split('T')[0], "%Y-%m-%d") - duration = 24.0 - value['duration'] = duration - # change start_date's time to 00:00:00 in the user's timezone - user = self.pool.get('res.users').browse(cr, uid, uid) - tz = pytz.timezone(user.tz) if user.tz else pytz.utc - start = pytz.utc.localize(start).astimezone(tz) # convert start in user's timezone - #start = start.replace(hour=0, minute=0, second=0) # remove time - start = start.astimezone(pytz.utc) # convert start back to utc - value['date'] = start.strftime("%Y-%m-%d") + ' 00:00:00' - else: - start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S") - - if end_date and not duration: - end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S") - diff = end - start - duration = float(diff.days)* 24 + (float(diff.seconds) / 3600) - value['duration'] = round(duration, 2) - elif not end_date: - end = start + timedelta(hours=duration) - value['date_deadline'] = end.strftime("%Y-%m-%d %H:%M:%S") - elif end_date and duration and not allday: - # we have both, keep them synchronized: - # set duration based on end_date (arbitrary decision: this avoid - # getting dates like 06:31:48 instead of 06:32:00) - end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S") - diff = end - start - duration = float(diff.days)* 24 + (float(diff.seconds) / 3600) - value['duration'] = round(duration, 2) - - return {'value': value} - - def new_invitation_token(self, cr, uid, record, partner_id): - db_uuid = self.pool.get('ir.config_parameter').get_param(cr, uid, 'database.uuid') - invitation_token = hashlib.sha256('%s-%s-%s-%s-%s' % (time.time(), db_uuid, record._name, record.id, partner_id)).hexdigest() - return invitation_token - - def create_attendees(self, cr, uid, ids, context): - att_obj = self.pool.get('calendar.attendee') - user_obj = self.pool.get('res.users') - current_user = user_obj.browse(cr, uid, uid, context=context) - res = {} - for event in self.browse(cr, uid, ids, context): - attendees = {} - for att in event.attendee_ids: - attendees[att.partner_id.id] = True - new_attendees = [] - new_att_partner_ids = [] -# mail_to = "" - for partner in event.partner_ids: - if partner.id in attendees: - continue - access_token = self.new_invitation_token(cr, uid, event, partner.id) - values = { - 'partner_id': partner.id, - 'event_id': event.id, - 'access_token': access_token, - 'email': partner.email, - } - - if partner.id == current_user.partner_id.id: - values['state']='accepted' - - att_id = self.pool.get('calendar.attendee').create(cr, uid,values, context=context) -# if partner.email: -# mail_to = mail_to + " " + partner.email - - new_attendees.append(att_id) - new_att_partner_ids.append(partner.id) - - - if not current_user.email or current_user.email != partner.email : - mail_from = current_user.email or tools.config.get('email_from', False) - print "Send mail... from ",mail_from, " to ",att_id - if self.pool.get('calendar.attendee')._send_mail_to_attendees(cr, uid, att_id, email_from = mail_from, context=context): - self.message_post(cr, uid, event.id, body=_("An invitation email has been sent to attendee %s") % (partner.name,),subtype="base_calendar.subtype_invitation", context=context) - - if new_attendees: - self.write(cr, uid, [event.id], {'attendee_ids': [(4, att) for att in new_attendees]},context=context) - if new_att_partner_ids: - self.message_subscribe(cr, uid, [event.id], new_att_partner_ids, context=context) - - - # We remove old attendees who are not in partner_ids now. - all_partner_ids = [part.id for part in event.partner_ids] - all_part_attendee_ids = [att.partner_id.id for att in event.attendee_ids] - all_attendee_ids = [att.id for att in event.attendee_ids] - partner_ids_to_remove = map(lambda x: x, set(all_part_attendee_ids + new_att_partner_ids) - set(all_partner_ids)) - - attendee_ids_to_remove = [] - - if partner_ids_to_remove: - attendee_ids_to_remove =self.pool.get("calendar.attendee").search(cr,uid,[('partner_id.id','in',partner_ids_to_remove),('event_id.id','=',event.id)],context=context) - if attendee_ids_to_remove: - self.pool.get("calendar.attendee").unlink(cr, uid, attendee_ids_to_remove, context) - ## NEED TO UNSUBSCRIBE ? - - res[event.id] = { - 'new_attendee_ids' : new_attendees , - 'old_attendee_ids' : all_attendee_ids , - 'removed_attendee_ids' : attendee_ids_to_remove - } - return res - - def get_recurrent_ids(self, cr, uid, select, domain, limit=100, context=None): - """Gives virtual event ids for recurring events based on value of Recurrence Rule - This method gives ids of dates that comes between start date and end date of calendar views - - @param limit: The Number of Results to Return """ - if not context: - context = {} - - if isinstance(select, (str, int, long)): - ids_to_browse = [select] #keep select for return - else: - ids_to_browse = select - - result = [] - for ev in self.browse(cr, uid, select, context=context): - if not ev.recurrency or not ev.rrule: - result.append(ev.id) - continue -# event_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S") -# event_date = pytz.UTC.localize(event_date) - - rdates = self.get_recurrent_date_by_event(cr, uid, ev, context=context) - - for r_date in rdates: - # fix domain evaluation - # step 1: check date and replace expression by True or False, replace other expressions by True - # step 2: evaluation of & and | - # check if there are one False - pile = [] - ok = True - for arg in domain: - if str(arg[0]) in (str('date'), str('date_deadline'), str('end_date')): - if (arg[1] == '='): - ok = r_date.strftime('%Y-%m-%d')==arg[2] - if (arg[1] == '>'): - ok = r_date.strftime('%Y-%m-%d')>arg[2] - if (arg[1] == '<'): - ok = r_date.strftime('%Y-%m-%d')='): - ok = r_date.strftime('%Y-%m-%d')>=arg[2] - if (arg[1] == '<='): - ok = r_date.strftime('%Y-%m-%d')<=arg[2] - pile.append(ok) - elif str(arg) == str('&') or str(arg) == str('|'): - pile.append(arg) - else: - pile.append(True) - pile.reverse() - new_pile = [] - for item in pile: - if not isinstance(item, basestring): - res = item - elif str(item) == str('&'): - first = new_pile.pop() - second = new_pile.pop() - res = first and second - elif str(item) == str('|'): - first = new_pile.pop() - second = new_pile.pop() - res = first or second - new_pile.append(res) - - if [True for item in new_pile if not item]: - continue - # idval = real_id2base_calendar_id(data['id'], r_date.strftime("%Y-%m-%d %H:%M:%S")) - idval = '%d-%s' % (ev.id, r_date.strftime("%Y%m%d%H%M%S")) - result.append(idval) - - - if isinstance(select, (str, int, long)): - return ids and ids[0] or False - else: - ids = list(set(result)) - - return ids - - def compute_rule_string(self, data): - """ - Compute rule string according to value type RECUR of iCalendar from the values given. - @param self: the object pointer - @param data: dictionary of freq and interval value - @return: string containing recurring rule (empty if no rule) - """ - def get_week_string(freq, data): - weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su'] - if freq == 'weekly': - byday = map(lambda x: x.upper(), filter(lambda x: data.get(x) and x in weekdays, data)) - #byday = map(lambda x: x.upper(),[data[day] for day in weekdays if data[day]]) - - if byday: - return ';BYDAY=' + ','.join(byday) - return '' - - def get_month_string(freq, data): - if freq == 'monthly': - if data.get('month_by')=='date' and (data.get('day') < 1 or data.get('day') > 31): - raise osv.except_osv(_('Error!'), ("Please select a proper day of the month.")) - - if data.get('month_by')=='day': #Eg : Second Monday of the month - return ';BYDAY=' + data.get('byday') + data.get('week_list') - elif data.get('month_by')=='date': #Eg : 16th of the month - return ';BYMONTHDAY=' + str(data.get('day')) - return '' - - def get_end_date(data): - if data.get('end_date'): - data['end_date_new'] = ''.join((re.compile('\d')).findall(data.get('end_date'))) + 'T235959Z' - - return (data.get('end_type') == 'count' and (';COUNT=' + str(data.get('count'))) or '') +\ - ((data.get('end_date_new') and data.get('end_type') == 'end_date' and (';UNTIL=' + data.get('end_date_new'))) or '') - - freq = data.get('rrule_type', False) #day/week/month/year - res = '' - if freq: - interval_srting = data.get('interval') and (';INTERVAL=' + str(data.get('interval'))) or '' - res = 'FREQ=' + freq.upper() + get_week_string(freq, data) + interval_srting + get_end_date(data) + get_month_string(freq, data) - - return res - - def _get_empty_rrule_data(self): - return { - 'byday' : False, - 'recurrency' : False, - 'end_date' : False, - 'rrule_type' : False, - 'month_by' : False, - 'interval' : 0, - 'count' : False, - 'end_type' : False, - 'mo' : False, - 'tu' : False, - 'we' : False, - 'th' : False, - 'fr' : False, - 'sa' : False, - 'su' : False, - 'day' : False, - 'week_list' : False - } - - def _parse_rrule(self, rule, data, date_start): - day_list = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su'] - rrule_type = ['yearly', 'monthly', 'weekly', 'daily'] - r = rrule.rrulestr(rule, dtstart=datetime.strptime(date_start, "%Y-%m-%d %H:%M:%S")) - - if r._freq > 0 and r._freq < 4: - data['rrule_type'] = rrule_type[r._freq] - - data['count'] = r._count - data['interval'] = r._interval - data['end_date'] = r._until and r._until.strftime("%Y-%m-%d %H:%M:%S") - #repeat weekly - if r._byweekday: - for i in xrange(0,7): - if i in r._byweekday: - data[day_list[i]] = True - data['rrule_type'] = 'weekly' - #repeat monthly by nweekday ((weekday, weeknumber), ) - if r._bynweekday: - data['week_list'] = day_list[r._bynweekday[0][0]].upper() - data['byday'] = r._bynweekday[0][1] - data['month_by'] = 'day' - data['rrule_type'] = 'monthly' - - if r._bymonthday: - data['day'] = r._bymonthday[0] - data['month_by'] = 'date' - data['rrule_type'] = 'monthly' - - #repeat yearly but for openerp it's monthly, take same information as monthly but interval is 12 times - if r._bymonth: - data['interval'] = data['interval'] * 12 - - #FIXEME handle forever case - #end of recurrence - #in case of repeat for ever that we do not support right now - if not (data.get('count') or data.get('end_date')): - data['count'] = 100 - if data.get('count'): - data['end_type'] = 'count' - else: - data['end_type'] = 'end_date' - - return data - - #def _get_data(self, cr, uid, id, context=None): - # return self.read(cr, uid, id,['date', 'date_deadline']) - -# def need_to_update(self, event_id, vals): -# split_id = str(event_id).split("-") -# if len(split_id) < 2: -# return False -# else: -# date_start = vals.get('date', '') -# try: -# date_start = datetime.strptime(date_start, '%Y-%m-%d %H:%M:%S').strftime("%Y%m%d%H%M%S") -# return date_start == split_id[1] -# except Exception: -# return True - - def message_get_subscription_data(self, cr, uid, ids, user_pid=None, context=None): - res = {} - for virtual_id in ids: - real_id = base_calendar_id2real_id(virtual_id) - result = super(crm_meeting, self).message_get_subscription_data(cr, uid, [real_id], user_pid=None, context=context) - res[virtual_id] = result[real_id] - return res - - def onchange_partner_ids(self, cr, uid, ids, value, context=None): - """ The basic purpose of this method is to check that destination partners - effectively have email addresses. Otherwise a warning is thrown. - :param value: value format: [[6, 0, [3, 4]]] - """ - res = {'value': {}} - - if not value or not value[0] or not value[0][0] == 6: - return - - res.update(self.check_partners_email(cr, uid, value[0][2], context=context)) - return res - - def check_partners_email(self, cr, uid, partner_ids, context=None): - ##TODO : REFACTOR ! - """ Verify that selected partner_ids have an email_address defined. - Otherwise throw a warning. """ - partner_wo_email_lst = [] - for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids, context=context): - if not partner.email: - partner_wo_email_lst.append(partner) - if not partner_wo_email_lst: - return {} - warning_msg = _('The following contacts have no email address :') - for partner in partner_wo_email_lst: - warning_msg += '\n- %s' % (partner.name) - return {'warning': { - 'title': _('Email addresses not found'), - 'message': warning_msg, - } - } - # ---------------------------------------- - # OpenChatter - # ---------------------------------------- - - # shows events of the day for this user - - def _needaction_domain_get(self, cr, uid, context=None): - return [('end_date', '>=', time.strftime(DEFAULT_SERVER_DATE_FORMAT + ' 23:59:59')), ('date', '>=', time.strftime(DEFAULT_SERVER_DATE_FORMAT + ' 23:59:59')), ('user_id', '=', uid)] - - def message_post(self, cr, uid, thread_id, body='', subject=None, type='notification', subtype=None, parent_id=False, attachments=None, context=None, **kwargs): - if isinstance(thread_id, str): - thread_id = get_real_ids(thread_id) - if context.get('default_date'): - del context['default_date'] - return super(crm_meeting, self).message_post(cr, uid, thread_id, body=body, subject=subject, type=type, subtype=subtype, parent_id=parent_id, attachments=attachments, context=context, **kwargs) - - def do_sendmail(self, cr, uid, ids, context=None): - for event in self.browse(cr, uid, ids, context): - current_user = self.pool.get('res.users').browse(cr, uid, uid, context=context) - - if current_user.email: - if self.pool.get('calendar.attendee')._send_mail_to_attendees(cr, uid, [att.id for att in event.attendee_ids], email_from = current_user.email, context=context): - self.message_post(cr, uid, event.id, body=_("An invitation email has been sent to attendee(s)"), subtype="base_calendar.subtype_invitation", context=context) - return; - - def get_attendee(self, cr, uid, meeting_id, context=None): - #Used for view in controller - invitation = {'meeting':{}, 'attendee': [], 'logo': ''} - attendee_pool = self.pool.get('calendar.attendee') - company_logo = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.logo - meeting = self.browse(cr, uid, int(meeting_id), context) - invitation['meeting'] = { - 'event':meeting.name, - 'where': meeting.location, - 'when':meeting.display_time - } - invitation['logo'] = company_logo.replace('\n','\\n') if company_logo else '' - for attendee in meeting.attendee_ids: - invitation['attendee'].append({'name':attendee.cn,'status': attendee.state}) - return invitation - - def get_interval(self, cr, uid, ids, date, interval, context=None): - #Function used only in crm_meeting_data.xml for email template - date = datetime.strptime(date.split('.')[0], DEFAULT_SERVER_DATETIME_FORMAT) - if interval == 'day': - res = str(date.day) - elif interval == 'month': - res = date.strftime('%B') + " " + str(date.year) - elif interval == 'dayname': - res = date.strftime('%A') - elif interval == 'time': - res = date.strftime('%I:%M %p') - return res - - def search(self, cr, uid, args, offset=0, limit=0, order=None, context=None, count=False): - - if context is None: - context={} - - if context.get('mymeetings',False): - partner_id = self.pool.get('res.users').browse(cr, uid, uid, context).partner_id.id - args += ['|', ('partner_ids', 'in', [partner_id]), ('user_id', '=', uid)] - - new_args = [] - for arg in args: - new_arg = arg - - if arg[0] in ('date', unicode('date')) and arg[1]==">=": - if context.get('virtual_id', True): - new_args += ['|','&',('recurrency','=',1),('end_date', arg[1], arg[2])] - # new_args += ['|','&',('recurrency','=',1),('date_deadline', arg[1], arg[2])] - elif arg[0] in ('date', unicode('date')): - if context.get('virtual_id', True): - print '' - #new_args += ['|','&',('recurrency','=',1),('end_date', arg[1], arg[2])] - # new_args += ['|','&',('recurrency','=',1),('date_deadline', arg[1], arg[2])] - elif arg[0] == "id": - new_id = get_real_ids(arg[2]) - new_arg = (arg[0], arg[1], new_id) - new_args.append(new_arg) - #offset, limit and count must be treated separately as we may need to deal with virtual ids - - res = super(crm_meeting,self).search(cr, uid, new_args, offset=0, limit=0, order=order, context=context, count=False) - - if context.get('virtual_id', True): - res = self.get_recurrent_ids(cr, uid, res, args, limit, context=context) - if count: - return len(res) - elif limit: - return res[offset:offset+limit] - return res - - def copy(self, cr, uid, id, default=None, context=None): - if context is None: - context = {} - - default = default or {} - default['attendee_ids'] = False - - - res = super(crm_meeting, self).copy(cr, uid, base_calendar_id2real_id(id), default, context) - return res - - def write(self, cr, uid, ids, values, context=None): - def _only_changes_to_apply_on_real_ids(field_names): - ''' return True if changes are only to be made on the real ids''' - for field in field_names: - if field not in ['name','message_follower_ids']: - return False - return True - - context = context or {} - if isinstance(ids, (str, int, long)): - ids = [ids] - res = False - new_id = False - - # Special write of complex IDS - for event_id in ids: - if len(str(event_id).split('-')) == 1: - continue - - ids.remove(event_id) - real_event_id = base_calendar_id2real_id(event_id) - - # if we are setting the recurrency flag to False or if we are only changing fields that - # should be only updated on the real ID and not on the virtual (like message_follower_ids): - # then set real ids to be updated. - if not values.get('recurrency', True) or _only_changes_to_apply_on_real_ids(values.keys()): - ids.append(real_event_id) - continue - - #if edit one instance of a reccurrent id - data = self.read(cr, uid, event_id, ['date', 'date_deadline', \ - 'rrule', 'duration'])#, 'exdate']) - if data.get('rrule'): - data.update( - values, - recurrent_id=real_event_id, - recurrent_id_date=data.get('date'), - rrule_type=False, - rrule='', - recurrency=False, - end_date = datetime.strptime(values.get('date',False) or data.get('date'),"%Y-%m-%d %H:%M:%S") + timedelta(hours=values.get('duration',False) or data.get('duration')) - ) - - #do not copy the id - if data.get('id'): - del(data['id']) - new_id = self.copy(cr, uid, real_event_id, default=data, context=context) - - date_new = event_id.split('-')[1] - date_new = time.strftime("%Y%m%dT%H%M%SZ", time.strptime(date_new, "%Y%m%d%H%M%S")) - - #exdate = (data['exdate'] and (data['exdate'] + ',') or '') + date_new - #res = super(crm_meeting, self).write(cr, uid, [real_event_id], {'exdate': exdate}) - - context.update({'active_id': new_id, 'active_ids': [new_id]}) - continue - - res = super(crm_meeting, self).write(cr, uid, ids, values, context=context) - - # set end_date for calendar searching - if values.get('recurrency', True) and values.get('end_type', 'count') in ('count', unicode('count')) and \ - (values.get('rrule_type') or values.get('count') or values.get('date') or values.get('date_deadline')): - for data in self.read(cr, uid, ids, ['date', 'date_deadline', 'recurrency', 'rrule_type', 'count', 'end_type'], context=context): - end_date = self._get_recurrency_end_date(data, context=context) - super(crm_meeting, self).write(cr, uid, [data['id']], {'end_date': end_date}, context=context) - - attendees_create = False - if values.get('partner_ids', False): - attendees_create = self.create_attendees(cr, uid, ids, context) - - if values.get('date', False) and values.get('active',True): #Not send mail when install data - the_id = new_id or (ids and int(ids[0])); - - if attendees_create: - attendees_create = attendees_create[the_id] - mail_to_ids = list(set(attendees_create['old_attendee_ids']) - set(attendees_create['removed_attendee_ids'])) - else: - mail_to_ids = [att.id for att in self.browse(cr,uid,the_id,context=context).attendee_ids] - - if mail_to_ids: - current_user = self.pool.get('res.users').browse(cr, uid, uid, context=context) - if self.pool.get('calendar.attendee')._send_mail_to_attendees(cr, uid, mail_to_ids, template_xmlid='crm_email_template_meeting_changedate',email_from=current_user.email, context=context): - self.message_post(cr, uid, the_id, body=_("A email has been send to specify that the date has been changed !"),subtype="base_calendar.subtype_invitation", context=context) - else: - print 'Send mail return false' - return res or True and False - - def create(self, cr, uid, vals, context=None): - - if context is None: - context = {} - - if not 'user_id' in vals: #Else bug with quick_create when we are filter on an other user - vals['user_id'] = uid - - if vals.get('recurrency', True) and vals.get('end_type', 'count') in ('count', unicode('count')) and \ - (vals.get('rrule_type') or vals.get('count') or vals.get('date') or vals.get('date_deadline')): - vals['end_date'] = self._get_recurrency_end_date(vals, context=context) - - res = super(crm_meeting, self).create(cr, uid, vals, context=context) - - self.create_attendees(cr, uid, [res], context=context) - return res - - def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False): - - if not context: - context = {} - - if 'date' in groupby: - raise osv.except_osv(_('Warning!'), _('Group by date is not supported, use the calendar view instead.')) - virtual_id = context.get('virtual_id', True) - context.update({'virtual_id': False}) - res = super(crm_meeting, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby) - for re in res: - #remove the count, since the value is not consistent with the result of the search when expand the group - for groupname in groupby: - if re.get(groupname + "_count"): - del re[groupname + "_count"] - re.get('__context', {}).update({'virtual_id' : virtual_id}) - return res - - def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'): - - if context is None: - context = {} - fields2 = fields and fields[:] or None - - EXTRAFIELDS = ('class','user_id','duration', 'date','rrule', 'vtimezone')#, 'exdate') - for f in EXTRAFIELDS: - if fields and (f not in fields): - fields2.append(f) - - # FIXME This whole id mangling has to go! - if isinstance(ids, (str, int, long)): - select = [ids] - else: - select = ids - - select = map(lambda x: (x, base_calendar_id2real_id(x)), select) - result = [] - - real_data = super(crm_meeting, self).read(cr, uid, [real_id for base_calendar_id, real_id in select], fields=fields2, context=context, load=load) - real_data = dict(zip([x['id'] for x in real_data], real_data)) - - - for base_calendar_id, real_id in select: - res = real_data[real_id].copy() - - res = real_data[real_id].copy() - ls = base_calendar_id2real_id(base_calendar_id, with_date=res and res.get('duration', 0) or 0) - if not isinstance(ls, (str, int, long)) and len(ls) >= 2: - #recurrent_dates = [d.strftime("%Y-%m-%d %H:%M:%S") for d in get_recurrent_dates(res['rrule'], res['date'], res['exdate'],res['vtimezone'], context=context)] - #if not (ls[1] in recurrent_dates or ls[1] in res['exdate']): #when update a recurrent event - - res['date'] = ls[1] - res['date_deadline'] = ls[2] - res['id'] = base_calendar_id - result.append(res) - - for r in result: - if r['user_id']: - user_id = type(r['user_id']) in (tuple,list) and r['user_id'][0] or r['user_id'] - if user_id==uid: - continue - if r['class']=='private': - for f in r.keys(): - if f not in ('id','date','date_deadline','duration','user_id','state','interval','count'): - if isinstance(r[f], list): - r[f] = [] - else: - r[f] = False - if f=='name': - r[f] = _('Busy') - - for r in result: - for k in EXTRAFIELDS: - if (k in r) and (fields and (k not in fields)): - del r[k] - if isinstance(ids, (str, int, long)): - return result and result[0] or False - return result - - - def count_left_instance(self,cr,uid,event_id,context=None): - - event = self.browse(cr,uid,event_id,context=context) - if event.recurrent_id and event.recurrent_id > 0: - parent_event_id = event.recurrent_id - else: - parent_event_id = event.id - domain = ['|', ('id', '=', parent_event_id), ('recurrent_id', '=', parent_event_id)] - - count = self.search(cr, uid, domain, context=context) - print "Count = ",count - return len(count) - - def get_linked_ids(self,cr,uid,event_id,show_unactive=True,context=None): - event = self.browse(cr,uid,event_id,context=context) - if event.recurrent_id and event.recurrent_id > 0: - parent_event_id = event.recurrent_id - else: - parent_event_id = event.id - - domain = ['|', ('id', '=', parent_event_id), ('recurrent_id', '=', parent_event_id)] - - if show_unactive: - domain += ['|',('active', '=', True), ('active', '=', False)] - - return super(crm_meeting, self).search(cr, uid, domain, context=context) - -# def delete(self,cr,uid,ids,context=None): -# if not isinstance(ids, list): -# ids = [ids] -# all_ids = [] -# for id_to_unlink in ids: -# all_ids += self.get_linked_ids(cr, uid, id_to_unlink, context=context) -# print "in deleTe functIon === ids : %s, all_ids : %s" % (ids,all_ids) -# all_ids = list(set(all_ids)) -# res = super(crm_meeting, self).unlink(cr, uid, all_ids, context=context) -# return all_ids - - - def unlink(self, cr, uid, ids,unlink_level=0, context=None): - print "IN UNLINK !!!" - if not isinstance(ids, list): - ids = [ids] - res = False - - ids_to_exclure = [] - ids_to_unlink = [] - - #One time moved to google_Calendar, we can specify, an if not in google, and not rec or get_inst = 0, we delete it - for event_id in ids: - if unlink_level == 1 and len(str(event_id).split('-')) == 1: ## if ID REAL - if self.browse(cr,uid,event_id).recurrent_id: - # print "Could not be deleted ! because instance of recursive" - ids_to_exclure.append(event_id) - else: - ids_to_unlink.append(event_id) - else: - #print "Could not be deleted ! because instance virtual" - ids_to_exclure.append(event_id) - - - if ids_to_unlink: - res = super(crm_meeting, self).unlink(cr, uid, ids_to_unlink, context=context) - - if ids_to_exclure: - for id_to_exclure in ids_to_exclure: - res = self.write(cr, uid, id_to_exclure, {'active': False}, context=context) - - return res - - -class mail_message(osv.osv): - _inherit = "mail.message" - - def search(self, cr, uid, args, offset=0, limit=0, order=None, context=None, count=False): - ''' - convert the search on real ids in the case it was asked on virtual ids, then call super() - ''' - for index in range(len(args)): - if args[index][0] == "res_id" and isinstance(args[index][2], str): - args[index][2] = get_real_ids(args[index][2]) - return super(mail_message, self).search(cr, uid, args, offset=offset, limit=limit, order=order, context=context, count=count) - - def _find_allowed_model_wise(self, cr, uid, doc_model, doc_dict, context=None): - if doc_model == 'crm.meeting': - for virtual_id in self.pool[doc_model].get_recurrent_ids(cr, uid, doc_dict.keys(), [], context=context): - doc_dict.setdefault(virtual_id, doc_dict[get_real_ids(virtual_id)]) - return super(mail_message, self)._find_allowed_model_wise(cr, uid, doc_model, doc_dict, context=context) - -class ir_attachment(osv.osv): - _inherit = "ir.attachment" - - def search(self, cr, uid, args, offset=0, limit=0, order=None, context=None, count=False): - ''' - convert the search on real ids in the case it was asked on virtual ids, then call super() - ''' - for index in range(len(args)): - if args[index][0] == "res_id" and isinstance(args[index][2], str): - args[index][2] = get_real_ids(args[index][2]) - return super(ir_attachment, self).search(cr, uid, args, offset=offset, limit=limit, order=order, context=context, count=count) - - def write(self, cr, uid, ids, vals, context=None): - ''' - when posting an attachment (new or not), convert the virtual ids in real ids. - ''' - if isinstance(vals.get('res_id'), str): - vals['res_id'] = get_real_ids(vals.get('res_id')) - return super(ir_attachment, self).write(cr, uid, ids, vals, context=context) - -class invite_wizard(osv.osv_memory): - _inherit = 'mail.wizard.invite' - - def default_get(self, cr, uid, fields, context=None): - ''' - in case someone clicked on 'invite others' wizard in the followers widget, transform virtual ids in real ids - ''' - result = super(invite_wizard, self).default_get(cr, uid, fields, context=context) - if 'res_id' in result: - result['res_id'] = get_real_ids(result['res_id']) - return result - diff --git a/addons/base_calendar/base_calendar_data.xml b/addons/base_calendar/base_calendar_data.xml deleted file mode 100644 index 89de5b48787..00000000000 --- a/addons/base_calendar/base_calendar_data.xml +++ /dev/null @@ -1,489 +0,0 @@ - - - - - - - Invitation - crm.meeting - Warning, a mandatory field has been modified since the creation of this event - - - - - Event - calendar.event - - - - 15 min notif - - minutes - notification - - - 30 min notif - - minutes - notification - - - 1 hour notif - - hours - notification - - - 2 hours notif - - hours - notification - - - 1 day notif - - days - notification - - - - - 15 min mail - - minutes - email - - - 30 min mail - - minutes - email - - - 1 hour mail - - hours - email - - - 2 hours mail - - hours - email - - - 1 day mail - - days - email - - - - - - Run Event Reminder - - - 1 - minutes - -1 - - - - - - - - Customer Meeting - - - - Internal Meeting - - - - Off-site Meeting - - - - Open Discussion - - - - Feedback Meeting - - - - Meeting - crm.meeting - - - - Meeting Invitation - ${object.event_id.user_id.email or ''} - ${object.event_id.name} - - - ${('' if object.partner_id and object.partner_id.email and object.partner_id.email==object.email else object.email|safe)} - ${object.partner_id and object.partner_id.email and object.partner_id.email==object.email and object.partner_id.id or False } - - - - - - ${object.event_id.name} - - - -
-
- ${object.event_id.name} -
-
- Hello ${object.cn} ,

${object.event_id.user_id.partner_id.name} invited you for the ${object.event_id.name} meeting of ${object.event_id.user_id.company_id.name}.

-
-
- - - - - -
-
${object.event_id.get_interval(object.event_id.date, 'dayname')}
-
- ${object.event_id.get_interval(object.event_id.date,'day')} -
-
${object.event_id.get_interval(object.event_id.date, 'month')}
-
${object.event_id.get_interval(object.event_id.date, 'time')}
-
- - % if object.event_id.location: - - - - - % endif - % if object.event_id.description : - - - - - % endif - % if not object.event_id.allday and object.event_id.duration: - - - - - % endif - - - - -
-
- Where -
-
-
- : ${object.event_id.location} - (View Map) - -
-
-
- What -
-
-
- : ${object.event_id.description} -
-
-
- Duration -
-
-
- : ${('%dH%02d' % (object.event_id.duration,(object.event_id.duration*60)%60))} -
-
-
- Attendees -
-
- : - % for attendee in object.event_id.attendee_ids: -
- % if attendee.cn != object.cn: - ${attendee.cn} - % else: - You - % endif - % endfor -
-
-
-
- Accept - Decline - View -
-
- - - ]]> -
-
- - - Meeting Invitation - ${object.event_id.user_id.email or ''} - ${object.event_id.name} - Date has been updated - - - ${('' if object.partner_id and object.partner_id.email and object.partner_id.email==object.email else object.email|safe)} - ${object.partner_id and object.partner_id.email and object.partner_id.email==object.email and object.partner_id.id or False } - - - - - - ${object.event_id.name} - - - -
-
- ${object.event_id.name} -
-
- Hello ${object.cn} ,
-

The date of the meeting has been changed...
- The meeting created by ${object.event_id.user_id.partner_id.name} is now scheduled for : ${object.event_id.date}.

-
-
- - - - - -
-
${object.event_id.get_interval(object.event_id.date, 'dayname')}
-
- ${object.event_id.get_interval(object.event_id.date,'day')} -
-
${object.event_id.get_interval(object.event_id.date, 'month')}
-
${object.event_id.get_interval(object.event_id.date, 'time')}
-
- - % if object.event_id.location: - - - - - % endif - % if object.event_id.description : - - - - - % endif - % if not object.event_id.allday and object.event_id.duration: - - - - - % endif - - - - -
-
- Where -
-
-
- : ${object.event_id.location} - (View Map) - -
-
-
- What -
-
-
- : ${object.event_id.description} -
-
-
- Duration -
-
-
- : ${('%dH%02d' % (object.event_id.duration,(object.event_id.duration*60)%60))} -
-
-
- Attendees -
-
- : - % for attendee in object.event_id.attendee_ids: -
- % if attendee.cn != object.cn: - ${attendee.cn} - % else: - You - % endif - % endfor -
-
-
-
- Accept - Decline - View -
-
- - - ]]> -
-
- - - Meeting Invitation - ${object.event_id.user_id.email or ''} - ${object.event_id.name} - Reminder - - - ${('' if object.partner_id and object.partner_id.email and object.partner_id.email==object.email else object.email|safe)} - ${object.partner_id and object.partner_id.email and object.partner_id.email==object.email and object.partner_id.id or False } - - - - - - ${object.event_id.name} - - - -
-
- ${object.event_id.name} -
-
- Hello ${object.cn} ,
-

this it a rmeinder for the event below :

-
-
- - - - - -
-
${object.event_id.get_interval(object.event_id.date, 'dayname')}
-
- ${object.event_id.get_interval(object.event_id.date,'day')} -
-
${object.event_id.get_interval(object.event_id.date, 'month')}
-
${object.event_id.get_interval(object.event_id.date, 'time')}
-
- - % if object.event_id.location: - - - - - % endif - % if object.event_id.description : - - - - - % endif - % if not object.event_id.allday and object.event_id.duration: - - - - - % endif - - - - -
-
- Where -
-
-
- : ${object.event_id.location} - (View Map) - -
-
-
- What -
-
-
- : ${object.event_id.description} -
-
-
- Duration -
-
-
- : ${('%dH%02d' % (object.event_id.duration,(object.event_id.duration*60)%60))} -
-
-
- Attendees -
-
- : - % for attendee in object.event_id.attendee_ids: -
- % if attendee.cn != object.cn: - ${attendee.cn} - % else: - You - % endif - % endfor -
-
-
-
- - - ]]> -
-
- -
-
diff --git a/addons/base_calendar/base_calendar_demo.xml b/addons/base_calendar/base_calendar_demo.xml deleted file mode 100644 index e94f7681d25..00000000000 --- a/addons/base_calendar/base_calendar_demo.xml +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - - - Another Partner - - - another_partner@example.com - - - Another User - Another Login - - - - - - - - - - - - - - - - - - - - Follow-up for Project proposal - Meeting to discuss project plan and hash out the details of implementation. - - - - - open - - - - - - - Initial discussion - Discussion with partner for product. - - - - - draft - - - - - - Pricing Discussion - Internal meeting for discussion for new pricing for product and services. - - - - - open - - - - - - - Requirements review - - - - - open - - - - - - Changes in Designing - - - - - open - - - - - - - Presentation for new Services - - - - - draft - - - - - - - Meeting between 2 others users than me - - - - - draft - - - - - - - Meeting between 2 others users than me - - - - - draft - - - - diff --git a/addons/base_calendar/base_calendar_view.xml b/addons/base_calendar/base_calendar_view.xml deleted file mode 100644 index fae289eebc8..00000000000 --- a/addons/base_calendar/base_calendar_view.xml +++ /dev/null @@ -1,321 +0,0 @@ - - - - - - - - - Meeting Types Tree - crm.meeting.type - - - - - - - - - Meeting Types - crm.meeting.type - form - - - - - - - CRM - Meetings Form - crm.meeting - - -
- - - - -
-
-
-

- -

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ - \ - "; - a = self.do_notify(res.title,res.message,true); - - $(".link2event").on('click', function() { - self.rpc("/web/action/load", { - action_id: "base_calendar.action_crm_meeting_notify", - }).then( function(r) { - r.res_id = res.event_id; - return self.action_manager.do_action(r); - }); - - }); - a.element.find(".link2recall").on('click',function() { - self.getMyNotifBox(this).find('.ui-notify-close').trigger("click"); - }); - - a.element.find(".link2showed").on('click',function() { - self.getMyNotifBox(this).find('.ui-notify-close').trigger("click"); - self.rpc("/calendar/NextNotify", { - type: "UPDATE" - }); - }); - } - //If notification already displayed in the past, we remove the css attribute which hide this notification - else if (self.getMyNotifBox($.find(".eid_"+res.event_id)).attr("style") !== ""){ - self.getMyNotifBox($.find(".eid_"+res.event_id)).attr("style",""); - } - },res.timer * 1000); - }); - } - ); - }, - check_notifications: function() { - var self= this; - self.get_next_event(); - setInterval(function(){ - self.get_next_event(); - }, 5 * 60 * 1000 ); - }, - - //Override the show_application of addons/web/static/src/js/chrome.js - show_application: function() { - this._super(); - this.check_notifications(); - } - }); - - - instance.base_calendar.invitation = instance.web.Widget.extend({ - - init: function(parent, db, action, id, view, attendee_data) { - this._super(); - this.db = db; - this.action = action; - this.id = id; - this.view = view; - this.attendee_data = attendee_data; - }, - start: function() { - var self = this; - if(instance.session.session_is_valid(self.db) && instance.session.username != "anonymous") { - self.redirect_meeting_view(self.db,self.action,self.id,self.view); - } else { - self.open_invitation_form(self.attendee_data); - } - }, - open_invitation_form : function(invitation){ - this.$el.html(QWeb.render('invitation_view', {'invitation': JSON.parse(invitation)})); - }, - redirect_meeting_view : function(db, action, meeting_id, view){ - var self = this; - var action_url = ''; - if(view == "form") { - action_url = _.str.sprintf('/?db=%s#id=%s&view_type=%s&model=crm.meeting', db, meeting_id, view, meeting_id); - } else { - action_url = _.str.sprintf('/?db=%s#view_type=%s&model=crm.meeting&action=%s',self.db,self.view,self.action); - } - var reload_page = function(){ - return location.replace(action_url); - } - reload_page(); - }, - }); - - instance.web.form.Many2ManyAttendee = instance.web.form.FieldMany2ManyTags.extend({ - tag_template: "many2manyattendee", - initialize_texttext: function() { - return _.extend(this._super(),{ - html : { - tag: '
' - } - }); - }, - map_tag: function(value){ - return _.map(value, function(el) {return {name: el[1], id:el[0], state: el[2]};}) - }, - get_render_data: function(ids){ - var self = this; - var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.build_context()); - return dataset.call('get_attendee_detail',[ids, self.getParent().datarecord.id || false]); - }, - render_tag: function(data){ - this._super(data); - var self = this; - if (! self.get("effective_readonly")) { - var tag_element = self.tags.tagElements(); - _.each(data,function(value, key){ - $(tag_element[key]).find(".custom-edit").addClass(data[key][2]) - }); - } - } - }); - instance.web.form.widgets = instance.web.form.widgets.extend({ - 'many2manyattendee' : 'instance.web.form.Many2ManyAttendee', - }); - - instance.base_calendar.event = function (db, action, id, view, attendee_data) { - instance.session.session_bind(instance.session.origin).done(function () { - new instance.base_calendar.invitation(null,db,action,id,view,attendee_data).appendTo($("body").addClass('openerp')); - }); - } - - - - - -}; - diff --git a/addons/base_calendar/static/src/xml/base_calendar.xml b/addons/base_calendar/static/src/xml/base_calendar.xml deleted file mode 100644 index 42689c5515a..00000000000 --- a/addons/base_calendar/static/src/xml/base_calendar.xml +++ /dev/null @@ -1,43 +0,0 @@ - - diff --git a/addons/base_calendar/test/base_calendar_test.yml b/addons/base_calendar/test/base_calendar_test.yml deleted file mode 100644 index c95646c0fc6..00000000000 --- a/addons/base_calendar/test/base_calendar_test.yml +++ /dev/null @@ -1,66 +0,0 @@ -- - In Order to test base_calendar, I will first create One Simple Event with real data -- - !record {model: crm.meeting, id: crm_meeting_technicalpresentation0}: - class: private - date: '2011-04-30 16:00:00' - date_deadline: '2011-04-30 18:30:00' - description: The Technical Presentation will cover following topics:\n* Creating OpenERP - class\n* Views\n* Wizards\n* Workflows - duration: 2.5 - location: OpenERP S.A. - name: Technical Presentation -- - Now I will set recurrence for this event to occur monday and friday of week -- - !python {model: crm.meeting}: | - data = {'fr': 1, 'mo': 1, 'interval': 1, 'rrule_type': 'weekly', 'end_type': 'end_date', 'end_date': '2011-05-31 00:00:00', 'recurrency' : True} - self.write(cr, uid, [ref("crm_meeting_technicalpresentation0")], data) -- - In order to check that recurrent events are views successfully in calendar view, I will open calendar view of events -- - !python {model: crm.meeting}: | - self.fields_view_get(cr, uid, False, 'calendar', context) -- - In order to check that recurrent events are views successfully in calendar view, I will search for one of the recurrent event and count the number of events -- - !python {model: crm.meeting}: | - ids = self.search(cr, uid, [('date', '>=', '2011-04-30 16:00:00'), ('date', '<=', '2011-05-31 00:00:00')], context={'virtual_id': True} ) - assert len(ids) == 9, 'Wrong number of events found' -- - Now I move a virtual event, to see that a real event is well created and depending from the native recurrence -- - !python {model: crm.meeting}: | - ids = self.search(cr, uid, [('date', '>=', '2011-04-30 16:00:00'), ('date', '<=', '2011-05-31 00:00:00')], context={'virtual_id': True} ) - before = self.search(cr, uid, [('date', '>=', '2011-04-30 16:00:00'), ('date', '<=', '2011-05-31 00:00:00')], context={'virtual_id': False}) - self.write(cr, uid,[ids[1]], {'name':'New Name','recurrency' : True}, context={'virtual_id': True}) - after = self.search(cr, uid, [('date', '>=', '2011-04-30 16:00:00'), ('date', '<=', '2011-05-31 00:00:00')], context={'virtual_id': False}) - assert len(after) == len(before)+1, 'Wrong number of events found, after to have moved a virtual event' - new_id = list(set(after)-set(before))[0] - new_event = self.browse(cr,uid,new_id,context=context) - assert new_event.recurrent_id == before[0], 'Recurrent_id not correctly passed to the new event' -- - Now I will make All day event and test it -- - !record {model: crm.meeting, id: crm_meeting_alldaytestevent0}: - allday: 1 - class: confidential - date: '2011-04-30 00:00:00' - date_deadline: '2011-04-30 00:00:00' - description: 'All day technical test ' - location: School - name: All day test event -- - In order to check reminder I will first create reminder -- - !record {model: calendar.alarm, id: res_alarm_daybeforeeventstarts0}: - name: 1 Day before event starts - duration: 1 - interval: days - type: notification - -- - Now I will assign this reminder to all day event -- - !python {model: crm.meeting}: | - self.write(cr, uid, [ref("crm_meeting_alldaytestevent0")], {'alarm_ids': [(6,0,[ref("res_alarm_daybeforeeventstarts0")])]}) diff --git a/addons/base_calendar/test/test_crm_recurrent_meeting_case2.yml b/addons/base_calendar/test/test_crm_recurrent_meeting_case2.yml deleted file mode 100644 index 6b060835c72..00000000000 --- a/addons/base_calendar/test/test_crm_recurrent_meeting_case2.yml +++ /dev/null @@ -1,95 +0,0 @@ -- - In order to test recurrent meetings in OpenERP, I create meetings with different recurrency using different test cases. -- - I create a recurrent meeting with daily recurrency and fixed amount of time. -- - !record {model: crm.meeting, id: crm_meeting_testmeeting0}: - count: 5 - date: '2011-04-13 11:04:00' - date_deadline: '2011-04-13 12:04:00' - day: 0.0 - duration: 1.0 - name: Test Meeting - recurrency: true - recurrent_id: 0.0 - rrule_type: daily - sequence: 0.0 -- - I search for all the recurrent meetings. -- - !python {model: crm.meeting}: | - meeting_ids = self.search(cr, uid, [('id', 'in', [ref('crm_meeting_testmeeting0')]),('date','>=','2011-03-13'), ('date_deadline', '<=', '2011-05-13')], context={'virtual_id': True}) - assert len(meeting_ids) == 5, 'Recurrent daily meetings are not created !' -- - I create a weekly meeting till a particular end date. -- - !record {model: crm.meeting, id: crm_meeting_reviewcodewithprogrammer0}: - date: '2011-04-18 11:47:00' - date_deadline: '2011-04-18 12:47:00' - day: 0.0 - duration: 1.0 - end_date: '2011-04-30' - end_type: end_date - fr: true - mo: true - th: true - tu: true - we: true - name: Review code with programmer - recurrency: true - recurrent_id: 0.0 - rrule_type: weekly - sequence: 0.0 -- - I search for all the recurrent weekly meetings. -- - !python {model: crm.meeting}: | - meeting_ids = self.search(cr, uid, [('id', 'in', [ref('crm_meeting_reviewcodewithprogrammer0')]),('date','>=','2011-03-13'), ('date_deadline', '<=', '2011-05-13')], context={'virtual_id': True}) - assert len(meeting_ids) == 10, 'Recurrent weekly meetings are not created !' -- - I want to schedule a meeting every month for Sprint review. -- - !record {model: crm.meeting, id: crm_meeting_sprintreview0}: - count: 12 - date: '2011-04-01 12:01:00' - date_deadline: '2011-04-01 13:01:00' - day: 1 - duration: 1.0 - name: Sprint Review - recurrency: true - recurrent_id: 0.0 - rrule_type: monthly - sequence: 0.0 -- - I search for all the recurrent monthly meetings. -- - !python {model: crm.meeting}: | - meeting_ids = self.search(cr, uid, [('id', 'in', [ref('crm_meeting_sprintreview0')]),('date','>=','2011-03-01'), ('date_deadline', '<=', '2012-05-13')], context={'virtual_id': True}) - assert len(meeting_ids) == 12, 'Recurrent monthly meetings are not created !' -- - I change name of my monthly Sprint Review meeting. -- - !python {model: crm.meeting}: | - idval = '%d-%s' % (ref('crm_meeting_sprintreview0'), '20110901130100') - self.write(cr, uid, [idval], {'name': 'Sprint Review for google modules'}) -- - I check whether all the records are edited or not. -- - !python {model: crm.meeting}: | - meeting_ids = self.search(cr, uid, [('id', 'in', [ref('crm_meeting_sprintreview0')]),('date','>=','2011-03-01'), ('date_deadline', '<=', '2012-05-13')], context={'virtual_id': True}) - meetings = self.browse(cr, uid, meeting_ids, context) - for meeting in meetings: - assert meeting.name == 'Sprint Review for google modules', 'Name not changed for id: %s' %meeting.id -- - I change description of my weekly meeting Review code with programmer. -- - !python {model: crm.meeting}: | - idval = '%d-%s' % (ref('crm_meeting_sprintreview0'), '20110425124700') - self.write(cr, uid, [idval], {'description': 'Review code of the module: sync_google_calendar.'}) -- - I check whether the record is edited perfectly or not. -- - !python {model: crm.meeting}: | - meeting_ids = self.search(cr, uid, [('recurrent_id', '=', ref('crm_meeting_sprintreview0')), ('recurrent_id_date','=','2011-04-25 12:47:00')], context) - assert meeting_ids, 'Meeting is not edited !' - diff --git a/addons/calendar/__init__.py b/addons/calendar/__init__.py new file mode 100644 index 00000000000..862c470dcc7 --- /dev/null +++ b/addons/calendar/__init__.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 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 calendar +import controllers +#import res_config + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/calendar/__openerp__.py b/addons/calendar/__openerp__.py new file mode 100644 index 00000000000..a28e4f80581 --- /dev/null +++ b/addons/calendar/__openerp__.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 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': 'Calendar', + 'version': '1.0', + 'depends': ['base', 'mail', 'base_action_rule','web_calendar'], + 'summary': 'Personal & Shared Calendar', + 'description': """ +This is a full-featured calendar system. +======================================== + +It supports: +------------ + - Calendar of events + - Recurring events + +If you need to manage your meetings, you should install the CRM module. + """, + 'author': 'OpenERP SA', + 'category': 'Hidden/Dependency', + 'website': 'http://www.openerp.com', + 'demo': ['calendar_demo.xml'], + 'data': [ + 'security/calendar_security.xml', + 'security/ir.model.access.csv', + 'calendar_view.xml', + 'calendar_data.xml', + ], + 'js': [ + 'static/src/js/*.js' + ], + 'qweb': ['static/src/xml/*.xml'], + 'css': [ + 'static/src/css/calendar.css' + ], + 'test' : [ + 'test/calendar_test.yml', + 'test/test_crm_recurrent_meeting_case2.yml' + ], + 'installable': True, + 'application': True, + 'auto_install': False, + 'images': ['images/calendar1.jpeg','images/calendar2.jpeg','images/calendar3.jpeg','images/calendar4.jpeg'], +} + + diff --git a/addons/calendar/calendar.py b/addons/calendar/calendar.py new file mode 100644 index 00000000000..18a74503e96 --- /dev/null +++ b/addons/calendar/calendar.py @@ -0,0 +1,1811 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 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 hashlib +import pytz +import re +import time +import openerp +import openerp.service.report + +from datetime import datetime, timedelta, date +from dateutil import parser +from dateutil import rrule +from dateutil.relativedelta import relativedelta + +from openerp import tools, SUPERUSER_ID +from openerp.osv import fields, osv +from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT +from openerp.tools.translate import _ + + + + +def calendar_id2real_id(calendar_id=None, with_date=False): + """ + Convert a "virtual/recurring event id" (type string) into a real event id (type int). + E.g. virtual/recurring event id is 4-20091201100000, so it will return 4. + @param calendar_id: id of calendar + @param with_date: if a value is passed to this param it will return dates based on value of withdate + calendar_id + @return: real event id + """ + if calendar_id and isinstance(calendar_id, (str, unicode)): + res = calendar_id.split('-') + if len(res) >= 2: + real_id = res[0] + if with_date: + real_date = time.strftime("%Y-%m-%d %H:%M:%S", time.strptime(res[1], "%Y%m%d%H%M%S")) + start = datetime.strptime(real_date, "%Y-%m-%d %H:%M:%S") + end = start + timedelta(hours=with_date) + return (int(real_id), real_date, end.strftime("%Y-%m-%d %H:%M:%S")) + return int(real_id) + + return calendar_id and int(calendar_id) or calendar_id + +def get_real_ids(ids): + if isinstance(ids, (str, int, long)): + return calendar_id2real_id(ids) + + if isinstance(ids, (list, tuple)): + res = [] + for id in ids: + res.append(calendar_id2real_id(id)) + return res + +class calendar_attendee(osv.osv): + """ + Calendar Attendee Information + """ + _name = 'calendar.attendee' + _description = 'Attendee information' + + def _compute_data(self, cr, uid, ids, name, arg, context=None): + """ + Compute data on function fields for attendee values. + @param ids: list of calendar attendee's IDs + @param name: name of field + @return: dictionary of form {id: {'field Name': value'}} + """ + name = name[0] + result = {} + for attdata in self.browse(cr, uid, ids, context=context): + id = attdata.id + result[id] = {} + + if name == 'cn': + if attdata.partner_id: + result[id][name] = attdata.partner_id.name or False + else: + result[id][name] = attdata.email or '' + + if name == 'event_date': + result[id][name] = attdata.event_id.date + + if name == 'event_end_date': + result[id][name] = attdata.event_id.date_deadline + + return result + + _columns = { + 'state': fields.selection([('needsAction', 'Needs Action'),('tentative', 'Uncertain'),('declined', 'Declined'),('accepted', 'Accepted')], 'Status', readonly=True, help="Status of the attendee's participation"), + 'cn': fields.function(_compute_data, string='Common name', type="char", size=124, multi='cn', store=True), + 'dir': fields.char('URI Reference', size=124, help="Reference to the URI that points to the directory information corresponding to the attendee."), + 'partner_id': fields.many2one('res.partner', 'Contact',readonly="True"), + 'email': fields.char('Email', size=124, help="Email of Invited Person"), + 'event_date': fields.function(_compute_data, string='Event Date', type="datetime", multi='event_date'), + 'event_end_date': fields.function(_compute_data, string='Event End Date', type="datetime", multi='event_end_date'), + 'availability': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Free/Busy', readonly="True"), + 'access_token':fields.char('Invitation Token', size=256), + 'event_id': fields.many2one('crm.meeting','Meeting linked'), + } + _defaults = { + 'state': 'needsAction', + } + + def copy(self, cr, uid, id, default=None, context=None): + raise osv.except_osv(_('Warning!'), _('You cannot duplicate a calendar attendee.')) + + def onchange_partner_id(self, cr, uid, ids, partner_id,context=None): + """ + Make entry on email and availability on change of partner_id field. + @param partner_id: changed value of partner id + """ + if not partner_id: + return {'value': {'email': ''}} + partner = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context) + return {'value': {'email': partner.email}} + + def get_ics_file(self, cr, uid, event_obj, context=None): + """ + Returns iCalendar file for the event invitation. + @param event_obj: event object (browse record) + @return: .ics file content + """ + res = None + def ics_datetime(idate, short=False): + if idate: + #returns the datetime as UTC, because it is stored as it in the database + return datetime.strptime(idate.split('.')[0], '%Y-%m-%d %H:%M:%S').replace(tzinfo=pytz.timezone('UTC')) + #return datetime.strptime(idate, DEFAULT_SERVER_DATETIME_FORMAT).replace(tzinfo=pytz.timezone('UTC')) + + return False + + try: + # FIXME: why isn't this in CalDAV? + import vobject + except ImportError: + return res + + cal = vobject.iCalendar() + event = cal.add('vevent') + if not event_obj.date_deadline or not event_obj.date: + raise osv.except_osv(_('Warning!'),_("First you have to specify the date of the invitation.")) + event.add('created').value = ics_datetime(time.strftime('%Y-%m-%d %H:%M:%S')) + event.add('dtstart').value = ics_datetime(event_obj.date) + event.add('dtend').value = ics_datetime(event_obj.date_deadline) + event.add('summary').value = event_obj.name + if event_obj.description: + event.add('description').value = event_obj.description + if event_obj.location: + event.add('location').value = event_obj.location + if event_obj.rrule: + event.add('rrule').value = event_obj.rrule + + if event_obj.alarm_ids: + for alarm in event_obj.alarm_ids: + # computes alarm data + valarm = event.add('valarm') + # Compute trigger data + interval = alarm.interval + occurs = 'before' + duration = alarm.duration + + trigger = valarm.add('TRIGGER') + trigger.params['related'] = ["START"] + if interval == 'days': + delta = timedelta(days=duration) + elif interval == 'hours': + delta = timedelta(hours=duration) + elif interval == 'minutes': + delta = timedelta(minutes=duration) + + trigger.value = delta + # Compute other details + valarm.add('DESCRIPTION').value = alarm.name or 'OpenERP' + + for attendee in event_obj.attendee_ids: + attendee_add = event.add('attendee') + attendee_add.value = 'MAILTO:' + (attendee.email or '') + res = cal.serialize() + return res + + def _send_mail_to_attendees(self, cr, uid, ids, email_from=tools.config.get('email_from', False), template_xmlid='crm_email_template_meeting_invitation', context=None): + """ + Send mail for event invitation to event attendees. + @param email_from: email address for user sending the mail + """ + res = False + + mail_ids = [] + data_pool = self.pool.get('ir.model.data') + mailmess_pool = self.pool.get('mail.message') + mail_pool = self.pool.get('mail.mail') + template_pool = self.pool.get('email.template') + local_context = context.copy() + color = { + 'needsAction' : 'grey', + 'accepted' :'green', + 'tentative' :'#FFFF00', + 'declined':'red' + } + + if not isinstance(ids, (tuple, list)): + ids = [ids] + + for attendee in self.browse(cr, uid, ids, context=context): + dummy,template_id = data_pool.get_object_reference(cr, uid, 'calendar',template_xmlid) + dummy,act_id = data_pool.get_object_reference(cr, uid, 'calendar', "view_crm_meeting_calendar") + + if attendee.email and email_from: + ics_file = self.get_ics_file(cr, uid, attendee.event_id, context=context) + local_context['color'] = color + local_context['action_id'] = self.pool.get('ir.actions.act_window').search(cr, uid, [('view_id','=',act_id)], context=context)[0] + local_context['dbname'] = cr.dbname + local_context['base_url'] = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url', default='http://localhost:8069', context=context) + mail_id = template_pool.send_mail(cr, uid, template_id, attendee.id, context=local_context) + + + vals = {} + if ics_file: + vals['attachment_ids'] = [(0,0,{'name': 'invitation.ics', + 'datas_fname': 'invitation.ics', + 'datas': str(ics_file).encode('base64')})] + vals['model'] = None #We don't want to have the mail in the tchatter while in queue! + the_mailmess = mail_pool.browse(cr,uid,mail_id,context=context).mail_message_id + mailmess_pool.write(cr,uid,[the_mailmess.id],vals,context=context) + mail_ids.append(mail_id) + + +# +# if not attendee.partner_id.opt_out: +# if 'partner_to' in vals: +# del vals['partner_to'] #hack between mail.mail and template.mail -> tde + + if mail_ids: + try: + res = mail_pool.send(cr, uid, mail_ids, context=context) + except Exception as e: + print e + + return res + + def ORI_send_mail_to_attendees(self, cr, uid, ids, email_from=tools.config.get('email_from', False), template_xmlid='crm_email_template_meeting_invitation', context=None): + """ + Send mail for event invitation to event attendees. + @param email_from: email address for user sending the mail + """ + res = False + + mail_id = [] + data_pool = self.pool.get('ir.model.data') + mail_pool = self.pool.get('mail.mail') + template_pool = self.pool.get('email.template') + local_context = context.copy() + color = { + 'needsAction' : 'grey', + 'accepted' :'green', + 'tentative' :'#FFFF00', + 'declined':'red' + } + + if not isinstance(ids, (tuple, list)): + ids = [ids] + + for attendee in self.browse(cr, uid, ids, context=context): + dummy,template_id = data_pool.get_object_reference(cr, uid, 'calendar', template_xmlid) + dummy,act_id = data_pool.get_object_reference(cr, uid, 'calendar', "view_crm_meeting_calendar") + body = template_pool.browse(cr, uid, template_id, context=context).body_html + if attendee.email and email_from: + ics_file = self.get_ics_file(cr, uid, attendee.event_id, context=context) + local_context['att_obj'] = attendee + local_context['color'] = color + local_context['action_id'] = self.pool.get('ir.actions.act_window').search(cr, uid, [('view_id','=',act_id)], context=context)[0] + local_context['dbname'] = cr.dbname + local_context['base_url'] = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url', default='http://localhost:8069', context=context) + vals = template_pool.generate_email(cr, uid, template_id, attendee.event_id.id, context=local_context) + if ics_file: + vals['attachment_ids'] = [(0,0,{'name': 'invitation.ics', + 'datas_fname': 'invitation.ics', + 'datas': str(ics_file).encode('base64')})] + + vals['model'] = None #We don't want to have the mail in the tchatter while in queue! + vals['auto_delete'] = True #We don't need mail after it has been sended ! + + if (vals['email_to']== attendee.partner_id.email): + vals['email_to'] = '' + vals['recipient_ids'] = [(4,attendee.partner_id.id),] + + if not attendee.partner_id.opt_out: + if 'partner_to' in vals: + del vals['partner_to'] #hack between mail.mail and template.mail -> tde + mail_id.append(mail_pool.create(cr, SUPERUSER_ID, vals, context=context)) + + + if mail_id: + try: + res = mail_pool.send(cr, uid, mail_id, context=context) + except Exception as e: + print e + + return res + + def onchange_user_id(self, cr, uid, ids, user_id, *args, **argv): + """ + Make entry on email and availbility on change of user_id field. + @param ids: list of attendee's IDs + @param user_id: changed value of User id + @return: dictionary of values which put value in email and availability fields + """ + if not user_id: + return {'value': {'email': ''}} + + usr_obj = self.pool.get('res.users').browse(cr, uid, user_id, *args) + return {'value': {'email': user.email, 'availability':user.availability}} + + def do_tentative(self, cr, uid, ids, context=None, *args): + """ + Makes event invitation as Tentative. + @param ids: list of attendee's IDs + """ + return self.write(cr, uid, ids, {'state': 'tentative'}, context) + + def do_accept(self, cr, uid, ids, context=None, *args): + """ + Marks event invitation as Accepted. + @param ids: list of attendee's IDs + """ + if context is None: + context = {} + meeting_obj = self.pool.get('crm.meeting') + res = self.write(cr, uid, ids, {'state': 'accepted'}, context) + for attendee in self.browse(cr, uid, ids, context=context): + meeting_obj.message_post(cr, uid, attendee.event_id.id, body=_(("%s has accepted invitation") % (attendee.cn)),subtype="calendar.subtype_invitation", context=context) + + return res + + def do_decline(self, cr, uid, ids, context=None, *args): + """ + Marks event invitation as Declined. + @param ids: list of calendar attendee's IDs + """ + if context is None: + context = {} + meeting_obj = self.pool.get('crm.meeting') + res = self.write(cr, uid, ids, {'state': 'declined'}, context) + for attendee in self.browse(cr, uid, ids, context=context): + meeting_obj.message_post(cr, uid, attendee.event_id.id, body=_(("%s has declined invitation") % (attendee.cn)),subtype="calendar.subtype_invitation", context=context) + return res + + def create(self, cr, uid, vals, context=None): + if context is None: + context = {} + if not vals.get("email") and vals.get("cn"): + cnval = vals.get("cn").split(':') + email = filter(lambda x:x.__contains__('@'), cnval) + vals['email'] = email and email[0] or '' + vals['cn'] = vals.get("cn") + res = super(calendar_attendee, self).create(cr, uid, vals, context=context) + return res + + +class res_partner(osv.osv): + _inherit = 'res.partner' + + _columns = { + 'cal_last_notif': fields.datetime('Last Notification from base Calendar'), + } + + def get_attendee_detail(self, cr, uid, ids, meeting_id, context=None): + datas = [] + meeting = False + if meeting_id: + meeting = self.pool.get('crm.meeting').browse(cr, uid, get_real_ids(meeting_id),context) + for partner in self.browse(cr, uid, ids, context=context): + data = self.name_get(cr, uid, [partner.id], context)[0] + if meeting: + for attendee in meeting.attendee_ids: + if attendee.partner_id.id == partner.id: + data = (data[0], data[1], attendee.state) + datas.append(data) + return datas + + def update_cal_last_event(self,cr,uid,context=None): + partner = self.pool.get('res.users').browse(cr,uid,uid,context=context).partner_id; + self.write(cr,uid,partner.id,{'cal_last_notif' : datetime.now() } ,context=context) + return "OK" + + +class calendar_alarm_manager(osv.osv): + _name = 'calendar.alarm_manager' + + def get_next_potential_limit_alarm(self,cr,uid,seconds, notif=True, mail=True, partner_id=None, context=None): + res = {} + base_request = """ + SELECT + crm.id, + crm.date - interval '1' minute * calcul_delta.max_delta AS first_alarm, + CASE + WHEN crm.recurrency THEN crm.end_date - interval '1' minute * calcul_delta.min_delta + ELSE crm.date_deadline - interval '1' minute * calcul_delta.min_delta + END as last_alarm, + crm.date as first_event_date, + CASE + WHEN crm.recurrency THEN crm.end_date + ELSE crm.date_deadline + END as last_event_date, + calcul_delta.min_delta, + calcul_delta.max_delta, + crm.rrule + FROM + crm_meeting AS crm + RIGHT JOIN + ( + SELECT + rel.crm_meeting_id, max(alarm.duration_minutes) AS max_delta,min(alarm.duration_minutes) AS min_delta + FROM + calendar_alarm_crm_meeting_rel AS rel + LEFT JOIN calendar_alarm AS alarm ON alarm.id = rel.calendar_alarm_id + WHERE alarm.type in %s + GROUP BY rel.crm_meeting_id + ) AS calcul_delta ON calcul_delta.crm_meeting_id = crm.id + """ + + filter_user = """ + LEFT JOIN crm_meeting_res_partner_rel AS part_rel ON part_rel.crm_meeting_id = crm.id + AND part_rel.res_partner_id = %s + """ + + + #Add filter on type + type_to_read = () #('dummy',) + if notif: + type_to_read += ('notification',) + if mail: + type_to_read += ('email',) + + tuple_params = (type_to_read,) + + + #ADD FILTER ON PARTNER_ID + if partner_id: + base_request += filter_user + tuple_params += (partner_id, ) + + #Add filter on hours + tuple_params += (seconds,seconds,) + + cr.execute(""" + SELECT + * + FROM ( + """ + + base_request + + """ + ) AS ALL_EVENTS + WHERE + ALL_EVENTS.first_alarm < (now() at time zone 'utc' + interval '%s' second ) + AND ALL_EVENTS.last_alarm > (now() at time zone 'utc' - interval '%s' second ) + """,tuple_params) + + for event_id, first_alarm,last_alarm,first_meeting,last_meeting,min_duration,max_duration,rrule in cr.fetchall(): + res[event_id] = {} + res[event_id]['event_id'] = event_id + res[event_id]['first_alarm'] = first_alarm + res[event_id]['last_alarm'] = last_alarm + res[event_id]['first_meeting'] = first_meeting + res[event_id]['last_meeting'] = last_meeting + res[event_id]['min_duration'] = min_duration + res[event_id]['max_duration'] = max_duration + res[event_id]['rrule'] = rrule + + return res + + def do_check_alarm_for_one_date(self,cr,uid,one_date,event, event_maxdelta,in_the_next_X_seconds,after=False,notif=True, mail=True,context=None): + res = [] + alarm_type = [] + + if notif: + alarm_type.append('notification') + if mail: + alarm_type.append('email') + + if one_date - timedelta(minutes=event_maxdelta) < datetime.now() + timedelta(seconds=in_the_next_X_seconds): #if an alarm is possible for this date + print "ALARMIDS = ", event.alarm_ids + for alarm in event.alarm_ids: + print "type = ",alarm.type + print "after = ",after + print "cond 0 =", alarm.type in alarm_type + print "cond 1 = ", one_date - timedelta(minutes=alarm.duration_minutes), " < ", datetime.now() + timedelta(seconds=in_the_next_X_seconds) + #print "cond 2", one_date - timedelta(minutes=alarm.duration_minutes), " > ",datetime.strptime(after.split('.')[0], "%Y-%m-%d %H:%M:%S") + + + if alarm.type in alarm_type and \ + one_date - timedelta(minutes=alarm.duration_minutes) < datetime.now() + timedelta(seconds=in_the_next_X_seconds) and \ + (not after or one_date - timedelta(minutes=alarm.duration_minutes) > datetime.strptime(after.split('.')[0], "%Y-%m-%d %H:%M:%S")): + alert = { + 'alarm_id' : alarm.id, + 'event_id' : event.id, + 'notify_at' : one_date - timedelta(minutes=alarm.duration_minutes), + } + print "ALERT ADDED : ", alert + res.append(alert) + else: + print "Not in condition..." + + return res + + def do_run_scheduler_mail(self,cr,uid,context=None): + cron = self.pool.get('ir.cron').search(cr,uid,[('model','ilike',self._name)],context=context) + if cron and len(cron) == 1: + cron = self.pool.get('ir.cron').browse(cr,uid,cron[0],context=context) + else: + raise ("Cron for " + self._name + " not identified :( !") + + if cron.interval_type=="weeks": + cron_interval = cron.interval_number * 7 * 24 * 60 * 60 + elif cron.interval_type=="days": + cron_interval = cron.interval_number * 24 * 60 * 60 + elif cron.interval_type=="hours": + cron_interval = cron.interval_number * 60 * 60 + elif cron.interval_type=="minutes": + cron_interval = cron.interval_number * 60 + elif cron.interval_type=="seconds": + cron_interval = cron.interval_number + + if not cron_interval: + raise ("Cron delay for " + self._name + " not calculated :( !") + + + all_events = self.get_next_potential_limit_alarm(cr,uid,cron_interval,notif=False,context=context) + for event in all_events: #.values() + max_delta = all_events[event]['max_duration']; + curEvent = self.pool.get('crm.meeting').browse(cr,uid,event,context=context) + if curEvent.recurrency: + bFound = False + LastFound = False + for one_date in self.pool.get('crm.meeting').get_recurrent_date_by_event(cr,uid,curEvent, context=context) : + in_date_format = datetime.strptime(one_date, '%Y-%m-%d %H:%M:%S'); + LastFound = self.do_check_alarm_for_one_date(cr,uid,in_date_format,curEvent,max_delta,cron_interval,notif=False,context=context) + if LastFound: + for alert in LastFound: + self.do_mail_reminder(cr,uid,alert,context=context) + + if not bFound: #if it's the first alarm for this recurrent event + bFound = True + if bFound and not LastFound: #if the precendent event had alarm but not this one, we can stop the search fot this event + break + else: + in_date_format = datetime.strptime(curEvent.date, '%Y-%m-%d %H:%M:%S'); + LastFound = self.do_check_alarm_for_one_date(cr,uid,in_date_format,curEvent,max_delta,cron_interval,notif=False,context=context) + if LastFound: + for alert in LastFound: + self.do_mail_reminder(cr,uid,alert,context=context) + + #Purge all done + + def get_next_event(self,cr,uid,context=None): + ajax_check_every_seconds = 300 + + partner = self.pool.get('res.users').browse(cr,uid,uid,context=context).partner_id; + + all_notif = [] + + all_events = self.get_next_potential_limit_alarm(cr,uid,ajax_check_every_seconds,partner_id=partner.id,mail=False,context=context) + print all_events + + for event in all_events: #.values() + max_delta = all_events[event]['max_duration']; + curEvent = self.pool.get('crm.meeting').browse(cr,uid,event,context=context) + if curEvent.recurrency: + bFound = False + LastFound = False + for one_date in self.pool.get("crm.meeting").get_recurrent_date_by_event(cr,uid,curEvent, context=context) : + in_date_format = datetime.strptime(one_date, '%Y-%m-%d %H:%M:%S'); + LastFound = self.do_check_alarm_for_one_date(cr,uid,in_date_format,curEvent,max_delta,ajax_check_every_seconds,after=partner.cal_last_notif,mail=False,context=context) + if LastFound: + for alert in LastFound: + all_notif.append(self.do_notif_reminder(cr,uid,alert,context=context)) + if not bFound: #if it's the first alarm for this recurrent event + bFound = True + if bFound and not LastFound: #if the precendent event had alarm but not this one, we can stop the search fot this event + break + else: + in_date_format = datetime.strptime(curEvent.date, '%Y-%m-%d %H:%M:%S'); + LastFound = self.do_check_alarm_for_one_date(cr,uid,in_date_format,curEvent,max_delta,ajax_check_every_seconds,partner.cal_last_notif,mail=False,context=context) + if LastFound: + for alert in LastFound: + all_notif.append(self.do_notif_reminder(cr,uid,alert,context=context)) + + return all_notif + + def do_mail_reminder(self,cr,uid,alert,context=None): + if context is None: + context = {} + res = False + + event = self.pool.get("crm.meeting").browse(cr,uid,alert['event_id'],context=context) + alarm = self.pool.get("calendar.alarm").browse(cr,uid,alert['alarm_id'],context=context) + + if alarm.type == 'email': + mail_ids = [] + data_pool = self.pool.get('ir.model.data') + mail_pool = self.pool.get('mail.mail') + template_pool = self.pool.get('email.template') + local_context = context and context.copy() or {} + color = { + 'needsAction' : 'grey', + 'accepted' :'green', + 'tentative' :'#FFFF00', + 'declined':'red' + } + + for attendee in event.attendee_ids: + dummy,template_id = data_pool.get_object_reference(cr, uid, 'calendar', 'crm_email_template_meeting_reminder') + dummy,act_id = data_pool.get_object_reference(cr, uid, 'calendar', "view_crm_meeting_calendar") + body = template_pool.browse(cr, uid, template_id, context=context).body_html + + #mail_from = tools.config.get('email_from',event.user_id.email) + if attendee.email: + local_context['att_obj'] = attendee + local_context['color'] = color + local_context['action_id'] = self.pool.get('ir.actions.act_window').search(cr, uid, [('view_id','=',act_id)], context=context)[0] + local_context['dbname'] = cr.dbname + local_context['base_url'] = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url', default='http://localhost:8069', context=context) + vals = template_pool.generate_email(cr, uid, template_id, attendee.event_id.id, context=local_context) + + vals['model'] = None #We don't want to have the mail in the tchatter while in queue! + vals['auto_delete'] = True #We don't need mail after it has been sended ! + + if (vals['email_to']== attendee.partner_id.email): + vals['email_to'] = '' + vals['recipient_ids'] = [(4,attendee.partner_id.id),] + + if not attendee.partner_id.opt_out: + if 'partner_to' in vals: + del vals['partner_to'] #hack between mail.mail and template.mail -> tde + mail_ids.append(mail_pool.create(cr, uid, vals, context=local_context)) + if mail_ids: + try: + res = mail_pool.send(cr, uid, mail_ids, context=local_context) + except Exception as e: + print e + + return res + + def do_notif_reminder(self,cr,uid,alert,context=None): + alarm = self.pool.get("calendar.alarm").browse(cr,uid,alert['alarm_id'],context=context) + event = self.pool.get("crm.meeting").browse(cr,uid,alert['event_id'],context=context) + + if alarm.type == 'notification': + mail_id = [] + mail_pool = self.pool.get('mail.mail') + data_pool = self.pool.get('ir.model.data') + template_pool = self.pool.get('email.template') + local_context = context.copy() + message = event.display_time + + delta = alert['notify_at'] - datetime.now() + delta = delta.seconds + delta.days * 3600 * 24 + + return { + 'event_id' : event.id, + 'title' : event.name, + 'message' : message, + 'timer' : delta, #Now - event_date - alaram.duration_minute + 'notify_at' : alert['notify_at'].strftime("%Y-%m-%d %H:%M:%S"), #Now - event_date - alaram.duration_minute + } + + + +class calendar_alarm(osv.osv): + _name = 'calendar.alarm' + _description = 'Event alarm' + + def _get_duration(self, cr, uid, ids, field_name, arg, context=None): + res = {} + for alarm in self.browse(cr, uid, ids, context=context): + if alarm.interval == "minutes": + res[alarm.id] = alarm.duration + elif alarm.interval == "hours": + res[alarm.id] = alarm.duration * 60 + elif alarm.interval == "days": + res[alarm.id] = alarm.duration * 60 * 24 + else: + res[alarm.id] = 0 + return res + + _columns = { + 'name':fields.char('Name', size=256, required=True), # fields function + 'type': fields.selection([('notification', 'Notification'), ('email', 'Email')], 'Type', required=True), + 'duration': fields.integer('Amount', required=True), + 'interval': fields.selection([('minutes', 'Minutes'), ('hours', 'Hours'), ('days', 'Days')], 'Unit', required=True), + + 'duration_minutes': fields.function(_get_duration, type='integer', string='duration_minutes',store=True), + } + + _defaults = { + 'type': 'notification', + 'duration': 1, + 'interval': 'hours', + } + + +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], calendar_id2real_id(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=None, res_id_req=False, without_user=True, key2_req=True): + if context is None: + context = {} + new_model = [] + for data in models: + if type(data) in (list, tuple): + new_model.append((data[0], calendar_id2real_id(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) + + +class ir_model(osv.osv): + + _inherit = 'ir.model' + + def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'): + + new_ids = isinstance(ids, (str, int, long)) and [ids] or ids + if context is None: + context = {} + data = super(ir_model, self).read(cr, uid, new_ids, fields=fields, \ + context=context, load=load) + if data: + for val in data: + val['id'] = calendar_id2real_id(val['id']) + return isinstance(ids, (str, int, long)) and data[0] or data + + +original_exp_report = openerp.service.report.exp_report + +def exp_report(db, uid, object, ids, data=None, context=None): + """ + Export Report + """ + if object == 'printscreen.list': + original_exp_report(db, uid, object, ids, data, context) + new_ids = [] + for id in ids: + new_ids.append(calendar_id2real_id(id)) + if data.get('id', False): + data['id'] = calendar_id2real_id(data['id']) + return original_exp_report(db, uid, object, new_ids, data, context) + +openerp.service.report.exp_report = exp_report + + +class crm_meeting_type(osv.Model): + _name = 'crm.meeting.type' + _description = 'Meeting Type' + _columns = { + 'name': fields.char('Name', size=64, required=True, translate=True), + } + + +class crm_meeting(osv.Model): + """ Model for CRM meetings """ + _name = 'crm.meeting' + _description = "Meeting" + _order = "id desc" + _inherit = ["mail.thread", "ir.needaction_mixin"] + + def do_run_scheduler(self,cr,uid,id,context=None): + self.pool.get('calendar.alarm_manager').do_run_scheduler(cr,uid,context=context) + + def get_recurrent_date_by_event(self,cr,uid,event,context=None): + """Get recurrent dates based on Rule string and all event where recurrent_id is child + """ + + def todate(date): + val = parser.parse(''.join((re.compile('\d')).findall(date))) + ## Dates are localized to saved timezone if any, else current timezone. + if not val.tzinfo: + val = pytz.UTC.localize(val) + return val.astimezone(timezone) + + timezone = pytz.timezone(event.vtimezone or context.get('tz') or 'UTC') + + startdate = pytz.UTC.localize(datetime.strptime(event.date, "%Y-%m-%d %H:%M:%S")) #Add "+hh:mm" timezone + if not startdate: + startdate = datetime.now() + + ## Convert the start date to saved timezone (or context tz) as it'll + ## define the correct hour/day asked by the user to repeat for recurrence. + startdate = startdate.astimezone(timezone) #transform "+hh:mm" timezone + + rset1 = rrule.rrulestr(str(event.rrule), dtstart=startdate, forceset=True) + + ids_depending = self.search(cr,uid,[('recurrent_id','=',event.id),'|',('active','=',False),('active','=',True)],context=context) + all_events = self.browse(cr,uid,ids_depending,context=context) + + for ev in all_events: + rset1._exdate.append(todate(ev.recurrent_id_date)) + #rset1.rdate(pytz.UTC.localize(datetime.strptime(ev.date, "%Y-%m-%d %H:%M:%S"))) + + return [d.astimezone(pytz.UTC) for d in rset1] + + + def _get_recurrency_end_date(self, data, context=None): + if data.get('recurrency') and data.get('end_type') in ('count', unicode('count')): + data_date_deadline = datetime.strptime(data.get('date_deadline'), '%Y-%m-%d %H:%M:%S') + if data.get('rrule_type') in ('daily', unicode('count')): + rel_date = relativedelta(days=data.get('count')+1) + elif data.get('rrule_type') in ('weekly', unicode('weekly')): + rel_date = relativedelta(days=(data.get('count')+1)*7) + elif data.get('rrule_type') in ('monthly', unicode('monthly')): + rel_date = relativedelta(months=data.get('count')+1) + elif data.get('rrule_type') in ('yearly', unicode('yearly')): + rel_date = relativedelta(years=data.get('count')+1) + end_date = data_date_deadline + rel_date + else: + end_date = data.get('end_date') + return end_date + + def _find_my_attendee(self, cr, uid, meeting_ids, context=None): + """ + Return the first attendee where the user connected has been invited from all the meeting_ids in parameters + """ + user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + for meeting_id in meeting_ids: + for attendee in self.browse(cr,uid,meeting_id,context).attendee_ids: + if user.partner_id.id == attendee.partner_id.id: + return attendee + return False + + def _get_display_time(self, cr, uid, meeting_id, context=None): + """ + Return date and time (from to from) based on duration with timezone in string : + eg. + 1) if user add duration for 2 hours, return : August-23-2013 at ( 04-30 To 06-30) (Europe/Brussels) + 2) if event all day ,return : AllDay, July-31-2013 + """ + if context is None: + context = {} + + tz = context.get('tz', False) + if not tz: #tz can have a value False, so dont do it in the default value of get ! + tz = pytz.timezone('UTC') + + meeting = self.browse(cr, uid, meeting_id, context=context) + date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(meeting.date, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context) + date_deadline = fields.datetime.context_timestamp(cr, uid, datetime.strptime(meeting.date_deadline, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context) + event_date = date.strftime('%B-%d-%Y') + display_time = date.strftime('%H-%M') + if meeting.allday: + time = _("AllDay , %s") % (event_date) + elif meeting.duration < 24: + duration = date + timedelta(hours= meeting.duration) + time = ("%s at ( %s To %s) (%s)") % (event_date, display_time, duration.strftime('%H-%M'), tz) + else : + time = ("%s at %s To\n %s at %s (%s)") % (event_date, display_time, date_deadline.strftime('%B-%d-%Y'), date_deadline.strftime('%H-%M'), tz) + return time + + def _compute(self, cr, uid, ids, fields, arg, context=None): + res = {} + for meeting_id in ids: + res[meeting_id] = {} + attendee = self._find_my_attendee(cr, uid, [meeting_id], context) + for field in fields: + if field == 'is_attendee': + res[meeting_id][field] = True if attendee else False + elif field == 'attendee_status': + res[meeting_id][field] = attendee.state if attendee else 'needsAction' + elif field == 'display_time': + res[meeting_id][field] = self._get_display_time(cr, uid, meeting_id, context=context) + return res + + def _get_rulestring(self, cr, uid, ids, name, arg, context=None): + """ + Gets Recurrence rule string according to value type RECUR of iCalendar from the values given. + @return: dictionary of rrule value. + """ + + result = {} + if not isinstance(ids, list): + ids = [ids] + + for id in ids: + #read these fields as SUPERUSER because if the record is private a normal search could return False and raise an error + data = self.browse(cr, SUPERUSER_ID, id, context=context) + + if data.interval and data.interval < 0: + raise osv.except_osv(_('Warning!'), _('Interval cannot be negative.')) + if data.count and data.count <= 0: + raise osv.except_osv(_('Warning!'), _('Count cannot be negative or 0.')) + + data = self.read(cr, uid, id, ['id','byday','recurrency', 'month_list','end_date', 'rrule_type', 'month_by', 'interval', 'count', 'end_type', 'mo', 'tu', 'we', 'th', 'fr', 'sa', 'su', 'day', 'week_list' ], context=context) + event = data['id'] + if data['recurrency']: + result[event] = self.compute_rule_string(data) + else: + result[event] = "" + return result + + def _rrule_write(self, cr, uid, ids, field_name, field_value, args, context=None): + if not isinstance(ids, list): + ids = [ids] + data = self._get_empty_rrule_data() + if field_value: + + data['recurrency'] = True + for event in self.browse(cr, uid, ids, context=context): + rdate = event.date + update_data = self._parse_rrule(field_value, dict(data), rdate) + data.update(update_data) + self.write(cr, uid, ids, data, context=context) + return True + + def _tz_get(self, cr, uid, context=None): + return [(x.lower(), x) for x in pytz.all_timezones] + + _track = { + 'location': { + 'calendar.subtype_invitation': lambda self, cr, uid, obj, ctx=None: True, + }, + 'date': { + 'calendar.subtype_invitation': lambda self, cr, uid, obj, ctx=None: True, + }, + } + _columns = { +# 'create_date': fields.datetime('Creation Date', readonly=True), +# 'write_date': fields.datetime('Write Date', readonly=True), + 'id': fields.integer('ID', readonly=True), + + + 'state': fields.selection([('draft', 'Unconfirmed'), ('open', 'Confirmed')], string='Status', size=16, readonly=True, track_visibility='onchange'), + + 'name': fields.char('Meeting Subject', size=128, required=True, states={'done': [('readonly', True)]}), + 'is_attendee': fields.function(_compute, string='Attendee', type="boolean", multi='attendee'), + 'attendee_status': fields.function(_compute, string='Attendee Status', type="selection", multi='attendee'), + 'display_time': fields.function(_compute, string='Event Time', type="char", multi='attendee'), + + 'sequence': fields.integer('Sequence'), + + 'date': fields.datetime('Date', states={'done': [('readonly', True)]}, required=True,track_visibility='onchange'), + 'date_deadline': fields.datetime('End Date', states={'done': [('readonly', True)]}, required=True,), + + 'duration': fields.float('Duration', states={'done': [('readonly', True)]}), + 'description': fields.text('Description', states={'done': [('readonly', True)]}), + 'class': fields.selection([('public', 'Public'), ('private', 'Private'), ('confidential', 'Public for Employees')], 'Privacy', states={'done': [('readonly', True)]}), + 'location': fields.char('Location', size=264, help="Location of Event", track_visibility='onchange', states={'done': [('readonly', True)]}), + 'show_as': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Show Time as', states={'done': [('readonly', True)]}), + + #'state': fields.selection([('tentative', 'Uncertain'),('cancelled', 'Cancelled'),('confirmed', 'Confirmed'),],'Status', readonly=True, track_visibility='onchange'), + + #FIELD FOR RECURRENCY + #'exdate': fields.text('Exception Date/Times', help="This property defines the list of date/time exceptions for a recurring calendar component."), + 'rrule': fields.function(_get_rulestring, type='char', size=124, fnct_inv=_rrule_write, store=True, string='Recurrent Rule'), + 'rrule_type': fields.selection([('daily', 'Day(s)'),('weekly', 'Week(s)'),('monthly', 'Month(s)'),('yearly', 'Year(s)')], 'Recurrency', states={'done': [('readonly', True)]}, help="Let the event automatically repeat at that interval"), + 'recurrency': fields.boolean('Recurrent', help="Recurrent Meeting"), + 'recurrent_id': fields.integer('Recurrent ID'), + 'recurrent_id_date': fields.datetime('Recurrent ID date'), + #'recurrence_end_date': fields.function(_get_recurrence_end_date, type='datetime', store=True, string='Recurrence end date',priority=30), + 'vtimezone': fields.selection(_tz_get, size=64, string='Timezone'), + 'end_type' : fields.selection([('count', 'Number of repetitions'), ('end_date','End date')], 'Recurrence Termination'), + 'interval': fields.integer('Repeat Every', help="Repeat every (Days/Week/Month/Year)"), + 'count': fields.integer('Repeat', help="Repeat x times"), + 'mo': fields.boolean('Mon'), + 'tu': fields.boolean('Tue'), + 'we': fields.boolean('Wed'), + 'th': fields.boolean('Thu'), + 'fr': fields.boolean('Fri'), + 'sa': fields.boolean('Sat'), + 'su': fields.boolean('Sun'), + 'month_by': fields.selection([('date', 'Date of month'),('day', 'Day of month')], 'Option'), + 'day': fields.integer('Date of month'), + 'week_list': fields.selection([('MO', 'Monday'),('TU', 'Tuesday'),('WE', 'Wednesday'),('TH', 'Thursday'),('FR', 'Friday'),('SA', 'Saturday'),('SU', 'Sunday')], 'Weekday'), + 'byday': fields.selection([('1', 'First'),('2', 'Second'),('3', 'Third'),('4', 'Fourth'),('5', 'Fifth'),('-1', 'Last')], 'By day'), + 'end_date': fields.date('Repeat Until'), + 'allday': fields.boolean('All Day', states={'done': [('readonly', True)]}), + + 'user_id': fields.many2one('res.users', 'Responsible', states={'done': [('readonly', True)]}), + 'color_partner_id': fields.related('user_id','partner_id','id',type="int",string="colorize",store=False), #Color of creator + 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the event alarm information without removing it."), + + 'categ_ids': fields.many2many('crm.meeting.type', 'meeting_category_rel', 'event_id', 'type_id', 'Tags'), + 'attendee_ids': fields.one2many('calendar.attendee', 'event_id', 'Attendees', ondelete='cascade'), + #'attendee_ids': fields.many2many('calendar.attendee', 'crmmeeting_attendee_rel', 'crmmeeting_id', 'attendee_id', 'Attendees', ondelete='cascade'), + 'partner_ids': fields.many2many('res.partner', string='Attendees', states={'done': [('readonly', True)]}), + 'alarm_ids': fields.many2many('calendar.alarm', string='Reminders',ondelete="restrict"), + } + _defaults = { + 'end_type': 'count', + 'count': 1, + 'rrule_type': False, + 'state': 'draft', + 'class': 'public', + 'show_as': 'busy', + 'month_by': 'date', + 'interval': 1, + 'active': 1, + 'user_id': lambda self, cr, uid, ctx: uid, + 'partner_ids': lambda self, cr, uid, ctx: [self.pool.get('res.users').browse(cr, uid, [uid],context=ctx)[0].partner_id.id] + } + + def _check_closing_date(self, cr, uid, ids, context=None): + for event in self.browse(cr, uid, ids, context=context): + if event.date_deadline < event.date: + return False + return True + + _constraints = [ + (_check_closing_date, 'Error ! End date cannot be set before start date.', ['date_deadline']), + ] + + def onchange_dates(self, cr, uid, ids, start_date, duration=False, end_date=False, allday=False, context=None): + """Returns duration and/or end date based on values passed + @param ids: List of calendar event's IDs. + @param start_date: Starting date + @param duration: Duration between start date and end date + @param end_date: Ending Datee + """ + if context is None: + context = {} + + value = {} + if not start_date: + return value + + if not end_date and not duration: + duration = 1.00 + value['duration'] = duration + + + if allday: # For all day event + start = datetime.strptime(start_date.split(' ')[0].split('T')[0], "%Y-%m-%d") + duration = 24.0 + value['duration'] = duration + # change start_date's time to 00:00:00 in the user's timezone + user = self.pool.get('res.users').browse(cr, uid, uid) + tz = pytz.timezone(user.tz) if user.tz else pytz.utc + start = pytz.utc.localize(start).astimezone(tz) # convert start in user's timezone + #start = start.replace(hour=0, minute=0, second=0) # remove time + start = start.astimezone(pytz.utc) # convert start back to utc + value['date'] = start.strftime("%Y-%m-%d") + ' 00:00:00' + else: + start = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S") + + if end_date and not duration: + end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S") + diff = end - start + duration = float(diff.days)* 24 + (float(diff.seconds) / 3600) + value['duration'] = round(duration, 2) + elif not end_date: + end = start + timedelta(hours=duration) + value['date_deadline'] = end.strftime("%Y-%m-%d %H:%M:%S") + elif end_date and duration and not allday: + # we have both, keep them synchronized: + # set duration based on end_date (arbitrary decision: this avoid + # getting dates like 06:31:48 instead of 06:32:00) + end = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S") + diff = end - start + duration = float(diff.days)* 24 + (float(diff.seconds) / 3600) + value['duration'] = round(duration, 2) + + return {'value': value} + + def new_invitation_token(self, cr, uid, record, partner_id): + db_uuid = self.pool.get('ir.config_parameter').get_param(cr, uid, 'database.uuid') + invitation_token = hashlib.sha256('%s-%s-%s-%s-%s' % (time.time(), db_uuid, record._name, record.id, partner_id)).hexdigest() + return invitation_token + + def create_attendees(self, cr, uid, ids, context): + att_obj = self.pool.get('calendar.attendee') + user_obj = self.pool.get('res.users') + current_user = user_obj.browse(cr, uid, uid, context=context) + res = {} + for event in self.browse(cr, uid, ids, context): + attendees = {} + for att in event.attendee_ids: + attendees[att.partner_id.id] = True + new_attendees = [] + new_att_partner_ids = [] +# mail_to = "" + for partner in event.partner_ids: + if partner.id in attendees: + continue + access_token = self.new_invitation_token(cr, uid, event, partner.id) + values = { + 'partner_id': partner.id, + 'event_id': event.id, + 'access_token': access_token, + 'email': partner.email, + } + + if partner.id == current_user.partner_id.id: + values['state']='accepted' + + att_id = self.pool.get('calendar.attendee').create(cr, uid,values, context=context) +# if partner.email: +# mail_to = mail_to + " " + partner.email + + new_attendees.append(att_id) + new_att_partner_ids.append(partner.id) + + + if not current_user.email or current_user.email != partner.email : + mail_from = current_user.email or tools.config.get('email_from', False) + print "Send mail... from ",mail_from, " to ",att_id + if self.pool.get('calendar.attendee')._send_mail_to_attendees(cr, uid, att_id, email_from = mail_from, context=context): + self.message_post(cr, uid, event.id, body=_("An invitation email has been sent to attendee %s") % (partner.name,),subtype="calendar.subtype_invitation", context=context) + + if new_attendees: + self.write(cr, uid, [event.id], {'attendee_ids': [(4, att) for att in new_attendees]},context=context) + if new_att_partner_ids: + self.message_subscribe(cr, uid, [event.id], new_att_partner_ids, context=context) + + + # We remove old attendees who are not in partner_ids now. + all_partner_ids = [part.id for part in event.partner_ids] + all_part_attendee_ids = [att.partner_id.id for att in event.attendee_ids] + all_attendee_ids = [att.id for att in event.attendee_ids] + partner_ids_to_remove = map(lambda x: x, set(all_part_attendee_ids + new_att_partner_ids) - set(all_partner_ids)) + + attendee_ids_to_remove = [] + + if partner_ids_to_remove: + attendee_ids_to_remove =self.pool.get("calendar.attendee").search(cr,uid,[('partner_id.id','in',partner_ids_to_remove),('event_id.id','=',event.id)],context=context) + if attendee_ids_to_remove: + self.pool.get("calendar.attendee").unlink(cr, uid, attendee_ids_to_remove, context) + ## NEED TO UNSUBSCRIBE ? + + res[event.id] = { + 'new_attendee_ids' : new_attendees , + 'old_attendee_ids' : all_attendee_ids , + 'removed_attendee_ids' : attendee_ids_to_remove + } + return res + + def get_recurrent_ids(self, cr, uid, select, domain, limit=100, context=None): + """Gives virtual event ids for recurring events based on value of Recurrence Rule + This method gives ids of dates that comes between start date and end date of calendar views + + @param limit: The Number of Results to Return """ + if not context: + context = {} + + if isinstance(select, (str, int, long)): + ids_to_browse = [select] #keep select for return + else: + ids_to_browse = select + + result = [] + for ev in self.browse(cr, uid, select, context=context): + if not ev.recurrency or not ev.rrule: + result.append(ev.id) + continue +# event_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S") +# event_date = pytz.UTC.localize(event_date) + + rdates = self.get_recurrent_date_by_event(cr, uid, ev, context=context) + + for r_date in rdates: + # fix domain evaluation + # step 1: check date and replace expression by True or False, replace other expressions by True + # step 2: evaluation of & and | + # check if there are one False + pile = [] + ok = True + for arg in domain: + if str(arg[0]) in (str('date'), str('date_deadline'), str('end_date')): + if (arg[1] == '='): + ok = r_date.strftime('%Y-%m-%d')==arg[2] + if (arg[1] == '>'): + ok = r_date.strftime('%Y-%m-%d')>arg[2] + if (arg[1] == '<'): + ok = r_date.strftime('%Y-%m-%d')='): + ok = r_date.strftime('%Y-%m-%d')>=arg[2] + if (arg[1] == '<='): + ok = r_date.strftime('%Y-%m-%d')<=arg[2] + pile.append(ok) + elif str(arg) == str('&') or str(arg) == str('|'): + pile.append(arg) + else: + pile.append(True) + pile.reverse() + new_pile = [] + for item in pile: + if not isinstance(item, basestring): + res = item + elif str(item) == str('&'): + first = new_pile.pop() + second = new_pile.pop() + res = first and second + elif str(item) == str('|'): + first = new_pile.pop() + second = new_pile.pop() + res = first or second + new_pile.append(res) + + if [True for item in new_pile if not item]: + continue + # idval = real_id2calendar_id(data['id'], r_date.strftime("%Y-%m-%d %H:%M:%S")) + idval = '%d-%s' % (ev.id, r_date.strftime("%Y%m%d%H%M%S")) + result.append(idval) + + + if isinstance(select, (str, int, long)): + return ids and ids[0] or False + else: + ids = list(set(result)) + + return ids + + def compute_rule_string(self, data): + """ + Compute rule string according to value type RECUR of iCalendar from the values given. + @param self: the object pointer + @param data: dictionary of freq and interval value + @return: string containing recurring rule (empty if no rule) + """ + def get_week_string(freq, data): + weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su'] + if freq == 'weekly': + byday = map(lambda x: x.upper(), filter(lambda x: data.get(x) and x in weekdays, data)) + #byday = map(lambda x: x.upper(),[data[day] for day in weekdays if data[day]]) + + if byday: + return ';BYDAY=' + ','.join(byday) + return '' + + def get_month_string(freq, data): + if freq == 'monthly': + if data.get('month_by')=='date' and (data.get('day') < 1 or data.get('day') > 31): + raise osv.except_osv(_('Error!'), ("Please select a proper day of the month.")) + + if data.get('month_by')=='day': #Eg : Second Monday of the month + return ';BYDAY=' + data.get('byday') + data.get('week_list') + elif data.get('month_by')=='date': #Eg : 16th of the month + return ';BYMONTHDAY=' + str(data.get('day')) + return '' + + def get_end_date(data): + if data.get('end_date'): + data['end_date_new'] = ''.join((re.compile('\d')).findall(data.get('end_date'))) + 'T235959Z' + + return (data.get('end_type') == 'count' and (';COUNT=' + str(data.get('count'))) or '') +\ + ((data.get('end_date_new') and data.get('end_type') == 'end_date' and (';UNTIL=' + data.get('end_date_new'))) or '') + + freq = data.get('rrule_type', False) #day/week/month/year + res = '' + if freq: + interval_srting = data.get('interval') and (';INTERVAL=' + str(data.get('interval'))) or '' + res = 'FREQ=' + freq.upper() + get_week_string(freq, data) + interval_srting + get_end_date(data) + get_month_string(freq, data) + + return res + + def _get_empty_rrule_data(self): + return { + 'byday' : False, + 'recurrency' : False, + 'end_date' : False, + 'rrule_type' : False, + 'month_by' : False, + 'interval' : 0, + 'count' : False, + 'end_type' : False, + 'mo' : False, + 'tu' : False, + 'we' : False, + 'th' : False, + 'fr' : False, + 'sa' : False, + 'su' : False, + 'day' : False, + 'week_list' : False + } + + def _parse_rrule(self, rule, data, date_start): + day_list = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su'] + rrule_type = ['yearly', 'monthly', 'weekly', 'daily'] + r = rrule.rrulestr(rule, dtstart=datetime.strptime(date_start, "%Y-%m-%d %H:%M:%S")) + + if r._freq > 0 and r._freq < 4: + data['rrule_type'] = rrule_type[r._freq] + + data['count'] = r._count + data['interval'] = r._interval + data['end_date'] = r._until and r._until.strftime("%Y-%m-%d %H:%M:%S") + #repeat weekly + if r._byweekday: + for i in xrange(0,7): + if i in r._byweekday: + data[day_list[i]] = True + data['rrule_type'] = 'weekly' + #repeat monthly by nweekday ((weekday, weeknumber), ) + if r._bynweekday: + data['week_list'] = day_list[r._bynweekday[0][0]].upper() + data['byday'] = r._bynweekday[0][1] + data['month_by'] = 'day' + data['rrule_type'] = 'monthly' + + if r._bymonthday: + data['day'] = r._bymonthday[0] + data['month_by'] = 'date' + data['rrule_type'] = 'monthly' + + #repeat yearly but for openerp it's monthly, take same information as monthly but interval is 12 times + if r._bymonth: + data['interval'] = data['interval'] * 12 + + #FIXEME handle forever case + #end of recurrence + #in case of repeat for ever that we do not support right now + if not (data.get('count') or data.get('end_date')): + data['count'] = 100 + if data.get('count'): + data['end_type'] = 'count' + else: + data['end_type'] = 'end_date' + + return data + + #def _get_data(self, cr, uid, id, context=None): + # return self.read(cr, uid, id,['date', 'date_deadline']) + +# def need_to_update(self, event_id, vals): +# split_id = str(event_id).split("-") +# if len(split_id) < 2: +# return False +# else: +# date_start = vals.get('date', '') +# try: +# date_start = datetime.strptime(date_start, '%Y-%m-%d %H:%M:%S').strftime("%Y%m%d%H%M%S") +# return date_start == split_id[1] +# except Exception: +# return True + + def message_get_subscription_data(self, cr, uid, ids, user_pid=None, context=None): + res = {} + for virtual_id in ids: + real_id = calendar_id2real_id(virtual_id) + result = super(crm_meeting, self).message_get_subscription_data(cr, uid, [real_id], user_pid=None, context=context) + res[virtual_id] = result[real_id] + return res + + def onchange_partner_ids(self, cr, uid, ids, value, context=None): + """ The basic purpose of this method is to check that destination partners + effectively have email addresses. Otherwise a warning is thrown. + :param value: value format: [[6, 0, [3, 4]]] + """ + res = {'value': {}} + + if not value or not value[0] or not value[0][0] == 6: + return + + res.update(self.check_partners_email(cr, uid, value[0][2], context=context)) + return res + + def check_partners_email(self, cr, uid, partner_ids, context=None): + ##TODO : REFACTOR ! + """ Verify that selected partner_ids have an email_address defined. + Otherwise throw a warning. """ + partner_wo_email_lst = [] + for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids, context=context): + if not partner.email: + partner_wo_email_lst.append(partner) + if not partner_wo_email_lst: + return {} + warning_msg = _('The following contacts have no email address :') + for partner in partner_wo_email_lst: + warning_msg += '\n- %s' % (partner.name) + return {'warning': { + 'title': _('Email addresses not found'), + 'message': warning_msg, + } + } + # ---------------------------------------- + # OpenChatter + # ---------------------------------------- + + # shows events of the day for this user + + def _needaction_domain_get(self, cr, uid, context=None): + return [('end_date', '>=', time.strftime(DEFAULT_SERVER_DATE_FORMAT + ' 23:59:59')), ('date', '>=', time.strftime(DEFAULT_SERVER_DATE_FORMAT + ' 23:59:59')), ('user_id', '=', uid)] + + def message_post(self, cr, uid, thread_id, body='', subject=None, type='notification', subtype=None, parent_id=False, attachments=None, context=None, **kwargs): + if isinstance(thread_id, str): + thread_id = get_real_ids(thread_id) + if context.get('default_date'): + del context['default_date'] + return super(crm_meeting, self).message_post(cr, uid, thread_id, body=body, subject=subject, type=type, subtype=subtype, parent_id=parent_id, attachments=attachments, context=context, **kwargs) + + def do_sendmail(self, cr, uid, ids, context=None): + for event in self.browse(cr, uid, ids, context): + current_user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + + if current_user.email: + if self.pool.get('calendar.attendee')._send_mail_to_attendees(cr, uid, [att.id for att in event.attendee_ids], email_from = current_user.email, context=context): + self.message_post(cr, uid, event.id, body=_("An invitation email has been sent to attendee(s)"), subtype="calendar.subtype_invitation", context=context) + return; + + def get_attendee(self, cr, uid, meeting_id, context=None): + #Used for view in controller + invitation = {'meeting':{}, 'attendee': [], 'logo': ''} + attendee_pool = self.pool.get('calendar.attendee') + company_logo = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.logo + meeting = self.browse(cr, uid, int(meeting_id), context) + invitation['meeting'] = { + 'event':meeting.name, + 'where': meeting.location, + 'when':meeting.display_time + } + invitation['logo'] = company_logo.replace('\n','\\n') if company_logo else '' + for attendee in meeting.attendee_ids: + invitation['attendee'].append({'name':attendee.cn,'status': attendee.state}) + return invitation + + def get_interval(self, cr, uid, ids, date, interval, context=None): + #Function used only in crm_meeting_data.xml for email template + date = datetime.strptime(date.split('.')[0], DEFAULT_SERVER_DATETIME_FORMAT) + if interval == 'day': + res = str(date.day) + elif interval == 'month': + res = date.strftime('%B') + " " + str(date.year) + elif interval == 'dayname': + res = date.strftime('%A') + elif interval == 'time': + res = date.strftime('%I:%M %p') + return res + + def search(self, cr, uid, args, offset=0, limit=0, order=None, context=None, count=False): + + if context is None: + context={} + + if context.get('mymeetings',False): + partner_id = self.pool.get('res.users').browse(cr, uid, uid, context).partner_id.id + args += ['|', ('partner_ids', 'in', [partner_id]), ('user_id', '=', uid)] + + new_args = [] + for arg in args: + new_arg = arg + + if arg[0] in ('date', unicode('date')) and arg[1]==">=": + if context.get('virtual_id', True): + new_args += ['|','&',('recurrency','=',1),('end_date', arg[1], arg[2])] + # new_args += ['|','&',('recurrency','=',1),('date_deadline', arg[1], arg[2])] + elif arg[0] in ('date', unicode('date')): + if context.get('virtual_id', True): + print '' + #new_args += ['|','&',('recurrency','=',1),('end_date', arg[1], arg[2])] + # new_args += ['|','&',('recurrency','=',1),('date_deadline', arg[1], arg[2])] + elif arg[0] == "id": + new_id = get_real_ids(arg[2]) + new_arg = (arg[0], arg[1], new_id) + new_args.append(new_arg) + #offset, limit and count must be treated separately as we may need to deal with virtual ids + + res = super(crm_meeting,self).search(cr, uid, new_args, offset=0, limit=0, order=order, context=context, count=False) + + if context.get('virtual_id', True): + res = self.get_recurrent_ids(cr, uid, res, args, limit, context=context) + if count: + return len(res) + elif limit: + return res[offset:offset+limit] + return res + + def copy(self, cr, uid, id, default=None, context=None): + if context is None: + context = {} + + default = default or {} + default['attendee_ids'] = False + + + res = super(crm_meeting, self).copy(cr, uid, calendar_id2real_id(id), default, context) + return res + + def write(self, cr, uid, ids, values, context=None): + def _only_changes_to_apply_on_real_ids(field_names): + ''' return True if changes are only to be made on the real ids''' + for field in field_names: + if field not in ['name','message_follower_ids']: + return False + return True + + context = context or {} + if isinstance(ids, (str, int, long)): + ids = [ids] + res = False + new_id = False + + # Special write of complex IDS + for event_id in ids: + if len(str(event_id).split('-')) == 1: + continue + + ids.remove(event_id) + real_event_id = calendar_id2real_id(event_id) + + # if we are setting the recurrency flag to False or if we are only changing fields that + # should be only updated on the real ID and not on the virtual (like message_follower_ids): + # then set real ids to be updated. + if not values.get('recurrency', True) or _only_changes_to_apply_on_real_ids(values.keys()): + ids.append(real_event_id) + continue + + #if edit one instance of a reccurrent id + data = self.read(cr, uid, event_id, ['date', 'date_deadline', \ + 'rrule', 'duration'])#, 'exdate']) + if data.get('rrule'): + data.update( + values, + recurrent_id=real_event_id, + recurrent_id_date=data.get('date'), + rrule_type=False, + rrule='', + recurrency=False, + end_date = datetime.strptime(values.get('date',False) or data.get('date'),"%Y-%m-%d %H:%M:%S") + timedelta(hours=values.get('duration',False) or data.get('duration')) + ) + + #do not copy the id + if data.get('id'): + del(data['id']) + new_id = self.copy(cr, uid, real_event_id, default=data, context=context) + + date_new = event_id.split('-')[1] + date_new = time.strftime("%Y%m%dT%H%M%SZ", time.strptime(date_new, "%Y%m%d%H%M%S")) + + #exdate = (data['exdate'] and (data['exdate'] + ',') or '') + date_new + #res = super(crm_meeting, self).write(cr, uid, [real_event_id], {'exdate': exdate}) + + context.update({'active_id': new_id, 'active_ids': [new_id]}) + continue + + res = super(crm_meeting, self).write(cr, uid, ids, values, context=context) + + # set end_date for calendar searching + if values.get('recurrency', True) and values.get('end_type', 'count') in ('count', unicode('count')) and \ + (values.get('rrule_type') or values.get('count') or values.get('date') or values.get('date_deadline')): + for data in self.read(cr, uid, ids, ['date', 'date_deadline', 'recurrency', 'rrule_type', 'count', 'end_type'], context=context): + end_date = self._get_recurrency_end_date(data, context=context) + super(crm_meeting, self).write(cr, uid, [data['id']], {'end_date': end_date}, context=context) + + attendees_create = False + if values.get('partner_ids', False): + attendees_create = self.create_attendees(cr, uid, ids, context) + + if values.get('date', False) and values.get('active',True): #Not send mail when install data + the_id = new_id or (ids and int(ids[0])); + + if attendees_create: + attendees_create = attendees_create[the_id] + mail_to_ids = list(set(attendees_create['old_attendee_ids']) - set(attendees_create['removed_attendee_ids'])) + else: + mail_to_ids = [att.id for att in self.browse(cr,uid,the_id,context=context).attendee_ids] + + if mail_to_ids: + current_user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + if self.pool.get('calendar.attendee')._send_mail_to_attendees(cr, uid, mail_to_ids, template_xmlid='crm_email_template_meeting_changedate',email_from=current_user.email, context=context): + self.message_post(cr, uid, the_id, body=_("A email has been send to specify that the date has been changed !"),subtype="calendar.subtype_invitation", context=context) + else: + print 'Send mail return false' + return res or True and False + + def create(self, cr, uid, vals, context=None): + + if context is None: + context = {} + + if not 'user_id' in vals: #Else bug with quick_create when we are filter on an other user + vals['user_id'] = uid + + if vals.get('recurrency', True) and vals.get('end_type', 'count') in ('count', unicode('count')) and \ + (vals.get('rrule_type') or vals.get('count') or vals.get('date') or vals.get('date_deadline')): + vals['end_date'] = self._get_recurrency_end_date(vals, context=context) + + res = super(crm_meeting, self).create(cr, uid, vals, context=context) + + self.create_attendees(cr, uid, [res], context=context) + return res + + def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False): + + if not context: + context = {} + + if 'date' in groupby: + raise osv.except_osv(_('Warning!'), _('Group by date is not supported, use the calendar view instead.')) + virtual_id = context.get('virtual_id', True) + context.update({'virtual_id': False}) + res = super(crm_meeting, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby) + for re in res: + #remove the count, since the value is not consistent with the result of the search when expand the group + for groupname in groupby: + if re.get(groupname + "_count"): + del re[groupname + "_count"] + re.get('__context', {}).update({'virtual_id' : virtual_id}) + return res + + def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'): + + if context is None: + context = {} + fields2 = fields and fields[:] or None + + EXTRAFIELDS = ('class','user_id','duration', 'date','rrule', 'vtimezone')#, 'exdate') + for f in EXTRAFIELDS: + if fields and (f not in fields): + fields2.append(f) + + # FIXME This whole id mangling has to go! + if isinstance(ids, (str, int, long)): + select = [ids] + else: + select = ids + + select = map(lambda x: (x, calendar_id2real_id(x)), select) + result = [] + + real_data = super(crm_meeting, self).read(cr, uid, [real_id for calendar_id, real_id in select], fields=fields2, context=context, load=load) + real_data = dict(zip([x['id'] for x in real_data], real_data)) + + + for calendar_id, real_id in select: + res = real_data[real_id].copy() + + res = real_data[real_id].copy() + ls = calendar_id2real_id(calendar_id, with_date=res and res.get('duration', 0) or 0) + if not isinstance(ls, (str, int, long)) and len(ls) >= 2: + #recurrent_dates = [d.strftime("%Y-%m-%d %H:%M:%S") for d in get_recurrent_dates(res['rrule'], res['date'], res['exdate'],res['vtimezone'], context=context)] + #if not (ls[1] in recurrent_dates or ls[1] in res['exdate']): #when update a recurrent event + + res['date'] = ls[1] + res['date_deadline'] = ls[2] + res['id'] = calendar_id + result.append(res) + + for r in result: + if r['user_id']: + user_id = type(r['user_id']) in (tuple,list) and r['user_id'][0] or r['user_id'] + if user_id==uid: + continue + if r['class']=='private': + for f in r.keys(): + if f not in ('id','date','date_deadline','duration','user_id','state','interval','count'): + if isinstance(r[f], list): + r[f] = [] + else: + r[f] = False + if f=='name': + r[f] = _('Busy') + + for r in result: + for k in EXTRAFIELDS: + if (k in r) and (fields and (k not in fields)): + del r[k] + if isinstance(ids, (str, int, long)): + return result and result[0] or False + return result + + + def count_left_instance(self,cr,uid,event_id,context=None): + + event = self.browse(cr,uid,event_id,context=context) + if event.recurrent_id and event.recurrent_id > 0: + parent_event_id = event.recurrent_id + else: + parent_event_id = event.id + domain = ['|', ('id', '=', parent_event_id), ('recurrent_id', '=', parent_event_id)] + + count = self.search(cr, uid, domain, context=context) + print "Count = ",count + return len(count) + + def get_linked_ids(self,cr,uid,event_id,show_unactive=True,context=None): + event = self.browse(cr,uid,event_id,context=context) + if event.recurrent_id and event.recurrent_id > 0: + parent_event_id = event.recurrent_id + else: + parent_event_id = event.id + + domain = ['|', ('id', '=', parent_event_id), ('recurrent_id', '=', parent_event_id)] + + if show_unactive: + domain += ['|',('active', '=', True), ('active', '=', False)] + + return super(crm_meeting, self).search(cr, uid, domain, context=context) + +# def delete(self,cr,uid,ids,context=None): +# if not isinstance(ids, list): +# ids = [ids] +# all_ids = [] +# for id_to_unlink in ids: +# all_ids += self.get_linked_ids(cr, uid, id_to_unlink, context=context) +# print "in deleTe functIon === ids : %s, all_ids : %s" % (ids,all_ids) +# all_ids = list(set(all_ids)) +# res = super(crm_meeting, self).unlink(cr, uid, all_ids, context=context) +# return all_ids + + + def unlink(self, cr, uid, ids,unlink_level=0, context=None): + print "IN UNLINK !!!" + if not isinstance(ids, list): + ids = [ids] + res = False + + ids_to_exclure = [] + ids_to_unlink = [] + + #One time moved to google_Calendar, we can specify, an if not in google, and not rec or get_inst = 0, we delete it + for event_id in ids: + if unlink_level == 1 and len(str(event_id).split('-')) == 1: ## if ID REAL + if self.browse(cr,uid,event_id).recurrent_id: + # print "Could not be deleted ! because instance of recursive" + ids_to_exclure.append(event_id) + else: + ids_to_unlink.append(event_id) + else: + #print "Could not be deleted ! because instance virtual" + ids_to_exclure.append(event_id) + + + if ids_to_unlink: + res = super(crm_meeting, self).unlink(cr, uid, ids_to_unlink, context=context) + + if ids_to_exclure: + for id_to_exclure in ids_to_exclure: + res = self.write(cr, uid, id_to_exclure, {'active': False}, context=context) + + return res + + +class mail_message(osv.osv): + _inherit = "mail.message" + + def search(self, cr, uid, args, offset=0, limit=0, order=None, context=None, count=False): + ''' + convert the search on real ids in the case it was asked on virtual ids, then call super() + ''' + for index in range(len(args)): + if args[index][0] == "res_id" and isinstance(args[index][2], str): + args[index][2] = get_real_ids(args[index][2]) + return super(mail_message, self).search(cr, uid, args, offset=offset, limit=limit, order=order, context=context, count=count) + + def _find_allowed_model_wise(self, cr, uid, doc_model, doc_dict, context=None): + if doc_model == 'crm.meeting': + for virtual_id in self.pool[doc_model].get_recurrent_ids(cr, uid, doc_dict.keys(), [], context=context): + doc_dict.setdefault(virtual_id, doc_dict[get_real_ids(virtual_id)]) + return super(mail_message, self)._find_allowed_model_wise(cr, uid, doc_model, doc_dict, context=context) + +class ir_attachment(osv.osv): + _inherit = "ir.attachment" + + def search(self, cr, uid, args, offset=0, limit=0, order=None, context=None, count=False): + ''' + convert the search on real ids in the case it was asked on virtual ids, then call super() + ''' + for index in range(len(args)): + if args[index][0] == "res_id" and isinstance(args[index][2], str): + args[index][2] = get_real_ids(args[index][2]) + return super(ir_attachment, self).search(cr, uid, args, offset=offset, limit=limit, order=order, context=context, count=count) + + def write(self, cr, uid, ids, vals, context=None): + ''' + when posting an attachment (new or not), convert the virtual ids in real ids. + ''' + if isinstance(vals.get('res_id'), str): + vals['res_id'] = get_real_ids(vals.get('res_id')) + return super(ir_attachment, self).write(cr, uid, ids, vals, context=context) + +class invite_wizard(osv.osv_memory): + _inherit = 'mail.wizard.invite' + + def default_get(self, cr, uid, fields, context=None): + ''' + in case someone clicked on 'invite others' wizard in the followers widget, transform virtual ids in real ids + ''' + result = super(invite_wizard, self).default_get(cr, uid, fields, context=context) + if 'res_id' in result: + result['res_id'] = get_real_ids(result['res_id']) + return result + diff --git a/addons/calendar/calendar_data.xml b/addons/calendar/calendar_data.xml new file mode 100644 index 00000000000..b1ceb6cbd7c --- /dev/null +++ b/addons/calendar/calendar_data.xml @@ -0,0 +1,489 @@ + + + + + + + Invitation + crm.meeting + Warning, a mandatory field has been modified since the creation of this event + + + + + Event + calendar.event + + + + 15 min notif + + minutes + notification + + + 30 min notif + + minutes + notification + + + 1 hour notif + + hours + notification + + + 2 hours notif + + hours + notification + + + 1 day notif + + days + notification + + + + + 15 min mail + + minutes + email + + + 30 min mail + + minutes + email + + + 1 hour mail + + hours + email + + + 2 hours mail + + hours + email + + + 1 day mail + + days + email + + + + + + Run Event Reminder + + + 1 + minutes + -1 + + + + + + + + Customer Meeting + + + + Internal Meeting + + + + Off-site Meeting + + + + Open Discussion + + + + Feedback Meeting + + + + Meeting + crm.meeting + + + + Meeting Invitation + ${object.event_id.user_id.email or ''} + ${object.event_id.name} + + + ${('' if object.partner_id and object.partner_id.email and object.partner_id.email==object.email else object.email|safe)} + ${object.partner_id and object.partner_id.email and object.partner_id.email==object.email and object.partner_id.id or False } + + + + + + ${object.event_id.name} + + + +
+
+ ${object.event_id.name} +
+
+ Hello ${object.cn} ,

${object.event_id.user_id.partner_id.name} invited you for the ${object.event_id.name} meeting of ${object.event_id.user_id.company_id.name}.

+
+
+ + + + + +
+
${object.event_id.get_interval(object.event_id.date, 'dayname')}
+
+ ${object.event_id.get_interval(object.event_id.date,'day')} +
+
${object.event_id.get_interval(object.event_id.date, 'month')}
+
${object.event_id.get_interval(object.event_id.date, 'time')}
+
+ + % if object.event_id.location: + + + + + % endif + % if object.event_id.description : + + + + + % endif + % if not object.event_id.allday and object.event_id.duration: + + + + + % endif + + + + +
+
+ Where +
+
+
+ : ${object.event_id.location} + (View Map) + +
+
+
+ What +
+
+
+ : ${object.event_id.description} +
+
+
+ Duration +
+
+
+ : ${('%dH%02d' % (object.event_id.duration,(object.event_id.duration*60)%60))} +
+
+
+ Attendees +
+
+ : + % for attendee in object.event_id.attendee_ids: +
+ % if attendee.cn != object.cn: + ${attendee.cn} + % else: + You + % endif + % endfor +
+
+
+
+
+ + + ]]> +
+
+ + + Meeting Invitation + ${object.event_id.user_id.email or ''} + ${object.event_id.name} - Date has been updated + + + ${('' if object.partner_id and object.partner_id.email and object.partner_id.email==object.email else object.email|safe)} + ${object.partner_id and object.partner_id.email and object.partner_id.email==object.email and object.partner_id.id or False } + + + + + + ${object.event_id.name} + + + +
+
+ ${object.event_id.name} +
+
+ Hello ${object.cn} ,
+

The date of the meeting has been changed...
+ The meeting created by ${object.event_id.user_id.partner_id.name} is now scheduled for : ${object.event_id.date}.

+
+
+ + + + + +
+
${object.event_id.get_interval(object.event_id.date, 'dayname')}
+
+ ${object.event_id.get_interval(object.event_id.date,'day')} +
+
${object.event_id.get_interval(object.event_id.date, 'month')}
+
${object.event_id.get_interval(object.event_id.date, 'time')}
+
+ + % if object.event_id.location: + + + + + % endif + % if object.event_id.description : + + + + + % endif + % if not object.event_id.allday and object.event_id.duration: + + + + + % endif + + + + +
+
+ Where +
+
+
+ : ${object.event_id.location} + (View Map) + +
+
+
+ What +
+
+
+ : ${object.event_id.description} +
+
+
+ Duration +
+
+
+ : ${('%dH%02d' % (object.event_id.duration,(object.event_id.duration*60)%60))} +
+
+
+ Attendees +
+
+ : + % for attendee in object.event_id.attendee_ids: +
+ % if attendee.cn != object.cn: + ${attendee.cn} + % else: + You + % endif + % endfor +
+
+
+
+ Accept + Decline + View +
+
+ + + ]]> +
+
+ + + Meeting Invitation + ${object.event_id.user_id.email or ''} + ${object.event_id.name} - Reminder + + + ${('' if object.partner_id and object.partner_id.email and object.partner_id.email==object.email else object.email|safe)} + ${object.partner_id and object.partner_id.email and object.partner_id.email==object.email and object.partner_id.id or False } + + + + + + ${object.event_id.name} + + + +
+
+ ${object.event_id.name} +
+
+ Hello ${object.cn} ,
+

this it a rmeinder for the event below :

+
+
+ + + + + +
+
${object.event_id.get_interval(object.event_id.date, 'dayname')}
+
+ ${object.event_id.get_interval(object.event_id.date,'day')} +
+
${object.event_id.get_interval(object.event_id.date, 'month')}
+
${object.event_id.get_interval(object.event_id.date, 'time')}
+
+ + % if object.event_id.location: + + + + + % endif + % if object.event_id.description : + + + + + % endif + % if not object.event_id.allday and object.event_id.duration: + + + + + % endif + + + + +
+
+ Where +
+
+
+ : ${object.event_id.location} + (View Map) + +
+
+
+ What +
+
+
+ : ${object.event_id.description} +
+
+
+ Duration +
+
+
+ : ${('%dH%02d' % (object.event_id.duration,(object.event_id.duration*60)%60))} +
+
+
+ Attendees +
+
+ : + % for attendee in object.event_id.attendee_ids: +
+ % if attendee.cn != object.cn: + ${attendee.cn} + % else: + You + % endif + % endfor +
+
+
+
+ + + ]]> +
+
+ +
+
diff --git a/addons/calendar/calendar_demo.xml b/addons/calendar/calendar_demo.xml new file mode 100644 index 00000000000..e94f7681d25 --- /dev/null +++ b/addons/calendar/calendar_demo.xml @@ -0,0 +1,131 @@ + + + + + + + + Another Partner + + + another_partner@example.com + + + Another User + Another Login + + + + + + + + + + + + + + + + + + + + Follow-up for Project proposal + Meeting to discuss project plan and hash out the details of implementation. + + + + + open + + + + + + + Initial discussion + Discussion with partner for product. + + + + + draft + + + + + + Pricing Discussion + Internal meeting for discussion for new pricing for product and services. + + + + + open + + + + + + + Requirements review + + + + + open + + + + + + Changes in Designing + + + + + open + + + + + + + Presentation for new Services + + + + + draft + + + + + + + Meeting between 2 others users than me + + + + + draft + + + + + + + Meeting between 2 others users than me + + + + + draft + + + + diff --git a/addons/calendar/calendar_view.xml b/addons/calendar/calendar_view.xml new file mode 100644 index 00000000000..97d6f34a56b --- /dev/null +++ b/addons/calendar/calendar_view.xml @@ -0,0 +1,321 @@ + + + + + + + + + Meeting Types Tree + crm.meeting.type + + + + + + + + + Meeting Types + crm.meeting.type + form + + + + + + + CRM - Meetings Form + crm.meeting + + +
+ + + + +
+
+
+

+ +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ + \ + "; + a = self.do_notify(res.title,res.message,true); + + $(".link2event").on('click', function() { + self.rpc("/web/action/load", { + action_id: "calendar.action_crm_meeting_notify", + }).then( function(r) { + r.res_id = res.event_id; + return self.action_manager.do_action(r); + }); + + }); + a.element.find(".link2recall").on('click',function() { + self.getMyNotifBox(this).find('.ui-notify-close').trigger("click"); + }); + + a.element.find(".link2showed").on('click',function() { + self.getMyNotifBox(this).find('.ui-notify-close').trigger("click"); + self.rpc("/calendar/NextNotify", { + type: "UPDATE" + }); + }); + } + //If notification already displayed in the past, we remove the css attribute which hide this notification + else if (self.getMyNotifBox($.find(".eid_"+res.event_id)).attr("style") !== ""){ + self.getMyNotifBox($.find(".eid_"+res.event_id)).attr("style",""); + } + },res.timer * 1000); + }); + } + ); + }, + check_notifications: function() { + var self= this; + self.get_next_event(); + setInterval(function(){ + self.get_next_event(); + }, 5 * 60 * 1000 ); + }, + + //Override the show_application of addons/web/static/src/js/chrome.js + show_application: function() { + this._super(); + this.check_notifications(); + } + }); + + + instance.calendar.invitation = instance.web.Widget.extend({ + + init: function(parent, db, action, id, view, attendee_data) { + this._super(); + this.db = db; + this.action = action; + this.id = id; + this.view = view; + this.attendee_data = attendee_data; + }, + start: function() { + var self = this; + if(instance.session.session_is_valid(self.db) && instance.session.username != "anonymous") { + self.redirect_meeting_view(self.db,self.action,self.id,self.view); + } else { + self.open_invitation_form(self.attendee_data); + } + }, + open_invitation_form : function(invitation){ + this.$el.html(QWeb.render('invitation_view', {'invitation': JSON.parse(invitation)})); + }, + redirect_meeting_view : function(db, action, meeting_id, view){ + var self = this; + var action_url = ''; + if(view == "form") { + action_url = _.str.sprintf('/?db=%s#id=%s&view_type=%s&model=crm.meeting', db, meeting_id, view, meeting_id); + } else { + action_url = _.str.sprintf('/?db=%s#view_type=%s&model=crm.meeting&action=%s',self.db,self.view,self.action); + } + var reload_page = function(){ + return location.replace(action_url); + } + reload_page(); + }, + }); + + instance.web.form.Many2ManyAttendee = instance.web.form.FieldMany2ManyTags.extend({ + tag_template: "many2manyattendee", + initialize_texttext: function() { + return _.extend(this._super(),{ + html : { + tag: '
' + } + }); + }, + map_tag: function(value){ + return _.map(value, function(el) {return {name: el[1], id:el[0], state: el[2]};}) + }, + get_render_data: function(ids){ + var self = this; + var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.build_context()); + return dataset.call('get_attendee_detail',[ids, self.getParent().datarecord.id || false]); + }, + render_tag: function(data){ + this._super(data); + var self = this; + if (! self.get("effective_readonly")) { + var tag_element = self.tags.tagElements(); + _.each(data,function(value, key){ + $(tag_element[key]).find(".custom-edit").addClass(data[key][2]) + }); + } + } + }); + instance.web.form.widgets = instance.web.form.widgets.extend({ + 'many2manyattendee' : 'instance.web.form.Many2ManyAttendee', + }); + + instance.calendar.event = function (db, action, id, view, attendee_data) { + instance.session.session_bind(instance.session.origin).done(function () { + new instance.calendar.invitation(null,db,action,id,view,attendee_data).appendTo($("body").addClass('openerp')); + }); + } + + + + + +}; + diff --git a/addons/calendar/static/src/xml/base_calendar.xml b/addons/calendar/static/src/xml/base_calendar.xml new file mode 100644 index 00000000000..42689c5515a --- /dev/null +++ b/addons/calendar/static/src/xml/base_calendar.xml @@ -0,0 +1,43 @@ + + diff --git a/addons/calendar/test/base_calendar_test.yml b/addons/calendar/test/base_calendar_test.yml new file mode 100644 index 00000000000..f9b59864a68 --- /dev/null +++ b/addons/calendar/test/base_calendar_test.yml @@ -0,0 +1,66 @@ +- + In Order to test calendar, I will first create One Simple Event with real data +- + !record {model: crm.meeting, id: crm_meeting_technicalpresentation0}: + class: private + date: '2011-04-30 16:00:00' + date_deadline: '2011-04-30 18:30:00' + description: The Technical Presentation will cover following topics:\n* Creating OpenERP + class\n* Views\n* Wizards\n* Workflows + duration: 2.5 + location: OpenERP S.A. + name: Technical Presentation +- + Now I will set recurrence for this event to occur monday and friday of week +- + !python {model: crm.meeting}: | + data = {'fr': 1, 'mo': 1, 'interval': 1, 'rrule_type': 'weekly', 'end_type': 'end_date', 'end_date': '2011-05-31 00:00:00', 'recurrency' : True} + self.write(cr, uid, [ref("crm_meeting_technicalpresentation0")], data) +- + In order to check that recurrent events are views successfully in calendar view, I will open calendar view of events +- + !python {model: crm.meeting}: | + self.fields_view_get(cr, uid, False, 'calendar', context) +- + In order to check that recurrent events are views successfully in calendar view, I will search for one of the recurrent event and count the number of events +- + !python {model: crm.meeting}: | + ids = self.search(cr, uid, [('date', '>=', '2011-04-30 16:00:00'), ('date', '<=', '2011-05-31 00:00:00')], context={'virtual_id': True} ) + assert len(ids) == 9, 'Wrong number of events found' +- + Now I move a virtual event, to see that a real event is well created and depending from the native recurrence +- + !python {model: crm.meeting}: | + ids = self.search(cr, uid, [('date', '>=', '2011-04-30 16:00:00'), ('date', '<=', '2011-05-31 00:00:00')], context={'virtual_id': True} ) + before = self.search(cr, uid, [('date', '>=', '2011-04-30 16:00:00'), ('date', '<=', '2011-05-31 00:00:00')], context={'virtual_id': False}) + self.write(cr, uid,[ids[1]], {'name':'New Name','recurrency' : True}, context={'virtual_id': True}) + after = self.search(cr, uid, [('date', '>=', '2011-04-30 16:00:00'), ('date', '<=', '2011-05-31 00:00:00')], context={'virtual_id': False}) + assert len(after) == len(before)+1, 'Wrong number of events found, after to have moved a virtual event' + new_id = list(set(after)-set(before))[0] + new_event = self.browse(cr,uid,new_id,context=context) + assert new_event.recurrent_id == before[0], 'Recurrent_id not correctly passed to the new event' +- + Now I will make All day event and test it +- + !record {model: crm.meeting, id: crm_meeting_alldaytestevent0}: + allday: 1 + class: confidential + date: '2011-04-30 00:00:00' + date_deadline: '2011-04-30 00:00:00' + description: 'All day technical test ' + location: School + name: All day test event +- + In order to check reminder I will first create reminder +- + !record {model: calendar.alarm, id: res_alarm_daybeforeeventstarts0}: + name: 1 Day before event starts + duration: 1 + interval: days + type: notification + +- + Now I will assign this reminder to all day event +- + !python {model: crm.meeting}: | + self.write(cr, uid, [ref("crm_meeting_alldaytestevent0")], {'alarm_ids': [(6,0,[ref("res_alarm_daybeforeeventstarts0")])]}) diff --git a/addons/calendar/test/test_crm_recurrent_meeting_case2.yml b/addons/calendar/test/test_crm_recurrent_meeting_case2.yml new file mode 100644 index 00000000000..6b060835c72 --- /dev/null +++ b/addons/calendar/test/test_crm_recurrent_meeting_case2.yml @@ -0,0 +1,95 @@ +- + In order to test recurrent meetings in OpenERP, I create meetings with different recurrency using different test cases. +- + I create a recurrent meeting with daily recurrency and fixed amount of time. +- + !record {model: crm.meeting, id: crm_meeting_testmeeting0}: + count: 5 + date: '2011-04-13 11:04:00' + date_deadline: '2011-04-13 12:04:00' + day: 0.0 + duration: 1.0 + name: Test Meeting + recurrency: true + recurrent_id: 0.0 + rrule_type: daily + sequence: 0.0 +- + I search for all the recurrent meetings. +- + !python {model: crm.meeting}: | + meeting_ids = self.search(cr, uid, [('id', 'in', [ref('crm_meeting_testmeeting0')]),('date','>=','2011-03-13'), ('date_deadline', '<=', '2011-05-13')], context={'virtual_id': True}) + assert len(meeting_ids) == 5, 'Recurrent daily meetings are not created !' +- + I create a weekly meeting till a particular end date. +- + !record {model: crm.meeting, id: crm_meeting_reviewcodewithprogrammer0}: + date: '2011-04-18 11:47:00' + date_deadline: '2011-04-18 12:47:00' + day: 0.0 + duration: 1.0 + end_date: '2011-04-30' + end_type: end_date + fr: true + mo: true + th: true + tu: true + we: true + name: Review code with programmer + recurrency: true + recurrent_id: 0.0 + rrule_type: weekly + sequence: 0.0 +- + I search for all the recurrent weekly meetings. +- + !python {model: crm.meeting}: | + meeting_ids = self.search(cr, uid, [('id', 'in', [ref('crm_meeting_reviewcodewithprogrammer0')]),('date','>=','2011-03-13'), ('date_deadline', '<=', '2011-05-13')], context={'virtual_id': True}) + assert len(meeting_ids) == 10, 'Recurrent weekly meetings are not created !' +- + I want to schedule a meeting every month for Sprint review. +- + !record {model: crm.meeting, id: crm_meeting_sprintreview0}: + count: 12 + date: '2011-04-01 12:01:00' + date_deadline: '2011-04-01 13:01:00' + day: 1 + duration: 1.0 + name: Sprint Review + recurrency: true + recurrent_id: 0.0 + rrule_type: monthly + sequence: 0.0 +- + I search for all the recurrent monthly meetings. +- + !python {model: crm.meeting}: | + meeting_ids = self.search(cr, uid, [('id', 'in', [ref('crm_meeting_sprintreview0')]),('date','>=','2011-03-01'), ('date_deadline', '<=', '2012-05-13')], context={'virtual_id': True}) + assert len(meeting_ids) == 12, 'Recurrent monthly meetings are not created !' +- + I change name of my monthly Sprint Review meeting. +- + !python {model: crm.meeting}: | + idval = '%d-%s' % (ref('crm_meeting_sprintreview0'), '20110901130100') + self.write(cr, uid, [idval], {'name': 'Sprint Review for google modules'}) +- + I check whether all the records are edited or not. +- + !python {model: crm.meeting}: | + meeting_ids = self.search(cr, uid, [('id', 'in', [ref('crm_meeting_sprintreview0')]),('date','>=','2011-03-01'), ('date_deadline', '<=', '2012-05-13')], context={'virtual_id': True}) + meetings = self.browse(cr, uid, meeting_ids, context) + for meeting in meetings: + assert meeting.name == 'Sprint Review for google modules', 'Name not changed for id: %s' %meeting.id +- + I change description of my weekly meeting Review code with programmer. +- + !python {model: crm.meeting}: | + idval = '%d-%s' % (ref('crm_meeting_sprintreview0'), '20110425124700') + self.write(cr, uid, [idval], {'description': 'Review code of the module: sync_google_calendar.'}) +- + I check whether the record is edited perfectly or not. +- + !python {model: crm.meeting}: | + meeting_ids = self.search(cr, uid, [('recurrent_id', '=', ref('crm_meeting_sprintreview0')), ('recurrent_id_date','=','2011-04-25 12:47:00')], context) + assert meeting_ids, 'Meeting is not edited !' + diff --git a/addons/crm/__openerp__.py b/addons/crm/__openerp__.py index e3443d5200f..0caacc88025 100644 --- a/addons/crm/__openerp__.py +++ b/addons/crm/__openerp__.py @@ -54,7 +54,7 @@ Dashboard for CRM will include: 'process', 'mail', 'email_template', - 'base_calendar', + 'calendar', 'resource', 'board', 'fetchmail', diff --git a/addons/crm/base_partner_merge.py b/addons/crm/base_partner_merge.py index c73b5fdf599..c07a32579f0 100644 --- a/addons/crm/base_partner_merge.py +++ b/addons/crm/base_partner_merge.py @@ -223,7 +223,7 @@ class MergePartnerAutomatic(osv.TransientModel): update_records = functools.partial(update_records, context=context) for partner in src_partners: - update_records('base.calendar', src=partner, field_model='model_id.model') + update_records('calendar', src=partner, field_model='model_id.model') update_records('ir.attachment', src=partner, field_model='res_model') update_records('mail.followers', src=partner, field_model='res_model') update_records('mail.message', src=partner) diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index eaf51293355..733849efb02 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -914,7 +914,7 @@ class crm_lead(format_address, osv.osv): :return dict: dictionary value for created Meeting view """ opportunity = self.browse(cr, uid, ids[0], context) - res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid, 'base_calendar', 'action_crm_meeting', context) + res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid, 'calendar', 'action_crm_meeting', context) res['context'] = { 'default_opportunity_id': opportunity.id, 'default_partner_id': opportunity.partner_id and opportunity.partner_id.id or False, diff --git a/addons/crm/crm_meeting.py b/addons/crm/crm_meeting.py index c31907bebe2..40aaa2228c0 100644 --- a/addons/crm/crm_meeting.py +++ b/addons/crm/crm_meeting.py @@ -24,7 +24,7 @@ import logging _logger = logging.getLogger(__name__) # -# crm.meeting is defined in module base_calendar +# crm.meeting is defined in module calendar # class crm_meeting(osv.Model): """ Model for CRM meetings """ diff --git a/addons/crm/crm_phonecall.py b/addons/crm/crm_phonecall.py index e74fecc9bdf..bd30ef5c678 100644 --- a/addons/crm/crm_phonecall.py +++ b/addons/crm/crm_phonecall.py @@ -265,7 +265,7 @@ class crm_phonecall(osv.osv): :return dict: dictionary value for created meeting view """ phonecall = self.browse(cr, uid, ids[0], context) - res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid, 'base_calendar', 'action_crm_meeting', context) + res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid, 'calendar', 'action_crm_meeting', context) res['context'] = { 'default_phonecall_id': phonecall.id, 'default_partner_id': phonecall.partner_id and phonecall.partner_id.id or False, diff --git a/addons/crm/res_partner_view.xml b/addons/crm/res_partner_view.xml index 8b941a395dd..ff4134b5570 100644 --- a/addons/crm/res_partner_view.xml +++ b/addons/crm/res_partner_view.xml @@ -96,7 +96,7 @@ context="{'search_default_partner_id': active_id}"/>