[MERGE] Merge with base branch tempalte-aja
bzr revid: jke@openerp.com-20131107140448-kp3daxcqvymdfy7r
This commit is contained in:
commit
b92220ea7d
|
@ -21,5 +21,6 @@
|
|||
|
||||
import base_calendar
|
||||
import crm_meeting
|
||||
import controllers
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -47,11 +47,18 @@ If you need to manage your meetings, you should install the CRM module.
|
|||
'base_calendar_data.xml',
|
||||
'crm_meeting_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'],
|
||||
'installable': True,
|
||||
'application': True,
|
||||
'auto_install': False,
|
||||
'images': ['images/base_calendar1.jpeg','images/base_calendar2.jpeg','images/base_calendar3.jpeg','images/base_calendar4.jpeg',],
|
||||
'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:
|
||||
|
|
|
@ -28,10 +28,9 @@ from openerp.tools.translate import _
|
|||
import pytz
|
||||
import re
|
||||
import time
|
||||
|
||||
import hashlib
|
||||
from openerp import tools, SUPERUSER_ID
|
||||
import openerp.service.report
|
||||
|
||||
months = {
|
||||
1: "January", 2: "February", 3: "March", 4: "April", \
|
||||
5: "May", 6: "June", 7: "July", 8: "August", 9: "September", \
|
||||
|
@ -143,95 +142,6 @@ def real_id2base_calendar_id(real_id, recurrent_date):
|
|||
return '%d-%s' % (real_id, recurrent_date)
|
||||
return real_id
|
||||
|
||||
html_invitation = """
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
<title>%(name)s</title>
|
||||
</head>
|
||||
<body>
|
||||
<table border="0" cellspacing="10" cellpadding="0" width="100%%"
|
||||
style="font-family: Arial, Sans-serif; font-size: 14">
|
||||
<tr>
|
||||
<td width="100%%">Hello,</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%%">You are invited for <i>%(company)s</i> Event.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%%">Below are the details of event. Hours and dates expressed in %(timezone)s time.</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table cellspacing="0" cellpadding="5" border="0" summary=""
|
||||
style="width: 90%%; font-family: Arial, Sans-serif; border: 1px Solid #ccc; background-color: #f6f6f6">
|
||||
<tr valign="center" align="center">
|
||||
<td bgcolor="DFDFDF">
|
||||
<h3>%(name)s</h3>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table cellpadding="8" cellspacing="0" border="0"
|
||||
style="font-size: 14" summary="Eventdetails" bgcolor="f6f6f6"
|
||||
width="90%%">
|
||||
<tr>
|
||||
<td width="21%%">
|
||||
<div><b>Start Date</b></div>
|
||||
</td>
|
||||
<td><b>:</b></td>
|
||||
<td>%(start_date)s</td>
|
||||
<td width="15%%">
|
||||
<div><b>End Date</b></div>
|
||||
</td>
|
||||
<td><b>:</b></td>
|
||||
<td width="25%%">%(end_date)s</td>
|
||||
</tr>
|
||||
<tr valign="top">
|
||||
<td><b>Description</b></td>
|
||||
<td><b>:</b></td>
|
||||
<td colspan="3">%(description)s</td>
|
||||
</tr>
|
||||
<tr valign="top">
|
||||
<td>
|
||||
<div><b>Location</b></div>
|
||||
</td>
|
||||
<td><b>:</b></td>
|
||||
<td colspan="3">%(location)s</td>
|
||||
</tr>
|
||||
<tr valign="top">
|
||||
<td>
|
||||
<div><b>Event Attendees</b></div>
|
||||
</td>
|
||||
<td><b>:</b></td>
|
||||
<td colspan="3">
|
||||
<div>
|
||||
<div>%(attendees)s</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table border="0" cellspacing="10" cellpadding="0" width="100%%"
|
||||
style="font-family: Arial, Sans-serif; font-size: 14">
|
||||
<tr>
|
||||
<td width="100%%">From:</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%%">%(user)s</td>
|
||||
</tr>
|
||||
<tr valign="top">
|
||||
<td width="100%%">-<font color="a7a7a7">-------------------------</font></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%%"> <font color="a7a7a7">%(sign)s</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
class calendar_attendee(osv.osv):
|
||||
"""
|
||||
|
@ -390,6 +300,8 @@ property or property parameter."),
|
|||
multi='event_end_date'),
|
||||
'ref': fields.reference('Event Ref', selection=openerp.addons.base.res.res_request.referencable_models, size=128),
|
||||
'availability': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Free/Busy', readonly="True"),
|
||||
'access_token':fields.char('Invitation Token', size=256),
|
||||
|
||||
}
|
||||
_defaults = {
|
||||
'state': 'needs-action',
|
||||
|
@ -503,53 +415,43 @@ property or property parameter."),
|
|||
@param email_from: email address for user sending the mail
|
||||
@return: True
|
||||
"""
|
||||
company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.name
|
||||
for att in self.browse(cr, uid, ids, context=context):
|
||||
sign = att.sent_by_uid and att.sent_by_uid.signature or ''
|
||||
sign = '<br>'.join(sign and sign.split('\n') or [])
|
||||
res_obj = att.ref
|
||||
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 = {
|
||||
'needs-action' : 'grey',
|
||||
'accepted' :'green',
|
||||
'tentative' :'#FFFF00',
|
||||
'declined':'red',
|
||||
'delegated':'grey'
|
||||
}
|
||||
for attendee in self.browse(cr, uid, ids, context=context):
|
||||
res_obj = attendee.ref
|
||||
if res_obj:
|
||||
att_infos = []
|
||||
sub = res_obj.name
|
||||
other_invitation_ids = self.search(cr, uid, [('ref', '=', res_obj._name + ',' + str(res_obj.id))])
|
||||
|
||||
for att2 in self.browse(cr, uid, other_invitation_ids):
|
||||
att_infos.append(((att2.user_id and att2.user_id.name) or \
|
||||
(att2.partner_id and att2.partner_id.name) or \
|
||||
att2.email) + ' - Status: ' + att2.state.title())
|
||||
#dates and times are gonna be expressed in `tz` time (local timezone of the `uid`)
|
||||
tz = context.get('tz', pytz.timezone('UTC'))
|
||||
#res_obj.date and res_obj.date_deadline are in UTC in database so we use context_timestamp() to transform them in the `tz` timezone
|
||||
date_start = fields.datetime.context_timestamp(cr, uid, datetime.strptime(res_obj.date, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context)
|
||||
date_stop = False
|
||||
if res_obj.date_deadline:
|
||||
date_stop = fields.datetime.context_timestamp(cr, uid, datetime.strptime(res_obj.date_deadline, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context)
|
||||
body_vals = {'name': res_obj.name,
|
||||
'start_date': date_start,
|
||||
'end_date': date_stop,
|
||||
'timezone': tz,
|
||||
'description': res_obj.description or '-',
|
||||
'location': res_obj.location or '-',
|
||||
'attendees': '<br>'.join(att_infos),
|
||||
'user': res_obj.user_id and res_obj.user_id.name or 'OpenERP User',
|
||||
'sign': sign,
|
||||
'company': company
|
||||
}
|
||||
body = html_invitation % body_vals
|
||||
if mail_to and email_from:
|
||||
model,template_id = data_pool.get_object_reference(cr, uid, 'base_calendar', "crm_email_template_meeting_invitation")
|
||||
model,act_id = data_pool.get_object_reference(cr, uid, 'base_calendar', "view_crm_meeting_calendar")
|
||||
action_id = self.pool.get('ir.actions.act_window').search(cr, uid, [('view_id','=',act_id)], context=context)
|
||||
base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url', default='http://localhost:8069', context=context)
|
||||
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, res_obj, context=context)
|
||||
vals = {'email_from': email_from,
|
||||
'email_to': mail_to,
|
||||
'state': 'outgoing',
|
||||
'subject': sub,
|
||||
'body_html': body,
|
||||
'auto_delete': True}
|
||||
local_context['att_obj'] = attendee
|
||||
local_context['color'] = color
|
||||
local_context['action_id'] = action_id[0]
|
||||
local_context['dbname'] = cr.dbname
|
||||
local_context['base_url'] = base_url
|
||||
vals = template_pool.generate_email(cr, uid, template_id, res_obj.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')})]
|
||||
self.pool.get('mail.mail').create(cr, uid, vals, context=context)
|
||||
return True
|
||||
'datas_fname': 'invitation.ics',
|
||||
'datas': str(ics_file).encode('base64')})]
|
||||
if not attendee.partner_id.opt_out:
|
||||
mail_id.append(mail_pool.create(cr, uid, vals, context=context))
|
||||
if mail_id:
|
||||
return mail_pool.send(cr, uid, mail_id, context=context)
|
||||
return False
|
||||
|
||||
def onchange_user_id(self, cr, uid, ids, user_id, *args, **argv):
|
||||
"""
|
||||
|
@ -590,8 +492,14 @@ property or property parameter."),
|
|||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
return self.write(cr, uid, ids, {'state': 'accepted'}, context)
|
||||
meeting_obj = self.pool.get('crm.meeting')
|
||||
res = self.write(cr, uid, ids, {'state': 'accepted'}, context)
|
||||
for attandee in self.browse(cr, uid, ids, context=context):
|
||||
meeting_ids = meeting_obj.search(cr, uid, [('attendee_ids', '=', attandee.id)], context=context)
|
||||
if meeting_ids:
|
||||
meeting_obj.message_post(cr, uid, get_real_ids(meeting_ids), body=_(("%s has accepted invitation") % (attandee.cn)), context=context)
|
||||
return res
|
||||
|
||||
|
||||
def do_decline(self, cr, uid, ids, context=None, *args):
|
||||
"""
|
||||
|
@ -605,7 +513,13 @@ property or property parameter."),
|
|||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
return self.write(cr, uid, ids, {'state': 'declined'}, context)
|
||||
meeting_obj = self.pool.get('crm.meeting')
|
||||
res = self.write(cr, uid, ids, {'state': 'declined'}, context)
|
||||
for attandee in self.browse(cr, uid, ids, context=context):
|
||||
meeting_ids = meeting_obj.search(cr, uid, [('attendee_ids', '=', attandee.id)], context=context)
|
||||
if meeting_ids:
|
||||
meeting_obj.message_post(cr, uid, get_real_ids(meeting_ids), body=_(("%s has declined invitation") % (attandee.cn)), context=context)
|
||||
return res
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
"""
|
||||
|
@ -825,6 +739,23 @@ class calendar_alarm(osv.osv):
|
|||
res = super(calendar_alarm, self).create(cr, uid, vals, context=context)
|
||||
return res
|
||||
|
||||
class res_partner(osv.osv):
|
||||
_inherit = 'res.partner'
|
||||
|
||||
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 do_run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, \
|
||||
context=None):
|
||||
"""Scheduler for event reminder
|
||||
|
@ -1083,7 +1014,7 @@ class calendar_event(osv.osv):
|
|||
('tentative', 'Uncertain'),
|
||||
('cancelled', 'Cancelled'),
|
||||
('confirmed', 'Confirmed'),
|
||||
], 'Status', readonly=True),
|
||||
],'Status', readonly=True),
|
||||
'exdate': fields.text('Exception Date/Times', help="This property \
|
||||
defines the list of date/time exceptions for a recurring calendar component."),
|
||||
'exrule': fields.char('Exception Rule', size=352, help="Defines a \
|
||||
|
@ -1149,6 +1080,11 @@ rule or repeating pattern of time to exclude from the recurring rule."),
|
|||
'partner_ids': fields.many2many('res.partner', string='Attendees', states={'done': [('readonly', True)]}),
|
||||
}
|
||||
|
||||
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')
|
||||
|
@ -1162,24 +1098,25 @@ rule or repeating pattern of time to exclude from the recurring rule."),
|
|||
for partner in event.partner_ids:
|
||||
if partner.id in attendees:
|
||||
continue
|
||||
local_context = context.copy()
|
||||
local_context.pop('default_state', None)
|
||||
access_token = self.new_invitation_token(cr, uid, event, partner.id)
|
||||
att_id = self.pool.get('calendar.attendee').create(cr, uid, {
|
||||
'partner_id': partner.id,
|
||||
'user_id': partner.user_ids and partner.user_ids[0].id or False,
|
||||
'ref': self._name+','+str(event.id),
|
||||
'email': partner.email
|
||||
}, context=local_context)
|
||||
'access_token': access_token,
|
||||
'email': partner.email,
|
||||
}, context=context)
|
||||
if partner.email:
|
||||
mail_to = mail_to + " " + partner.email
|
||||
self.write(cr, uid, [event.id], {
|
||||
'attendee_ids': [(4, att_id)]
|
||||
}, context=context)
|
||||
new_attendees.append(att_id)
|
||||
|
||||
if mail_to and current_user.email:
|
||||
att_obj._send_mail(cr, uid, new_attendees, mail_to,
|
||||
is_sent_mail = att_obj._send_mail(cr, uid, new_attendees, mail_to,
|
||||
email_from = current_user.email, context=context)
|
||||
if is_sent_mail:
|
||||
self.message_post(cr, uid, event.id, body=_("An invitation email has been sent to attendee(s)"), context=context)
|
||||
return True
|
||||
|
||||
def default_organizer(self, cr, uid, context=None):
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
import main
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,77 @@
|
|||
import simplejson
|
||||
import urllib
|
||||
import openerp
|
||||
import openerp.addons.web.http as http
|
||||
from openerp.addons.web.http import request
|
||||
import openerp.addons.web.controllers.main as webmain
|
||||
import json
|
||||
from openerp.addons.web.http import SessionExpiredException
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
class meetting_invitation(http.Controller):
|
||||
|
||||
def check_security(self, db, token):
|
||||
registry = openerp.modules.registry.RegistryManager.get(db)
|
||||
attendee_pool = registry.get('calendar.attendee')
|
||||
error_message = False
|
||||
with registry.cursor() as cr:
|
||||
attendee_id = attendee_pool.search(cr, openerp.SUPERUSER_ID, [('access_token','=',token)])
|
||||
if not attendee_id:
|
||||
# if token is not match
|
||||
error_message = """Invalid Invitation Token."""
|
||||
elif request.session.uid and request.session.login != 'anonymous':
|
||||
# if valid session but user is not match
|
||||
attendee = attendee_pool.browse(cr, openerp.SUPERUSER_ID, attendee_id[0])
|
||||
user = registry.get('res.users').browse(cr, openerp.SUPERUSER_ID, request.session.uid)
|
||||
if attendee.user_id.id != user.id:
|
||||
error_message = """Invitation cannot be forwarded via email. This event/meeting belongs to %s and you are logged in as %s. Please ask organizer to add you.""" % (attendee.email, user.email)
|
||||
if error_message:
|
||||
raise BadRequest(error_message)
|
||||
return True
|
||||
|
||||
@http.route('/meeting_invitation/accept', type='http', auth="none")
|
||||
def accept(self, db, token, action, id):
|
||||
# http://hostname:8069/meeting_invitation/accept?db=#token=&action=&id=
|
||||
self.check_security(db, token)
|
||||
registry = openerp.modules.registry.RegistryManager.get(db)
|
||||
attendee_pool = registry.get('calendar.attendee')
|
||||
with registry.cursor() as cr:
|
||||
attendee_id = attendee_pool.search(cr, openerp.SUPERUSER_ID, [('access_token','=',token),('state','!=', 'accepted')])
|
||||
if attendee_id:
|
||||
attendee_pool.do_accept(cr, openerp.SUPERUSER_ID, attendee_id)
|
||||
return self.view(db, token, action, id, view='form')
|
||||
|
||||
@http.route('/meeting_invitation/decline', type='http', auth="none")
|
||||
def declined(self, db, token, action, id):
|
||||
# http://hostname:8069/meeting_invitation/decline?db=#token=&action=&id=
|
||||
self.check_security(db, token)
|
||||
registry = openerp.modules.registry.RegistryManager.get(db)
|
||||
attendee_pool = registry.get('calendar.attendee')
|
||||
with registry.cursor() as cr:
|
||||
attendee_id = attendee_pool.search(cr, openerp.SUPERUSER_ID, [('access_token','=',token),('state','!=', 'declined')])
|
||||
if attendee_id:
|
||||
attendee_pool.do_decline(cr, openerp.SUPERUSER_ID, attendee_id)
|
||||
return self.view(db, token, action, id, view='form')
|
||||
|
||||
@http.route('/meeting_invitation/view', type='http', auth="none")
|
||||
def view(self, db, token, action, id, view='calendar'):
|
||||
# http://hostname:8069/meeting_invitation/view?db=#token=&action=&id=
|
||||
self.check_security(db, token)
|
||||
registry = openerp.modules.registry.RegistryManager.get(db)
|
||||
meeting_pool = registry.get('crm.meeting')
|
||||
attendee_pool = registry.get('calendar.attendee')
|
||||
with registry.cursor() as cr:
|
||||
attendee_data = meeting_pool.get_attendee(cr, openerp.SUPERUSER_ID, id);
|
||||
attendee = attendee_pool.search_read(cr, openerp.SUPERUSER_ID, [('access_token','=',token)],[])
|
||||
if attendee:
|
||||
attendee_data['current_attendee'] = attendee[0]
|
||||
js = "\n ".join('<script type="text/javascript" src="%s"></script>' % i for i in webmain.manifest_list('js', db=db))
|
||||
css = "\n ".join('<link rel="stylesheet" href="%s">' % i for i in webmain.manifest_list('css',db=db))
|
||||
return webmain.html_template % {
|
||||
'js': js,
|
||||
'css': css,
|
||||
'modules': simplejson.dumps(webmain.module_boot(db)),
|
||||
'init': "s.base_calendar.event('%s', '%s', '%s', '%s' , '%s');" % (db, action, id, view, json.dumps(attendee_data)),
|
||||
}
|
||||
|
||||
|
|
@ -22,9 +22,14 @@
|
|||
import time
|
||||
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
|
||||
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from openerp.tools.translate import _
|
||||
from base_calendar import get_real_ids, base_calendar_id2real_id
|
||||
from datetime import datetime, timedelta, date
|
||||
import pytz
|
||||
from openerp import tools
|
||||
import openerp
|
||||
|
||||
#
|
||||
# crm.meeting is defined here so that it may be used by modules other than crm,
|
||||
# without forcing the installation of crm.
|
||||
|
@ -43,6 +48,55 @@ class crm_meeting(osv.Model):
|
|||
_description = "Meeting"
|
||||
_order = "id desc"
|
||||
_inherit = ["calendar.event", "mail.thread", "ir.needaction_mixin"]
|
||||
|
||||
def _find_user_attendee(self, cr, uid, meeting_ids, context=None):
|
||||
attendee_pool = self.pool.get('calendar.attendee')
|
||||
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 _compute(self, cr, uid, ids, fields, arg, context=None):
|
||||
res = {}
|
||||
for meeting_id in ids:
|
||||
res[meeting_id] = {}
|
||||
attendee = self._find_user_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 'needs-action'
|
||||
elif field == 'event_time':
|
||||
res[meeting_id][field] = self._compute_time(cr, uid, meeting_id, context=context)
|
||||
return res
|
||||
|
||||
|
||||
def _compute_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', 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')
|
||||
event_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, event_time, duration.strftime('%H-%M'), tz)
|
||||
else :
|
||||
time = ("%s at %s To\n %s at %s (%s)") % (event_date, event_time, date_deadline.strftime('%B-%d-%Y'), date_deadline.strftime('%H-%M'), tz)
|
||||
return time
|
||||
|
||||
_columns = {
|
||||
'create_date': fields.datetime('Creation Date', readonly=True),
|
||||
'write_date': fields.datetime('Write Date', readonly=True),
|
||||
|
@ -59,16 +113,29 @@ class crm_meeting(osv.Model):
|
|||
'event_id', 'type_id', 'Tags'),
|
||||
'attendee_ids': fields.many2many('calendar.attendee', 'meeting_attendee_rel',\
|
||||
'event_id', 'attendee_id', 'Invited People', 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'),
|
||||
'event_time': fields.function(_compute, string='Event Time', type="char", multi='attendee'),
|
||||
}
|
||||
_defaults = {
|
||||
'state': 'open',
|
||||
}
|
||||
|
||||
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)]
|
||||
return super(crm_meeting, self).search(cr, uid, args, offset=offset, limit=limit, order=order, context=context, count=count)
|
||||
|
||||
def message_get_subscription_data(self, cr, uid, ids, context=None):
|
||||
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], context=context)
|
||||
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
|
||||
|
||||
|
@ -123,8 +190,48 @@ class crm_meeting(osv.Model):
|
|||
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_decline(self, cr, uid, ids, context=None):
|
||||
attendee_pool = self.pool.get('calendar.attendee')
|
||||
attendee = self._find_user_attendee(cr, uid, ids, context)
|
||||
return attendee_pool.do_decline(cr, uid, [attendee.id], context=context)
|
||||
|
||||
def do_accept(self, cr, uid, ids, context=None):
|
||||
attendee_pool = self.pool.get('calendar.attendee')
|
||||
attendee = self._find_user_attendee(cr, uid, ids, context)
|
||||
return attendee_pool.do_accept(cr, uid, [attendee.id], context=context)
|
||||
|
||||
def get_attendee(self, cr, uid, meeting_id, context=None):
|
||||
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,
|
||||
'organizer': meeting.organizer,
|
||||
'where': meeting.location,
|
||||
'when':meeting.event_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):
|
||||
date = datetime.strptime(date, 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
|
||||
|
||||
class mail_message(osv.osv):
|
||||
_inherit = "mail.message"
|
||||
|
||||
|
|
|
@ -28,5 +28,133 @@
|
|||
<field name="name">Meeting</field>
|
||||
<field name="object">crm.meeting</field>
|
||||
</record>
|
||||
<record id="crm_email_template_meeting_invitation" model="email.template">
|
||||
<field name="name">CRM Meeting Invitation</field>
|
||||
<field name="email_from">${object.user_id.email or ''}</field>
|
||||
<field name="subject">${object.name}</field>
|
||||
<field name="email_to" >${ctx['att_obj'].email}</field>
|
||||
<field name="model_id" ref="base_calendar.model_crm_meeting"/>
|
||||
|
||||
<field name="auto_delete" eval="True"/>
|
||||
<field name="body_html"><![CDATA[
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
<title>${object.name}</title>
|
||||
</head>
|
||||
<body>
|
||||
<div style="border-radius: 2px; max-width: 1200px; height: auto;margin-left: auto;margin-right: auto;background-color:#f9f9f9;">
|
||||
<div style="height:auto;text-align: center;font-size : 30px;color: #8A89BA;">
|
||||
<strong>${object.name}</strong>
|
||||
</div>
|
||||
<div style="height: 50px;text-align: left;font-size : 14px;border-collapse: separate;margin-top:10px">
|
||||
<strong style="margin-left:12px">Hello ${ctx['att_obj'].cn}</strong> ,<br/><p style="margin-left:12px">${object.organizer} invited you for the ${object.name} meeting of ${object.user_id.company_id.name}.</p>
|
||||
</div>
|
||||
<div style="height: auto;margin-left:12px;margin-top:30px;">
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<div style="border-top-left-radius:3px;border-top-right-radius:3px;font-size:12px;border-collapse:separate;text-align:center;font-weight:bold;color:#ffffff;width:130px;min-height: 18px;border-color:#ffffff;background:#8a89ba;padding-top: 4px;">${object.get_interval(object.date, 'dayname')}</div>
|
||||
<div style="font-size:48px;min-height:auto;font-weight:bold;text-align:center;color: #5F5F5F;background-color: #E1E2F8;width: 130px;">
|
||||
${object.get_interval(object.date,'day')}
|
||||
</div>
|
||||
<div style='font-size:12px;text-align:center;font-weight:bold;color:#ffffff;background-color:#8a89ba'>${object.get_interval(object.date, 'month')}</div>
|
||||
<div style="border-collapse:separate;color:#8a89ba;text-align:center;width: 128px;font-size:12px;border-bottom-right-radius:3px;font-weight:bold;border:1px solid;border-bottom-left-radius:3px;">${object.get_interval(object.date, 'time')}</div>
|
||||
</td>
|
||||
<td>
|
||||
<table cellspacing="0" cellpadding="0" border="0" style="margin-top: 15px; margin-left: 10px;font-size: 16px;">
|
||||
% if object.location :
|
||||
<tr style=" height: 30px;">
|
||||
<td style="vertical-align:top;">
|
||||
<div style="height: 25px; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
Where
|
||||
</div>
|
||||
</td>
|
||||
<td colspan="1" style="vertical-align:top;">
|
||||
<div style = "font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 14px" >
|
||||
: ${object.location}
|
||||
<span style= "color:#A9A9A9; ">(<a href="http://maps.google.com/maps?oi=map&q=${object.location}">View Map</a>)
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
% endif
|
||||
% if not object.location :
|
||||
<tr style=" height: 30px;color:#909090">
|
||||
<td>
|
||||
<div style="height: 25px; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
Where
|
||||
</div>
|
||||
</td>
|
||||
<td colspan="1">
|
||||
<div style = "font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 14px;" >
|
||||
: -
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
% endif
|
||||
% if object.description :
|
||||
<tr style=" height:auto;">
|
||||
<td style="vertical-align:top;">
|
||||
<div style="height:auto; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
What
|
||||
</div>
|
||||
</td>
|
||||
<td colspan="3" style="vertical-align:text-top;">
|
||||
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
: ${object.description or ''}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
% endif
|
||||
% if not object.description :
|
||||
<tr style=" height: 30px;color:#909090">
|
||||
<td style="vertical-align:top;">
|
||||
<div style="height: 25px; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
What
|
||||
</div>
|
||||
</td>
|
||||
<td colspan="3" style="vertical-align:text-top;">
|
||||
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
: -
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
% endif
|
||||
<tr style=" height: 30px;">
|
||||
<td style="height: 25px;width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
|
||||
<div>
|
||||
Attendees
|
||||
</div>
|
||||
</td>
|
||||
<td colspan="3">
|
||||
: <div style='display:inline-block; border-radius: 50%; width:10px; height:10px;background:grey;'></div>
|
||||
<span style="margin-left:5px">You</span>
|
||||
|
||||
% for attendee in object.attendee_ids:
|
||||
% if attendee.cn != ctx['att_obj'].cn:
|
||||
<div style='display:inline-block; border-radius: 50%; width:10px; height:10px;background:${ctx['color'][attendee.state]};'></div>
|
||||
<span style="margin-left:5px">${attendee.cn}</span>
|
||||
% endif
|
||||
% endfor
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div style="height: auto;width:300px; margin:0 auto;padding-top:20px;">
|
||||
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#8A89BA;margin : 0 15px 0 0;text-decoration: none;color:#FFFFFF;" href="${ctx['base_url']}/meeting_invitation/accept?db=${ctx['dbname']}&token=${ctx['att_obj'].access_token}&action=${ctx['action_id']}&id=${object.id}">Accept</a>
|
||||
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#808080;text-decoration: none;color:#FFFFFF;" href="${ctx['base_url']}/meeting_invitation/decline?db=${ctx['dbname']}&token=${ctx['att_obj'].access_token}&action=${ctx['action_id']}&id=${object.id}">Decline</a>
|
||||
</div>
|
||||
<div style="padding-top:10px;">
|
||||
-- </br> Sent by ${object.user_id.name} from ${object.user_id.company_id.name}. View this meeting detail <a href="${ctx['base_url']}/meeting_invitation/view?db=${ctx['dbname']}&token=${ctx['att_obj'].access_token}&action=${ctx['action_id']}&id=${object.id}">directly in OpenERP.</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
]]></field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -32,8 +32,16 @@
|
|||
<field name="model">crm.meeting</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Meetings" version="7.0">
|
||||
<field name="state" invisible="True"/>
|
||||
<header>
|
||||
<button name="do_accept" type="object"
|
||||
string="Accept" attrs="{'invisible':['|',('is_attendee','=',False),('attendee_status','=','accepted')]}"/>
|
||||
<button name="do_decline" type="object"
|
||||
string="Decline" attrs="{'invisible':['|',('is_attendee','=',False),('attendee_status','=','declined')]}"/>
|
||||
<field name="state" invisible="True"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<field name="is_attendee" invisible="1"/>
|
||||
<field name="attendee_status" invisible="1"/>
|
||||
<div class="oe_title">
|
||||
<div class="oe_edit_only">
|
||||
<label for="name"/>
|
||||
|
@ -43,7 +51,7 @@
|
|||
</h1>
|
||||
<label for="partner_ids" class="oe_edit_only"/>
|
||||
<h2>
|
||||
<field name="partner_ids" widget="many2many_tags"
|
||||
<field name="partner_ids" widget="many2manyattendee"
|
||||
context="{'force_email':True}"
|
||||
on_change="onchange_partner_ids(partner_ids)"/>
|
||||
</h2>
|
||||
|
@ -133,16 +141,16 @@
|
|||
</group>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Invitations">
|
||||
<page string="Invitations" groups="base.group_no_one">
|
||||
<field name="attendee_ids" widget="one2many" mode="tree">
|
||||
<tree string="Invitation details" editable="top">
|
||||
<tree string="Invitation details" editable="top" >
|
||||
<field name="partner_id" on_change="onchange_partner_id(partner_id)"/>
|
||||
<field name="email" string="Mail To"/>
|
||||
<field name="state"/>
|
||||
<button name="do_tentative"
|
||||
states="needs-action,declined,accepted"
|
||||
string="Uncertain" type="object"
|
||||
icon="terp-crm"/>
|
||||
icon="terp-crm" />
|
||||
<button name="do_accept" string="Accept"
|
||||
states="needs-action,tentative,declined"
|
||||
type="object" icon="gtk-apply"/>
|
||||
|
@ -181,6 +189,10 @@
|
|||
|
||||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread" />
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
@ -238,7 +250,7 @@
|
|||
<field name="categ_ids"/>
|
||||
<field name="user_id"/>
|
||||
<separator/>
|
||||
<filter string="My Meetings" help="My Meetings" domain="[('user_id','=',uid)]"/>
|
||||
<filter string="My Meetings" help="My Meetings" name="mymeetings" context='{"mymeetings": 1}'/>
|
||||
<filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
|
||||
</search>
|
||||
</field>
|
||||
|
@ -252,7 +264,7 @@
|
|||
<field name="view_mode">calendar,tree,form,gantt</field>
|
||||
<field name="view_id" ref="view_crm_meeting_calendar"/>
|
||||
<field name="search_view_id" ref="view_crm_meeting_search"/>
|
||||
<field name="context">{"calendar_default_user_id": uid}</field>
|
||||
<field name="context">{"search_default_mymeetings": 1}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to schedule a new meeting.
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
.. _calendar_attendee:
|
||||
calendar.attendee:
|
||||
=================
|
||||
|
||||
Fields
|
||||
++++++
|
||||
- ``access_token`` :
|
||||
unique value(token) for every new attendee.
|
||||
|
||||
Methods
|
||||
+++++++
|
||||
- ``do_accept``:
|
||||
REF : post message in chatter when attendee accepted an invitation.
|
||||
- ``do_decline``:
|
||||
REF : post message in chatter when attendee declined an invitation.
|
||||
|
||||
calendar.event:
|
||||
===============
|
||||
Methods
|
||||
+++++++
|
||||
- ``new_invitation_token``:
|
||||
generate a unique token for every new attendee.
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
.. _changelog:
|
||||
|
||||
Changelog
|
||||
=========
|
||||
|
||||
Email Template of Meeting Invitation:
|
||||
+++++++++++++++++++++++++++++++++++++
|
||||
- remove static code of HTML design of email of meeting invitation
|
||||
- added new better layout of email of meeting invitation using MAKO Template.
|
||||
|
||||
Web controller:
|
||||
+++++++++++++++
|
||||
- ``accept`` :
|
||||
handle request ('meeting_invitation/accept') ,when accepted an invitation it change the status of invitation as accepted , user do need to login in system.
|
||||
- ``declined``:
|
||||
handle request ('meeting_invitation/decline') ,when declined an invitation it change the status of invitation as declined , user do need to login in system.
|
||||
- ``view``:
|
||||
handle request ('meeting_invitation/view') ,when user click on accept,declined link button , it redirect user to form view if user is already login and if user has not been login it redirect to a simple qweb template to inform user has accepted/declined a meeting ,if user click on directly in openerp it redirect user to a meeting calendar view , if user is not login then it redirect to a qweb template.
|
||||
- ``check_security``:
|
||||
check token is valid and user is not allow to accept/decline invitation mail of other user from email template URL.
|
||||
|
||||
Web Widget:
|
||||
+++++++++++
|
||||
- ``Field Many2Many_invite``(widget):
|
||||
display a status button in left side of every invited attendees of meeting , in many2many.
|
||||
|
||||
Qweb Template:
|
||||
++++++++++++++
|
||||
- added template ,to directly allow any invited user to accept , decline a meeting , if user do not need to login in the system to accept or decline an invitation.
|
|
@ -0,0 +1,38 @@
|
|||
.. _crm_meeting:
|
||||
|
||||
Fields:
|
||||
+++++++
|
||||
- ``is_attendee`` :
|
||||
function field , that defined whether loged in user is attendee or not.
|
||||
- ``attendee_status``:
|
||||
function field , that defined login user status, either accepted, declined or needs-action.
|
||||
- ``event_time``:
|
||||
function field, defined an event_time in user's tz.
|
||||
|
||||
Methods:
|
||||
++++++++
|
||||
- ``_find_user_attendee``:
|
||||
return attendee if attendee is internal user else false.
|
||||
- ``_compute_time``:
|
||||
compute a time from date_start and duration with user's tz.
|
||||
- ``search``:
|
||||
search a current user's meetings
|
||||
- ``do_accept/do_decline``:
|
||||
trigger when ,user accept/decline from the meeting form view.
|
||||
- ``get_attendee``:
|
||||
get detail of attendees meeting.
|
||||
- ``get_interval``:
|
||||
call from email template that return formate of date, as per value pass from the email template.
|
||||
|
||||
views:
|
||||
++++++
|
||||
- ``do_accept``:
|
||||
Accept button in meeting form view that is allow a user to accept a meeting ,that is visible to only attendee and if attendee state is other than accepted.
|
||||
- ``do_decline``:
|
||||
Decline button in meeting form view that is allow a user to accept a meeting ,that is visible to only attendee and if attendee state is other than declined.
|
||||
- ``chatter(message_ids)``:
|
||||
show a log of meeting.
|
||||
|
||||
security:
|
||||
+++++++++
|
||||
- added record rule to restrict an user to show personal invitation on meeting , so user can't change other's status , from invitation tab.
|
|
@ -5,5 +5,5 @@
|
|||
<field name="name">Survey / User</field>
|
||||
<field name="users" eval="[(4, ref('base.user_root'))]"/>
|
||||
</record>
|
||||
</data>
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -7,7 +7,7 @@ access_calendar_event,calendar.event,model_calendar_event,base.group_user,1,1,1,
|
|||
access_calendar_attendee_survey_user,calendar.attendee,model_calendar_attendee,base.group_survey_user,1,0,0,0
|
||||
access_crm_meeting_manager,crm.meeting.manager,model_crm_meeting,base.group_sale_manager,1,1,1,1
|
||||
access_crm_meeting,crm.meeting,model_crm_meeting,base.group_sale_salesman,1,1,1,0
|
||||
access_crm_meeting_all,crm.meeting_allll,model_crm_meeting,base.group_user,1,0,0,0
|
||||
access_crm_meeting_all,crm.meeting_allll,model_crm_meeting,base.group_user,1,1,0,0
|
||||
access_crm_meeting_partner_manager,crm.meeting.partner.manager,model_crm_meeting,base.group_partner_manager,1,1,1,1
|
||||
access_crm_meeting_type_sale_manager,crm.meeting.type.manager,model_crm_meeting_type,base.group_sale_manager,1,1,1,0
|
||||
access_crm_meeting_type_sale_user,crm.meeting.type.user,model_crm_meeting_type,base.group_user,1,0,0,0
|
||||
|
|
|
|
@ -0,0 +1,65 @@
|
|||
.openerp .oe_invitation , .text-core .text-tag .oe_invitation{
|
||||
width : 13px;
|
||||
height : 13px;
|
||||
margin-bottom : -4px;
|
||||
display : inline-block;
|
||||
}
|
||||
.openerp .needs-action , .tentative,.text-core .text-tag .custom-edit, .text-core .text-tag .tentative {
|
||||
background : url(/web/static/src/img/icons/gtk-normal.png) no-repeat;
|
||||
background-size : 11px 11px;
|
||||
}
|
||||
.openerp .accepted , .text-core .text-tag .accepted {
|
||||
background : url(/web/static/src/img/icons/gtk-yes.png) no-repeat;
|
||||
background-size : 11px 11px;
|
||||
}
|
||||
.openerp .declined , .text-core .text-tag .declined {
|
||||
background : url(/web/static/src/img/icons/gtk-no.png) no-repeat;
|
||||
background-size : 11px 11px;
|
||||
}
|
||||
.cal_meeting {
|
||||
font-size : 24px;
|
||||
font-style: bold;
|
||||
text-align : justify;
|
||||
color : #8A89BA;
|
||||
}
|
||||
.cal_lable {
|
||||
width: 50px;
|
||||
color : #808080;
|
||||
}
|
||||
.invitation_block {
|
||||
padding : 50px 0 0 30px;
|
||||
font-size : 14px;
|
||||
background : #f9f9f9;
|
||||
}
|
||||
.attendee_accepted {
|
||||
background : url(/web/static/src/img/icons/gtk-apply.png) no-repeat;
|
||||
background-size : 15px 15px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
.attendee_declined {
|
||||
background : url(/web/static/src/img/icons/gtk-cancel.png) no-repeat;
|
||||
background-size : 15px 15px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
.event_status {
|
||||
border : 1px solid;
|
||||
height : 20px;
|
||||
width : auto;
|
||||
background: #808080;
|
||||
color : #FFFFFF;
|
||||
padding: 5px 10px;
|
||||
width: 400px;
|
||||
}
|
||||
.cal_inline {
|
||||
display: inline;
|
||||
}
|
||||
.cal_tag {
|
||||
padding-right : 10px;
|
||||
font-style : italic;
|
||||
font-size : 17px;
|
||||
vertical-align:bottom;
|
||||
}
|
||||
.cal_image {
|
||||
height: 30px;
|
||||
width : 100px;
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
openerp.base_calendar = function(instance) {
|
||||
var _t = instance.web._t;
|
||||
var QWeb = instance.web.qweb;
|
||||
instance.base_calendar = {}
|
||||
|
||||
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: '<div class="text-tag"><div class="text-button"><a class="oe_invitation custom-edit"/><span class="text-label"/><a class="text-remove"/></div></div>'
|
||||
}
|
||||
});
|
||||
},
|
||||
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'));
|
||||
});
|
||||
}
|
||||
};
|
||||
//vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
|
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<template>
|
||||
<t t-name="many2manyattendee">
|
||||
<t t-set="i" t-value="0"/>
|
||||
<t t-foreach="elements" t-as="el">
|
||||
<span class="oe_tag" t-att-data-index="i">
|
||||
<a t-attf-class="oe_invitation #{el[2]}"/>
|
||||
<t t-esc="el[1]"/>
|
||||
</span>
|
||||
<t t-set="i" t-value="i + 1"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-name="invitation_view">
|
||||
<div class="oe_right"><b><t t-esc="invitation['current_attendee'].cn"/> (<t t-esc="invitation['current_attendee'].email"/>)</b></div>
|
||||
<div class="oe_left"><img class="cal_inline cal_image" t-attf-src="data:image/png;base64,#{invitation['logo']}"/><p class="cal_tag cal_inline">Calendar</p></div>
|
||||
<div class="invitation_block">
|
||||
<t t-if="invitation['current_attendee'].state != 'needs-action'">
|
||||
<div class="event_status"><a t-attf-class="attendee_#{invitation['current_attendee'].state}"><b t-if="invitation['current_attendee'].state == 'accepted'">Yes I'm going.</b><b t-if="invitation['current_attendee'].state == 'declined'">No I'm not going.</b></a></div>
|
||||
</t>
|
||||
<div class="cal_meeting"><t t-esc="invitation['meeting'].event"/></div>
|
||||
<table calss="invitation_block">
|
||||
<tr>
|
||||
<td class="cal_lable">When</td>
|
||||
<td>: <t t-esc="invitation['meeting'].when"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="cal_lable">Where</td>
|
||||
<td>: <t t-esc="invitation['meeting'].where or '-'"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="cal_lable">Who</td>
|
||||
<td>
|
||||
<span>: <t t-esc="invitation['meeting'].organizer"/> - <a class="cal_lable">Organizer</a></span>
|
||||
<t t-foreach="invitation['attendee']" t-as="att">
|
||||
<br/>
|
||||
<span class="cal_status"><a t-attf-class="oe_invitation #{att.status}"/><t t-esc="att.name"/></span>
|
||||
</t>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
|
@ -6,4 +6,4 @@ access_mail_followers_portal,mail.followers.portal,mail.model_mail_followers,gro
|
|||
access_res_partner_portal,res.partner.portal,base.model_res_partner,portal.group_portal,1,0,0,0
|
||||
access_acquirer,portal.payment.acquirer,portal.model_portal_payment_acquirer,,1,0,0,0
|
||||
access_acquirer_all,portal.payment.acquirer,portal.model_portal_payment_acquirer,base.group_system,1,1,1,1
|
||||
access_ir_attachment_group_portal,ir.attachment group_portal,base.model_ir_attachment,portal.group_portal,1,0,1,0
|
||||
access_ir_attachment_group_portal,ir.attachment group_portal,base.model_ir_attachment,portal.group_portal,1,0,1,0
|
||||
|
|
|
|
@ -33,6 +33,7 @@ This module adds a contact page (with a contact form creating a lead when submit
|
|||
'depends': ['crm','portal'],
|
||||
'data': [
|
||||
'contact_view.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'test': [
|
||||
'test/contact_form.yml',
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_crm_meeting_portal,crm.meeting.portal,base_calendar.model_crm_meeting,portal.group_portal,1,1,0,0
|
||||
access_crm_meeting_type_portal,crm.meeting.type.portal,base_calendar.model_crm_meeting_type,portal.group_portal,1,0,0,0
|
|
|
@ -56,3 +56,16 @@ class hr_employee(osv.osv):
|
|||
_defaults = {
|
||||
'visibility': 'private',
|
||||
}
|
||||
|
||||
class calendar_attendee(osv.osv):
|
||||
_inherit = 'calendar.attendee'
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
user_pool = self.pool.get('res.users')
|
||||
partner_id = vals.get('partner_id')
|
||||
users = user_pool.search_read(cr, uid, [('partner_id','=', partner_id)],['employee_ids'], context=context)
|
||||
for user in users:
|
||||
if user['employee_ids']:
|
||||
vals['state'] = 'accepted'
|
||||
return super(calendar_attendee, self).create(cr, uid, vals, context=context)
|
||||
|
||||
|
|
Loading…
Reference in New Issue